@crystin001/theme-settings-lib 1.0.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/README.md +368 -0
- package/dist/color-utils.d.ts +24 -0
- package/dist/color-utils.d.ts.map +1 -0
- package/dist/color-utils.js +104 -0
- package/dist/hooks/use-theme-color.d.ts +15 -0
- package/dist/hooks/use-theme-color.d.ts.map +1 -0
- package/dist/hooks/use-theme-color.js +34 -0
- package/dist/hooks/use-translation.d.ts +10 -0
- package/dist/hooks/use-translation.d.ts.map +1 -0
- package/dist/hooks/use-translation.js +16 -0
- package/dist/i18n.d.ts +23 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +76 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/settings-context.d.ts +24 -0
- package/dist/settings-context.d.ts.map +1 -0
- package/dist/settings-context.js +144 -0
- package/dist/theme.d.ts +49 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +447 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Theme & Settings Library
|
|
2
|
+
|
|
3
|
+
A reusable library for theme management, settings, and internationalization that can be shared across multiple React Native applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Multiple Theme Families** - 8 theme families with light and dark variants
|
|
8
|
+
- 🌍 **Internationalization** - Built-in i18n support with extensible translations
|
|
9
|
+
- 💾 **Persistent Settings** - Theme and language preferences saved automatically
|
|
10
|
+
- ♿ **Accessibility** - WCAG-compliant contrast utilities
|
|
11
|
+
- 🔧 **Type-Safe** - Full TypeScript support with declaration files
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Option A: Git Repository (Recommended)
|
|
16
|
+
|
|
17
|
+
Add to your `package.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@crystin001/theme-settings-lib": "git+https://github.com/trial-and-code/theme-settings-lib.git"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then run `npm install`.
|
|
28
|
+
|
|
29
|
+
To install a specific version or branch:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@crystin001/theme-settings-lib": "git+https://github.com/trial-and-code/theme-settings-lib.git#v1.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Option B: Local Package (Monorepo)
|
|
40
|
+
|
|
41
|
+
If the library is in the same repository:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@crystin001/theme-settings-lib": "file:../packages/theme-settings-lib"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option C: npm Package (Future)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install @crystin001/theme-settings-lib
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### 1. Wrap Your App with SettingsProvider
|
|
60
|
+
|
|
61
|
+
Wrap your root component with `SettingsProvider` to enable theme and settings management:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// app/_layout.tsx or App.tsx
|
|
65
|
+
import { SettingsProvider } from '@crystin001/theme-settings-lib';
|
|
66
|
+
import { Stack } from 'expo-router';
|
|
67
|
+
|
|
68
|
+
export default function RootLayout() {
|
|
69
|
+
return (
|
|
70
|
+
<SettingsProvider>
|
|
71
|
+
<Stack>
|
|
72
|
+
{/* Your app screens */}
|
|
73
|
+
</Stack>
|
|
74
|
+
</SettingsProvider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. Use Theme Hooks in Your Components
|
|
80
|
+
|
|
81
|
+
Access theme colors and settings in your components:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { View, Text, Button } from 'react-native';
|
|
85
|
+
import { useTheme, useSettings } from '@crystin001/theme-settings-lib';
|
|
86
|
+
|
|
87
|
+
function MyComponent() {
|
|
88
|
+
const colors = useTheme();
|
|
89
|
+
const { setThemeFamily } = useSettings();
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<View style={{ backgroundColor: colors.background }}>
|
|
93
|
+
<Text style={{ color: colors.text }}>Hello World</Text>
|
|
94
|
+
<Button
|
|
95
|
+
onPress={() => setThemeFamily('neon-surf')}
|
|
96
|
+
title="Change to Neon Surf"
|
|
97
|
+
/>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Use Translations
|
|
104
|
+
|
|
105
|
+
Access translations using the `useTranslation` hook:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { Text } from 'react-native';
|
|
109
|
+
import { useTranslation } from '@crystin001/theme-settings-lib';
|
|
110
|
+
|
|
111
|
+
function MyComponent() {
|
|
112
|
+
const { t } = useTranslation();
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Text>{t('common.welcome')}</Text>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Get Specific Theme Colors
|
|
121
|
+
|
|
122
|
+
Use `useThemeColor` to get specific colors from the current theme:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { View, Text } from 'react-native';
|
|
126
|
+
import { useThemeColor } from '@crystin001/theme-settings-lib';
|
|
127
|
+
|
|
128
|
+
function MyComponent() {
|
|
129
|
+
const backgroundColor = useThemeColor({}, 'background');
|
|
130
|
+
const textColor = useThemeColor({}, 'text');
|
|
131
|
+
const primaryColor = useThemeColor({}, 'primary');
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<View style={{ backgroundColor }}>
|
|
135
|
+
<Text style={{ color: textColor }}>Primary: {primaryColor}</Text>
|
|
136
|
+
</View>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 5. Extend Translations
|
|
142
|
+
|
|
143
|
+
Add app-specific translations by extending the i18n instance:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { i18n } from '@crystin001/theme-settings-lib';
|
|
147
|
+
|
|
148
|
+
// Add custom translations
|
|
149
|
+
i18n.addResourceBundle('en-US', 'translation', {
|
|
150
|
+
myFeature: {
|
|
151
|
+
title: 'My Feature',
|
|
152
|
+
description: 'This is my feature',
|
|
153
|
+
},
|
|
154
|
+
}, true, true);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 6. Complete Example: Integration with React Navigation
|
|
158
|
+
|
|
159
|
+
Here's a complete example showing integration with React Navigation:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// app/_layout.tsx
|
|
163
|
+
import { Stack } from 'expo-router';
|
|
164
|
+
import { StatusBar } from 'expo-status-bar';
|
|
165
|
+
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
|
166
|
+
import { SettingsProvider, useSettings } from '@crystin001/theme-settings-lib';
|
|
167
|
+
|
|
168
|
+
function RootLayoutNav() {
|
|
169
|
+
const { currentTheme } = useSettings();
|
|
170
|
+
const isDarkTheme = currentTheme.endsWith('-dark');
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<ThemeProvider value={isDarkTheme ? DarkTheme : DefaultTheme}>
|
|
174
|
+
<Stack>
|
|
175
|
+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
176
|
+
</Stack>
|
|
177
|
+
<StatusBar style={isDarkTheme ? 'light' : 'dark'} />
|
|
178
|
+
</ThemeProvider>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default function RootLayout() {
|
|
183
|
+
return (
|
|
184
|
+
<SettingsProvider>
|
|
185
|
+
<RootLayoutNav />
|
|
186
|
+
</SettingsProvider>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Available Theme Families
|
|
192
|
+
|
|
193
|
+
- **System** - Follows device theme preference (default)
|
|
194
|
+
- **Neon Surf** - Vibrant neon colors (hot pink, cyan, yellow, orange)
|
|
195
|
+
- **Pink & Purple** - Soft purple and pink tones
|
|
196
|
+
- **Pastel** - Gentle pastel colors
|
|
197
|
+
- **GitHub** - GitHub-inspired theme
|
|
198
|
+
- **High Contrast** - Maximum contrast for accessibility
|
|
199
|
+
- **Solarized** - Solarized color scheme
|
|
200
|
+
- **Dark Plus** - Dark theme with blue accents
|
|
201
|
+
|
|
202
|
+
Each theme family has both light and dark variants that automatically switch based on device settings.
|
|
203
|
+
|
|
204
|
+
## API Reference
|
|
205
|
+
|
|
206
|
+
### Hooks
|
|
207
|
+
|
|
208
|
+
#### `useTheme()`
|
|
209
|
+
|
|
210
|
+
Returns all colors from the current theme.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
const colors = useTheme();
|
|
214
|
+
// Returns: { text, background, tint, icon, ... }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `useThemeColor(props?, colorName?)`
|
|
218
|
+
|
|
219
|
+
Get a specific color from the current theme.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
const backgroundColor = useThemeColor({}, 'background');
|
|
223
|
+
const textColor = useThemeColor({ light: '#000', dark: '#fff' }, 'text');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### `useSettings()`
|
|
227
|
+
|
|
228
|
+
Access theme and language settings.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
const {
|
|
232
|
+
selectedThemeFamily,
|
|
233
|
+
currentTheme,
|
|
234
|
+
setThemeFamily,
|
|
235
|
+
language,
|
|
236
|
+
setLanguage,
|
|
237
|
+
availableThemeFamilies,
|
|
238
|
+
availableLanguages
|
|
239
|
+
} = useSettings();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### `useTranslation()`
|
|
243
|
+
|
|
244
|
+
Get the translation function.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
const { t } = useTranslation();
|
|
248
|
+
const welcomeText = t('common.welcome');
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Settings Context
|
|
252
|
+
|
|
253
|
+
The `useSettings()` hook returns:
|
|
254
|
+
|
|
255
|
+
- `selectedThemeFamily` - Currently selected theme family (e.g., `'neon-surf'`)
|
|
256
|
+
- `currentTheme` - Effective theme being used (includes variant, e.g., `'neon-surf-light'`)
|
|
257
|
+
- `setThemeFamily(family)` - Change theme family
|
|
258
|
+
- `language` - Current language code (e.g., `'en-US'`)
|
|
259
|
+
- `setLanguage(lang)` - Change language
|
|
260
|
+
- `availableThemeFamilies` - Array of all available theme families with metadata
|
|
261
|
+
- `availableLanguages` - Array of all available languages
|
|
262
|
+
|
|
263
|
+
### Color Utilities
|
|
264
|
+
|
|
265
|
+
#### `getContrastRatio(color1, color2)`
|
|
266
|
+
|
|
267
|
+
Calculate the contrast ratio between two colors (returns 1-21).
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { getContrastRatio } from '@crystin001/theme-settings-lib';
|
|
271
|
+
|
|
272
|
+
const ratio = getContrastRatio('#000000', '#FFFFFF'); // Returns ~21
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### `getContrastTextColor(backgroundColor, lightColor?, darkColor?, minContrast?)`
|
|
276
|
+
|
|
277
|
+
Get a readable text color for a given background.
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { getContrastTextColor } from '@crystin001/theme-settings-lib';
|
|
281
|
+
|
|
282
|
+
const textColor = getContrastTextColor('#000000'); // Returns '#FFFFFF'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### `meetsContrastRequirement(foreground, background, level?)`
|
|
286
|
+
|
|
287
|
+
Check if a color combination meets WCAG contrast requirements.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
import { meetsContrastRequirement } from '@crystin001/theme-settings-lib';
|
|
291
|
+
|
|
292
|
+
const isAccessible = meetsContrastRequirement('#000000', '#FFFFFF', 'AA'); // Returns true
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### `ensureContrast(foreground, background, level?)`
|
|
296
|
+
|
|
297
|
+
Ensure a color combination has readable contrast, adjusting if needed.
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { ensureContrast } from '@crystin001/theme-settings-lib';
|
|
301
|
+
|
|
302
|
+
const readableColor = ensureContrast('#888888', '#FFFFFF', 'AA');
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Development
|
|
306
|
+
|
|
307
|
+
### Building the Library
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npm run build
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
This compiles TypeScript to JavaScript and generates type declaration files in the `dist/` folder.
|
|
314
|
+
|
|
315
|
+
### Project Structure
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
theme-settings-lib/
|
|
319
|
+
├── src/
|
|
320
|
+
│ ├── hooks/ # React hooks
|
|
321
|
+
│ ├── theme.ts # Theme definitions
|
|
322
|
+
│ ├── i18n.ts # Internationalization setup
|
|
323
|
+
│ ├── settings-context.tsx # Settings provider
|
|
324
|
+
│ ├── color-utils.ts # Color utility functions
|
|
325
|
+
│ └── index.ts # Main exports
|
|
326
|
+
├── dist/ # Compiled output (generated)
|
|
327
|
+
├── package.json
|
|
328
|
+
└── tsconfig.json
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Updating the Library
|
|
332
|
+
|
|
333
|
+
To update themes or translations across all apps:
|
|
334
|
+
|
|
335
|
+
1. Make changes in `src/` directory
|
|
336
|
+
2. Run `npm run build` to compile
|
|
337
|
+
3. Update the version in `package.json`
|
|
338
|
+
4. Commit and push to the repository
|
|
339
|
+
5. In each app, update the dependency version and run `npm install`
|
|
340
|
+
6. Apps will automatically get the latest themes and translations
|
|
341
|
+
|
|
342
|
+
## Versioning
|
|
343
|
+
|
|
344
|
+
The library follows [Semantic Versioning](https://semver.org/):
|
|
345
|
+
|
|
346
|
+
- **Major** (x.0.0) - Breaking API changes
|
|
347
|
+
- **Minor** (0.x.0) - New themes, features, or languages
|
|
348
|
+
- **Patch** (0.0.x) - Bug fixes and theme adjustments
|
|
349
|
+
|
|
350
|
+
## Peer Dependencies
|
|
351
|
+
|
|
352
|
+
This library requires the following peer dependencies (installed by your app):
|
|
353
|
+
|
|
354
|
+
- `react` - React library
|
|
355
|
+
- `react-native` - React Native framework
|
|
356
|
+
- `i18next` - Internationalization framework
|
|
357
|
+
- `react-i18next` - React bindings for i18next
|
|
358
|
+
- `expo-localization` - Expo localization utilities
|
|
359
|
+
- `@react-native-async-storage/async-storage` - Async storage for persistence
|
|
360
|
+
- `color` (optional) - Color manipulation library
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT
|
|
365
|
+
|
|
366
|
+
## Repository
|
|
367
|
+
|
|
368
|
+
[https://github.com/trial-and-code/theme-settings-lib](https://github.com/trial-and-code/theme-settings-lib)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the contrast ratio between two colors
|
|
3
|
+
* Returns a value between 1 (no contrast) and 21 (maximum contrast)
|
|
4
|
+
* WCAG requires at least 4.5:1 for normal text and 3:1 for large text
|
|
5
|
+
*/
|
|
6
|
+
export declare function getContrastRatio(color1: string, color2: string): number;
|
|
7
|
+
/**
|
|
8
|
+
* Determine if text should be light (white) or dark (black) based on background color
|
|
9
|
+
* Uses WCAG contrast guidelines to ensure readability
|
|
10
|
+
*/
|
|
11
|
+
export declare function getContrastTextColor(backgroundColor: string, lightColor?: string, darkColor?: string, minContrast?: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a color combination meets WCAG contrast requirements
|
|
14
|
+
* @param foreground - Text color
|
|
15
|
+
* @param background - Background color
|
|
16
|
+
* @param level - 'AA' (4.5:1) or 'AAA' (7:1) for normal text, or 'AALarge' (3:1) for large text
|
|
17
|
+
*/
|
|
18
|
+
export declare function meetsContrastRequirement(foreground: string, background: string, level?: 'AA' | 'AAA' | 'AALarge'): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Get a color that meets contrast requirements with a background
|
|
21
|
+
* Tries to find a readable version of the foreground color
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensureContrast(foreground: string, background: string, level?: 'AA' | 'AAA' | 'AALarge'): string;
|
|
24
|
+
//# sourceMappingURL=color-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-utils.d.ts","sourceRoot":"","sources":["../src/color-utils.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAavE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,EACvB,UAAU,GAAE,MAAkB,EAC9B,SAAS,GAAE,MAAkB,EAC7B,WAAW,GAAE,MAAY,GACxB,MAAM,CAgCR;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,IAAI,GAAG,KAAK,GAAG,SAAgB,GACrC,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,IAAI,GAAG,KAAK,GAAG,SAAgB,GACrC,MAAM,CAOR"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getContrastRatio = getContrastRatio;
|
|
7
|
+
exports.getContrastTextColor = getContrastTextColor;
|
|
8
|
+
exports.meetsContrastRequirement = meetsContrastRequirement;
|
|
9
|
+
exports.ensureContrast = ensureContrast;
|
|
10
|
+
const color_1 = __importDefault(require("color"));
|
|
11
|
+
/**
|
|
12
|
+
* Calculate the relative luminance of a color (0-1)
|
|
13
|
+
* Based on WCAG 2.1 guidelines
|
|
14
|
+
*/
|
|
15
|
+
function getLuminance(color) {
|
|
16
|
+
const rgb = color.rgb().array();
|
|
17
|
+
const [r, g, b] = rgb.map((val) => {
|
|
18
|
+
val = val / 255;
|
|
19
|
+
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
|
|
20
|
+
});
|
|
21
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calculate the contrast ratio between two colors
|
|
25
|
+
* Returns a value between 1 (no contrast) and 21 (maximum contrast)
|
|
26
|
+
* WCAG requires at least 4.5:1 for normal text and 3:1 for large text
|
|
27
|
+
*/
|
|
28
|
+
function getContrastRatio(color1, color2) {
|
|
29
|
+
try {
|
|
30
|
+
const c1 = (0, color_1.default)(color1);
|
|
31
|
+
const c2 = (0, color_1.default)(color2);
|
|
32
|
+
const l1 = getLuminance(c1);
|
|
33
|
+
const l2 = getLuminance(c2);
|
|
34
|
+
const lighter = Math.max(l1, l2);
|
|
35
|
+
const darker = Math.min(l1, l2);
|
|
36
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn('Failed to calculate contrast ratio:', error);
|
|
40
|
+
return 1; // Return minimum contrast on error
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Determine if text should be light (white) or dark (black) based on background color
|
|
45
|
+
* Uses WCAG contrast guidelines to ensure readability
|
|
46
|
+
*/
|
|
47
|
+
function getContrastTextColor(backgroundColor, lightColor = '#FFFFFF', darkColor = '#000000', minContrast = 4.5) {
|
|
48
|
+
try {
|
|
49
|
+
const bgColor = (0, color_1.default)(backgroundColor);
|
|
50
|
+
const lightContrast = getContrastRatio(backgroundColor, lightColor);
|
|
51
|
+
const darkContrast = getContrastRatio(backgroundColor, darkColor);
|
|
52
|
+
// If both meet minimum contrast, choose the one with better contrast
|
|
53
|
+
if (lightContrast >= minContrast && darkContrast >= minContrast) {
|
|
54
|
+
return lightContrast > darkContrast ? lightColor : darkColor;
|
|
55
|
+
}
|
|
56
|
+
// If only one meets minimum contrast, use that one
|
|
57
|
+
if (lightContrast >= minContrast) {
|
|
58
|
+
return lightColor;
|
|
59
|
+
}
|
|
60
|
+
if (darkContrast >= minContrast) {
|
|
61
|
+
return darkColor;
|
|
62
|
+
}
|
|
63
|
+
// If neither meets minimum, choose the one with better contrast anyway
|
|
64
|
+
return lightContrast > darkContrast ? lightColor : darkColor;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.warn('Failed to determine contrast text color:', error);
|
|
68
|
+
// Fallback: use light text for dark backgrounds, dark text for light backgrounds
|
|
69
|
+
try {
|
|
70
|
+
const bgColor = (0, color_1.default)(backgroundColor);
|
|
71
|
+
const luminance = getLuminance(bgColor);
|
|
72
|
+
return luminance > 0.5 ? darkColor : lightColor;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return lightColor; // Ultimate fallback
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if a color combination meets WCAG contrast requirements
|
|
81
|
+
* @param foreground - Text color
|
|
82
|
+
* @param background - Background color
|
|
83
|
+
* @param level - 'AA' (4.5:1) or 'AAA' (7:1) for normal text, or 'AALarge' (3:1) for large text
|
|
84
|
+
*/
|
|
85
|
+
function meetsContrastRequirement(foreground, background, level = 'AA') {
|
|
86
|
+
const ratio = getContrastRatio(foreground, background);
|
|
87
|
+
const requirements = {
|
|
88
|
+
AA: 4.5,
|
|
89
|
+
AAA: 7,
|
|
90
|
+
AALarge: 3,
|
|
91
|
+
};
|
|
92
|
+
return ratio >= requirements[level];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get a color that meets contrast requirements with a background
|
|
96
|
+
* Tries to find a readable version of the foreground color
|
|
97
|
+
*/
|
|
98
|
+
function ensureContrast(foreground, background, level = 'AA') {
|
|
99
|
+
if (meetsContrastRequirement(foreground, background, level)) {
|
|
100
|
+
return foreground;
|
|
101
|
+
}
|
|
102
|
+
// If contrast is insufficient, return the opposite contrast color
|
|
103
|
+
return getContrastTextColor(background);
|
|
104
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to get theme colors from the current theme
|
|
3
|
+
* Uses the settings context to get the active theme
|
|
4
|
+
*/
|
|
5
|
+
import { ThemeColors } from '../theme';
|
|
6
|
+
export declare function useThemeColor(props?: {
|
|
7
|
+
light?: string;
|
|
8
|
+
dark?: string;
|
|
9
|
+
[key: string]: string | undefined;
|
|
10
|
+
}, colorName?: keyof ThemeColors): string | ThemeColors;
|
|
11
|
+
/**
|
|
12
|
+
* Hook to get all colors from the current theme
|
|
13
|
+
*/
|
|
14
|
+
export declare function useTheme(): ThemeColors;
|
|
15
|
+
//# sourceMappingURL=use-theme-color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-theme-color.d.ts","sourceRoot":"","sources":["../../src/hooks/use-theme-color.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,WAAW,EAAE,MAAM,UAAU,CAAC;AAG/C,wBAAgB,aAAa,CAC3B,KAAK,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC5E,SAAS,CAAC,EAAE,MAAM,WAAW,wBAoB9B;AAED;;GAEG;AACH,wBAAgB,QAAQ,gBAGvB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to get theme colors from the current theme
|
|
4
|
+
* Uses the settings context to get the active theme
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.useThemeColor = useThemeColor;
|
|
8
|
+
exports.useTheme = useTheme;
|
|
9
|
+
const theme_1 = require("../theme");
|
|
10
|
+
const settings_context_1 = require("../settings-context");
|
|
11
|
+
function useThemeColor(props, colorName) {
|
|
12
|
+
const { currentTheme } = (0, settings_context_1.useSettings)();
|
|
13
|
+
const themeColors = theme_1.Themes[currentTheme];
|
|
14
|
+
// If props are provided, check for theme-specific override
|
|
15
|
+
if (props && colorName) {
|
|
16
|
+
const colorFromProps = props[currentTheme] || props[colorName];
|
|
17
|
+
if (colorFromProps) {
|
|
18
|
+
return colorFromProps;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Return the color from the current theme
|
|
22
|
+
if (colorName) {
|
|
23
|
+
return themeColors[colorName];
|
|
24
|
+
}
|
|
25
|
+
// If no color name provided, return the theme colors object
|
|
26
|
+
return themeColors;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hook to get all colors from the current theme
|
|
30
|
+
*/
|
|
31
|
+
function useTheme() {
|
|
32
|
+
const { currentTheme } = (0, settings_context_1.useSettings)();
|
|
33
|
+
return theme_1.Themes[currentTheme];
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for translations with type safety
|
|
3
|
+
* Usage: const t = useTranslation(); t('common.welcome')
|
|
4
|
+
*/
|
|
5
|
+
export declare function useTranslation(): {
|
|
6
|
+
t: (key: string, options?: Record<string, string | number>) => string;
|
|
7
|
+
changeLanguage: (language: string) => Promise<import("i18next").TFunction<"translation", undefined>>;
|
|
8
|
+
currentLanguage: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=use-translation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-translation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-translation.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,cAAc;aAIjB,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;+BAE/B,MAAM;;EAGpC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTranslation = useTranslation;
|
|
4
|
+
const react_i18next_1 = require("react-i18next");
|
|
5
|
+
/**
|
|
6
|
+
* Custom hook for translations with type safety
|
|
7
|
+
* Usage: const t = useTranslation(); t('common.welcome')
|
|
8
|
+
*/
|
|
9
|
+
function useTranslation() {
|
|
10
|
+
const { t, i18n } = (0, react_i18next_1.useTranslation)();
|
|
11
|
+
return {
|
|
12
|
+
t: (key, options) => t(key, options),
|
|
13
|
+
changeLanguage: (language) => i18n.changeLanguage(language),
|
|
14
|
+
currentLanguage: i18n.language,
|
|
15
|
+
};
|
|
16
|
+
}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import i18n from 'i18next';
|
|
2
|
+
export declare const SUPPORTED_LANGUAGES: readonly [{
|
|
3
|
+
readonly code: "en-US";
|
|
4
|
+
readonly name: "English (US)";
|
|
5
|
+
}, {
|
|
6
|
+
readonly code: "es-ES";
|
|
7
|
+
readonly name: "Español (ES)";
|
|
8
|
+
}, {
|
|
9
|
+
readonly code: "fr-FR";
|
|
10
|
+
readonly name: "Français (FR)";
|
|
11
|
+
}, {
|
|
12
|
+
readonly code: "de-DE";
|
|
13
|
+
readonly name: "Deutsch (DE)";
|
|
14
|
+
}, {
|
|
15
|
+
readonly code: "ja-JP";
|
|
16
|
+
readonly name: "日本語 (JP)";
|
|
17
|
+
}, {
|
|
18
|
+
readonly code: "zh-CN";
|
|
19
|
+
readonly name: "中文 (简体)";
|
|
20
|
+
}];
|
|
21
|
+
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]['code'];
|
|
22
|
+
export default i18n;
|
|
23
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,SAAS,CAAC;AAK3B,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;EAOtB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AA+D7E,eAAe,IAAI,CAAC"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SUPPORTED_LANGUAGES = void 0;
|
|
7
|
+
const i18next_1 = __importDefault(require("i18next"));
|
|
8
|
+
const react_i18next_1 = require("react-i18next");
|
|
9
|
+
const expo_localization_1 = require("expo-localization");
|
|
10
|
+
// Supported languages
|
|
11
|
+
exports.SUPPORTED_LANGUAGES = [
|
|
12
|
+
{ code: 'en-US', name: 'English (US)' },
|
|
13
|
+
{ code: 'es-ES', name: 'Español (ES)' },
|
|
14
|
+
{ code: 'fr-FR', name: 'Français (FR)' },
|
|
15
|
+
{ code: 'de-DE', name: 'Deutsch (DE)' },
|
|
16
|
+
{ code: 'ja-JP', name: '日本語 (JP)' },
|
|
17
|
+
{ code: 'zh-CN', name: '中文 (简体)' },
|
|
18
|
+
];
|
|
19
|
+
// Default translations - can be extended by apps
|
|
20
|
+
const defaultTranslations = {
|
|
21
|
+
'en-US': {
|
|
22
|
+
translation: {
|
|
23
|
+
common: {
|
|
24
|
+
welcome: 'Welcome',
|
|
25
|
+
hello: 'Hello',
|
|
26
|
+
save: 'Save',
|
|
27
|
+
cancel: 'Cancel',
|
|
28
|
+
delete: 'Delete',
|
|
29
|
+
edit: 'Edit',
|
|
30
|
+
done: 'Done',
|
|
31
|
+
loading: 'Loading...',
|
|
32
|
+
error: 'Error',
|
|
33
|
+
success: 'Success',
|
|
34
|
+
},
|
|
35
|
+
settings: {
|
|
36
|
+
title: 'Settings',
|
|
37
|
+
theme: 'Theme',
|
|
38
|
+
language: 'Language',
|
|
39
|
+
selectTheme: 'Select Theme',
|
|
40
|
+
selectLanguage: 'Select Language',
|
|
41
|
+
light: 'Light',
|
|
42
|
+
dark: 'Dark',
|
|
43
|
+
system: 'System',
|
|
44
|
+
themeMode: 'Theme Mode',
|
|
45
|
+
},
|
|
46
|
+
home: {
|
|
47
|
+
title: 'Welcome!',
|
|
48
|
+
step1: 'Step 1: Try it',
|
|
49
|
+
step2: 'Step 2: Explore',
|
|
50
|
+
step3: 'Step 3: Get a fresh start',
|
|
51
|
+
editFile: 'Edit {file} to see changes.',
|
|
52
|
+
pressKey: 'Press {key} to open developer tools.',
|
|
53
|
+
tapExplore: "Tap the Explore tab to learn more about what's included in this starter app.",
|
|
54
|
+
resetProject: "When you're ready, run {command} to get a fresh {directory} directory.",
|
|
55
|
+
},
|
|
56
|
+
errors: {
|
|
57
|
+
somethingWentWrong: 'Something went wrong',
|
|
58
|
+
tryAgain: 'Try again',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
// Initialize i18n
|
|
64
|
+
// Apps can extend translations by calling i18n.addResourceBundle
|
|
65
|
+
i18next_1.default.use(react_i18next_1.initReactI18next).init({
|
|
66
|
+
compatibilityJSON: 'v3',
|
|
67
|
+
resources: defaultTranslations,
|
|
68
|
+
lng: (0, expo_localization_1.getLocales)()[0]?.languageTag || 'en-US',
|
|
69
|
+
fallbackLng: 'en-US',
|
|
70
|
+
interpolation: {
|
|
71
|
+
escapeValue: false, // React already escapes values
|
|
72
|
+
prefix: '{',
|
|
73
|
+
suffix: '}',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
exports.default = i18next_1.default;
|