@chipmobilesdk/rn-theme 0.2.2 → 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/CHANGELOG.md +91 -23
- package/NOTICE +9 -0
- package/README.md +277 -187
- package/package.json +49 -50
- package/skills/sdk-rn-theme/SKILL.md +45 -48
- package/src/color/categorical.ts +33 -0
- package/src/color/contrast.ts +230 -0
- package/src/color/parse.ts +120 -0
- package/src/color/roles.ts +262 -0
- package/src/index.ts +23 -17
- package/src/integration/navigationTheme.ts +51 -0
- package/src/presets/byTone.ts +28 -0
- package/src/presets/layout.ts +38 -0
- package/src/presets/themePresets.ts +101 -0
- package/src/theme/buildThemes.ts +178 -0
- package/src/theme/errors.ts +34 -0
- package/src/theme/overrides.ts +142 -0
- package/src/theme/validateThemeConfig.ts +171 -0
- package/src/tokens/defaults.ts +103 -46
- package/src/tokens/typography.ts +144 -64
- package/src/types/brand.ts +16 -0
- package/src/types/colors.ts +80 -11
- package/src/types/index.ts +44 -5
- package/src/types/theme.ts +161 -0
- package/src/types/tokens.ts +47 -5
- package/src/types/typography.ts +46 -10
- package/src/unistyles/createUnistylesConfig.ts +165 -177
- package/src/unistyles/themeMode.ts +28 -0
- package/src/vendor/mcu/LICENSE +191 -0
- package/src/vendor/mcu/hct/cam16.ts +419 -0
- package/src/vendor/mcu/hct/hct.ts +216 -0
- package/src/vendor/mcu/hct/hct_solver.ts +557 -0
- package/src/vendor/mcu/hct/viewing_conditions.ts +121 -0
- package/src/vendor/mcu/index.ts +5 -0
- package/src/vendor/mcu/palettes/tonal_palette.ts +172 -0
- package/src/vendor/mcu/utils/color_utils.ts +302 -0
- package/src/vendor/mcu/utils/math_utils.ts +148 -0
- package/src/types/style.ts +0 -26
- package/src/unistyles/types.ts +0 -24
- package/src/unistyles/validateThemeConfig.ts +0 -154
- package/src/utils/colorGeneration.ts +0 -87
- package/src/utils/colorMath.ts +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,24 +1,92 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 - 2026-10-17
|
|
4
|
+
|
|
5
|
+
Major release: accessible colour roles for any brand, a complete token set, type-safe
|
|
6
|
+
extension, and style presets. See `specs/014-rn-theme-v1/spec.md` in the source repository.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- HCT tonal colour engine (vendored material-color-utilities v0.4.0, Apache-2.0): every role
|
|
11
|
+
is a fixed tone, so documented contrast pairs hold for any brand input.
|
|
12
|
+
- Full Material 3 role set: `onXContainer`, `onSurfaceVariant`, surface tiers, `inverse*`,
|
|
13
|
+
`outline`, `outlineVariant`, `scrim`, semantic container roles.
|
|
14
|
+
- `seeds` (neutral and semantic hues), custom `tones`, `tone.*` bundles and `byTone`.
|
|
15
|
+
- `extend` for app tokens, `themes` for extra named themes, validated `overrides`, `strict`.
|
|
16
|
+
- `buildThemes`, `auditTheme`, `contrastRatio`, `withAlpha`, `applyThemeMode`,
|
|
17
|
+
`toNavigationTheme`, `ThemeConfigError`, `ThemeContrastError`, `DEFAULT_BREAKPOINTS`.
|
|
18
|
+
- Tokens: typography Small/Large sizes, `font`/`fontWeight` with per-platform font files,
|
|
19
|
+
spacing `none`/`xxs`/`smPlus`/`mdPlus`/`xlPlus`/`xxxl`, radius `none`/`xs`, `borderWidth`,
|
|
20
|
+
`size`, `opacity`, `motion`, `maxFontScale`, `categorical`; default breakpoints.
|
|
21
|
+
- Presets: static `layout` and `presets`; theme `text`, `stack`, `inline`, `border`, `box`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Brand colours keep hue and chroma but take fixed role tones; the supplied hex is no
|
|
26
|
+
longer used as-is. Neutral surfaces carry a faint primary tint.
|
|
27
|
+
- `error`/`success`/`warning`/`info` are now readable as text (≥ 4.5:1) in both themes.
|
|
28
|
+
- Shadows are `boxShadow` strings (cross-platform) instead of iOS props + `elevation`.
|
|
29
|
+
- The package no longer augments `react-native-unistyles`; the app augments once from
|
|
30
|
+
`typeof config.themes`.
|
|
31
|
+
- `createUnistylesConfig` returns `breakpoints` and never invokes an `initialTheme` resolver.
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
|
|
35
|
+
- `generateLightPalette`, `generateDarkPalette`, `UnistylesThemeFromBrand`, `ThemeColors`,
|
|
36
|
+
`StyleTheme`, `StyleConfig`, the `tinycolor2` dependency, named and `hsl()` colour inputs.
|
|
37
|
+
|
|
38
|
+
### Migration (0.2.x → 1.0.0)
|
|
39
|
+
|
|
40
|
+
| 0.2.x | 1.0.0 |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `createUnistylesConfig({ brandColors, typographyConfig })` | `createUnistylesConfig({ brandColors, typography })` |
|
|
43
|
+
| `StyleConfig` / `StyleTheme` / `ThemeColors` | `ThemeConfig` / `Theme` / `ColorRoles` |
|
|
44
|
+
| `UnistylesThemeFromBrand` in `declare module` | `interface UnistylesThemes extends AppThemes {}` with `type AppThemes = typeof config.themes` |
|
|
45
|
+
| `generateLightPalette(brand)` / `generateDarkPalette(brand)` | `buildThemes({ brandColors }).light.color` / `.dark.color` |
|
|
46
|
+
| `color.border` | `color.outline` (interactive boundaries, ≥ 3:1) or `color.outlineVariant` (dividers) |
|
|
47
|
+
| `color.disabled` used as muted text | `color.onSurfaceVariant` or `text.bodyMuted` / `text.labelMuted` |
|
|
48
|
+
| `color.disabled` for inactive UI | unchanged meaning; now `onSurface` at 38 % opacity, contrast-exempt |
|
|
49
|
+
| `color.error`/`success`/`warning`/`info` as fills | `color.errorContainer` … with `onErrorContainer` …, or `tone.*.fill` / `onFill` |
|
|
50
|
+
| `color.primary` equal to the brand hex | tone-mapped; override `color.primary` to pin a hex |
|
|
51
|
+
| `shadow.md.shadowColor` … `elevation` | `shadow.md.boxShadow` |
|
|
52
|
+
| App tokens spread into the theme + cast | `extend: ({ theme }) => ({ ... })` |
|
|
53
|
+
| `overrides` adding unknown keys | rejected — use `extend` |
|
|
54
|
+
| Colour strings like `teal`, `hsl(...)` | `#RRGGBB`, `#RRGGBBAA`, `rgb()`, `rgba()` |
|
|
55
|
+
| Typography roles | unchanged sizes (medium); `<role>Small` / `<role>Large` added |
|
|
56
|
+
| Spacing / radius keys | unchanged values; new keys added |
|
|
57
|
+
|
|
58
|
+
## 0.2.2 - 2026-08-02
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- README and agent skill: Unistyles 3 style authoring and TypeScript diagnostics guidance.
|
|
63
|
+
No runtime change.
|
|
64
|
+
|
|
65
|
+
## 0.2.1 - 2026-08-01
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- The agent skill (`skills/`) ships in the package. No runtime change.
|
|
70
|
+
|
|
71
|
+
## 0.2.0 - 2026-06-06
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- Documented single-entrypoint public API for reusable theme generation and token types.
|
|
76
|
+
- Added consumer setup guidance for host dependencies, setup order, Android/iOS expectations, and theme-start strategy.
|
|
77
|
+
- Added package validation coverage for theme generation, config validation, public API imports, and package manifest readiness.
|
|
10
78
|
- Added package publish-readiness metadata and dry-run validation expectations.
|
|
11
|
-
|
|
12
|
-
### Changed
|
|
13
|
-
|
|
14
|
-
- Classified host integrations as peer dependencies and kept runtime color generation in package dependencies.
|
|
15
|
-
- Kept app-specific brand colors and typography outside the package.
|
|
16
|
-
|
|
17
|
-
### Migration
|
|
18
|
-
|
|
19
|
-
- This is a backward-compatible minor release for existing workspace consumers importing from `@chipmobilesdk/rn-theme`.
|
|
20
|
-
- Consumers importing private subpaths must migrate to the package root entrypoint.
|
|
21
|
-
|
|
22
|
-
### Deprecations
|
|
23
|
-
|
|
24
|
-
- Private subpath imports are unsupported and should not be used by consuming apps.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- Classified host integrations as peer dependencies and kept runtime color generation in package dependencies.
|
|
83
|
+
- Kept app-specific brand colors and typography outside the package.
|
|
84
|
+
|
|
85
|
+
### Migration
|
|
86
|
+
|
|
87
|
+
- This is a backward-compatible minor release for existing workspace consumers importing from `@chipmobilesdk/rn-theme`.
|
|
88
|
+
- Consumers importing private subpaths must migrate to the package root entrypoint.
|
|
89
|
+
|
|
90
|
+
### Deprecations
|
|
91
|
+
|
|
92
|
+
- Private subpath imports are unsupported and should not be used by consuming apps.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@chipmobilesdk/rn-theme
|
|
2
|
+
Copyright ChipMobileSdk.
|
|
3
|
+
|
|
4
|
+
This package includes software from material-color-utilities
|
|
5
|
+
(https://github.com/material-foundation/material-color-utilities), v0.4.0,
|
|
6
|
+
commit dd62982dfdefdfc9232ce8d76e122fb1c6d188ae, Copyright 2021-2023 Google LLC,
|
|
7
|
+
licensed under the Apache License, Version 2.0. The vendored files live in
|
|
8
|
+
src/vendor/mcu/ together with a copy of that license (src/vendor/mcu/LICENSE);
|
|
9
|
+
each file states the modification made to it.
|
package/README.md
CHANGED
|
@@ -1,212 +1,302 @@
|
|
|
1
|
-
# @chipmobilesdk/rn-theme
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @chipmobilesdk/rn-theme
|
|
2
|
+
|
|
3
|
+
Accessible theme generation, design tokens and style presets for ChipMobileSdk-family React
|
|
4
|
+
Native apps, configured for `react-native-unistyles` 3.
|
|
4
5
|
|
|
5
6
|
> **License:** `UNLICENSED`. Published publicly for use by the owner's
|
|
6
|
-
> applications; no open-source license is granted.
|
|
7
|
+
> applications; no open-source license is granted. Includes Apache-2.0 code from
|
|
8
|
+
> material-color-utilities — see `NOTICE`.
|
|
7
9
|
|
|
8
|
-
- **Version**: `0.
|
|
10
|
+
- **Version**: `1.0.0`
|
|
9
11
|
- **Entrypoint**: `@chipmobilesdk/rn-theme`
|
|
10
12
|
- **Demo**: `src/screens/ExampleScreen.tsx`
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
),
|
|
75
|
-
);
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Import the setup module first in the app entrypoint.
|
|
79
|
-
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @chipmobilesdk/rn-theme react-native-unistyles
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The host app owns `react`, `react-native` and `react-native-unistyles`. The package has no
|
|
21
|
+
runtime dependency, no native code, and renders no components.
|
|
22
|
+
|
|
23
|
+
## Compatibility
|
|
24
|
+
|
|
25
|
+
Validated in the ChipMobileSdk demo against React `19.2.3`, React Native `0.85.3`,
|
|
26
|
+
React Native Unistyles `^3.2.5`, TypeScript `^5.8.3` and Node.js `>=22.11.0`, on Android and
|
|
27
|
+
iOS. Shadows use React Native's cross-platform `boxShadow` (New Architecture).
|
|
28
|
+
|
|
29
|
+
## Setup Order
|
|
30
|
+
|
|
31
|
+
App-owned brand values (`src/theme/brand.ts`):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import type { BrandColors, TypographyConfig } from '@chipmobilesdk/rn-theme';
|
|
35
|
+
|
|
36
|
+
export const BRAND_COLORS: BrandColors = {
|
|
37
|
+
primary: '#046b63',
|
|
38
|
+
secondary: '#625b71',
|
|
39
|
+
tertiary: '#7d5260',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const BRAND_TYPOGRAPHY: TypographyConfig = { fontFamily: 'System' };
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Configure Unistyles before any themed component renders, and teach Unistyles the theme
|
|
46
|
+
shape **once**, from the config itself:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
50
|
+
import { createUnistylesConfig } from '@chipmobilesdk/rn-theme';
|
|
51
|
+
import { BRAND_COLORS, BRAND_TYPOGRAPHY } from './brand';
|
|
52
|
+
|
|
53
|
+
const unistylesConfig = createUnistylesConfig(
|
|
54
|
+
{ brandColors: BRAND_COLORS, typography: BRAND_TYPOGRAPHY },
|
|
55
|
+
{ initialTheme: 'light' },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
type AppThemes = typeof unistylesConfig.themes;
|
|
59
|
+
type AppBreakpoints = typeof unistylesConfig.breakpoints;
|
|
60
|
+
|
|
61
|
+
declare module 'react-native-unistyles' {
|
|
62
|
+
export interface UnistylesThemes extends AppThemes {}
|
|
63
|
+
export interface UnistylesBreakpoints extends AppBreakpoints {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
StyleSheet.configure(unistylesConfig);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Import that module first in the app entrypoint. Pass the config **inline** (or with
|
|
70
|
+
`satisfies ThemeConfig`): annotating a variable as `ThemeConfig` erases the extension, tone
|
|
71
|
+
and theme-name types the config would otherwise carry.
|
|
72
|
+
|
|
73
|
+
The package does not augment `react-native-unistyles` itself — your augmentation is the
|
|
74
|
+
only one, so an extended theme shape never conflicts.
|
|
75
|
+
|
|
80
76
|
## Theme Start Strategy
|
|
81
|
-
|
|
82
|
-
Choose exactly one startup strategy.
|
|
83
|
-
|
|
84
|
-
Manual initial theme:
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
createUnistylesConfig(config, { initialTheme: 'light' });
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
System adaptive themes:
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
createUnistylesConfig(config, { adaptiveThemes: true });
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Using `initialTheme` and `adaptiveThemes: true` together is invalid. Persisting a user theme preference, if needed, belongs in the consuming app.
|
|
97
77
|
|
|
98
|
-
|
|
78
|
+
Choose one: `{ initialTheme: 'light' | 'dark' | <named theme> }` or
|
|
79
|
+
`{ adaptiveThemes: true }`. Both together are rejected. A resolver function for
|
|
80
|
+
`initialTheme` is passed to Unistyles untouched — the package never calls it.
|
|
99
81
|
|
|
100
|
-
|
|
82
|
+
To apply a stored preference at runtime:
|
|
101
83
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Unistyles state:
|
|
84
|
+
```ts
|
|
85
|
+
import { applyThemeMode } from '@chipmobilesdk/rn-theme';
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
applyThemeMode('system'); // follow the OS
|
|
88
|
+
applyThemeMode('dark'); // fixed; survives later OS scheme changes
|
|
108
89
|
```
|
|
109
90
|
|
|
110
|
-
|
|
111
|
-
|
|
91
|
+
`applyThemeMode` switches adaptive themes off before setting a manual theme — in the other
|
|
92
|
+
order the next OS change overrides the user's choice. Persisting the preference is the app's.
|
|
112
93
|
|
|
113
|
-
|
|
114
|
-
`selected`, `disabled`, or `size` changes one or more style entries. Variants model
|
|
115
|
-
semantic component states; they are not a replacement for every style array.
|
|
94
|
+
## Colour System
|
|
116
95
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
stylesheet to an object/function union. Fix that inference instead of casting the array.
|
|
96
|
+
Every role is a fixed tone of an HCT tonal palette built from a brand or seed colour, so
|
|
97
|
+
contrast depends only on tone and holds for **any** brand:
|
|
120
98
|
|
|
121
|
-
|
|
99
|
+
| Guarantee | Pairs |
|
|
100
|
+
|---|---|
|
|
101
|
+
| ≥ 4.5:1 (text) | `error`/`success`/`warning`/`info` and `onSurfaceVariant` on `background` and `surface`; every `onX` on `X`; `onXContainer` on `XContainer`; `onSurface` on each surface tier; tone `fg`, `onContainer`, `onFill`; every text preset except `text.disabled` |
|
|
102
|
+
| ≥ 3:1 (non-text) | `outline` on `background`/`surface`; tone `border` on `background`; each `categorical` colour on `surface` |
|
|
103
|
+
|
|
104
|
+
- Brand colours keep their hue and chroma but always take the role tone (light 40 / dark 80;
|
|
105
|
+
containers 90 / 30). The supplied hex is not used as-is — override a role if a screen
|
|
106
|
+
needs the exact brand hex, and the contrast check below still applies.
|
|
107
|
+
- Neutrals carry a faint tint of the primary hue unless `seeds.neutral` is given. Semantic
|
|
108
|
+
hues come from `seeds.error|success|warning|info` (defaults: the 0.2.x light values).
|
|
109
|
+
- `disabled` is a **state** colour, exempt from contrast (WCAG 1.4.3). Muted readable text
|
|
110
|
+
is `onSurfaceVariant` or the `text.*Muted` presets.
|
|
111
|
+
- `outline` bounds interactive components; `outlineVariant` is for dividers.
|
|
112
|
+
- `auditTheme(theme)` lists every guaranteed pair below target. After overrides,
|
|
113
|
+
`buildThemes` warns in development; `strict: true` throws `ThemeContrastError` instead.
|
|
114
|
+
|
|
115
|
+
Accepted colours: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`, `rgb()`, `rgba()`. Brand and seed
|
|
116
|
+
colours must be opaque. `withAlpha(color, opacity)` applies an opacity token.
|
|
117
|
+
|
|
118
|
+
## Tokens
|
|
119
|
+
|
|
120
|
+
| Group | Contents |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `color` | Material 3 roles (above) plus `scrim`, `shadow`, `disabled` |
|
|
123
|
+
| `tone` | `neutral`, `primary`, `info`, `success`, `warning`, `error` and custom tones — each `{ fg, container, onContainer, border, fill, onFill }` |
|
|
124
|
+
| `typography` | `display`, `headline`, `title`, `body`, `label`, `caption` (medium) plus `<role>Small` / `<role>Large` |
|
|
125
|
+
| `font`, `fontWeight` | `regular`, `medium`, `semibold`, `bold`, `heavy` — `font.X` is `{ fontFamily, fontWeight }` resolved for the platform |
|
|
126
|
+
| `spacing` | `none` 0, `xxs` 2, `xs` 4, `sm` 8, `smPlus` 12, `md` 16, `mdPlus` 20, `lg` 24, `xl` 32, `xlPlus` 40, `xxl` 48, `xxxl` 64 |
|
|
127
|
+
| `radius` | `none`, `xs`, `sm`, `md`, `lg`, `xl`, `full` |
|
|
128
|
+
| `borderWidth` | `hairline`, `thin` 1, `thick` 2 |
|
|
129
|
+
| `size` | `touchTarget` 44, `icon.sm/md/lg` 16/20/24 |
|
|
130
|
+
| `opacity` | `hover`, `focus`, `pressed`, `dragged`, `disabledContent`, `disabledContainer` |
|
|
131
|
+
| `motion` | `duration.short/medium/long`, `easing.standard/emphasized/decelerate/accelerate` (`[x1, y1, x2, y2]`) |
|
|
132
|
+
| `shadow` | `sm`, `md`, `lg` — `{ boxShadow }` |
|
|
133
|
+
| `zIndex`, `maxFontScale`, `categorical` | layers; OS text-scale ceiling (default 2); chart series colours |
|
|
134
|
+
|
|
135
|
+
Breakpoints default to `{ compact: 0, medium: 600, expanded: 840 }`; replace them with the
|
|
136
|
+
`breakpoints` option.
|
|
137
|
+
|
|
138
|
+
### Custom fonts
|
|
139
|
+
|
|
140
|
+
Android cannot pick a weight inside a family, so give each weight its file there:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
typography: {
|
|
144
|
+
fonts: {
|
|
145
|
+
family: 'Inter',
|
|
146
|
+
android: { regular: 'Inter-Regular', medium: 'Inter-Medium', semibold: 'Inter-SemiBold', bold: 'Inter-Bold' },
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Use `...theme.font.bold` (not a literal `fontWeight`) to change weight in a style. A missing
|
|
152
|
+
Android weight falls back to the nearest one with a development warning. On iOS a partial
|
|
153
|
+
map only renames the weights it lists.
|
|
154
|
+
|
|
155
|
+
### Font scale
|
|
156
|
+
|
|
157
|
+
`fontScale` is a design-time multiplier and does not track the OS text size. Cap the OS
|
|
158
|
+
multiplier on your shared `Text` with `maxFontSizeMultiplier={theme.maxFontScale}`.
|
|
122
159
|
|
|
123
|
-
|
|
124
|
-
stylesheet. Call a dynamic entry before passing it to a `style` prop, and pass only
|
|
125
|
-
serializable arguments:
|
|
160
|
+
## Style Presets
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
|
|
162
|
+
Presets are plain style data, so stylesheets keep only what is specific to them.
|
|
163
|
+
|
|
164
|
+
- **Static** (`layout`, `presets`) — no theme dependency, so Unistyles never recalculates them:
|
|
165
|
+
`layout.row`, `column`, `rowCenter`, `rowBetween`, `center`, `fill`, `wrap`, `absoluteFill`,
|
|
166
|
+
`selfStart`, `selfCenter`, `selfEnd`; `presets.glyphAlign` (Android icon-glyph centring),
|
|
167
|
+
`presets.tabularNums`.
|
|
168
|
+
- **Theme** — read tokens and follow overrides: `theme.text.*` (typography + AA colour),
|
|
169
|
+
`theme.stack.<spacing>` / `theme.inline.<spacing>`, `theme.border.hairline|top|bottom|all`,
|
|
170
|
+
`theme.box.outlined|card|sheet|touchTarget`.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const styles = StyleSheet.create(theme => ({
|
|
174
|
+
row: { ...layout.rowCenter, ...theme.border.bottom, gap: theme.spacing.smPlus },
|
|
175
|
+
hint: theme.text.labelMuted,
|
|
176
|
+
container: {
|
|
177
|
+
...theme.box.outlined,
|
|
178
|
+
variants: { tone: byTone(theme, t => ({ borderColor: t.border, backgroundColor: t.container })) },
|
|
179
|
+
},
|
|
180
|
+
// One variant style for every text; combine it by array.
|
|
181
|
+
toneText: { variants: { tone: byTone(theme, t => ({ color: t.onContainer })) } },
|
|
182
|
+
title: theme.typography.body,
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
// <Text style={[styles.title, styles.toneText]} />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Rules the Unistyles Babel plugin imposes
|
|
189
|
+
|
|
190
|
+
The plugin decides **statically** which styles update on theme or variant changes. Verified
|
|
191
|
+
against Unistyles 3.2.5 and 3.3.0:
|
|
192
|
+
|
|
193
|
+
1. Read the theme through the stylesheet's own parameter. `const theme = cast(raw)` inside
|
|
194
|
+
the factory hides **every** dependency — those styles stop updating on theme change. Add
|
|
195
|
+
app tokens with `extend` instead of casting.
|
|
196
|
+
2. Keep a literal `variants` key in the style and call `byTone(theme, …)` inline under it. A
|
|
197
|
+
variants object built elsewhere, or spread in, loses the theme or variant dependency.
|
|
198
|
+
|
|
199
|
+
## Extending the Theme
|
|
200
|
+
|
|
201
|
+
Add app tokens with `extend`; they are typed everywhere without casts, overridable, and
|
|
202
|
+
validated (a value that is a colour must stay a colour):
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
createUnistylesConfig({
|
|
206
|
+
brandColors: BRAND_COLORS,
|
|
207
|
+
tones: { accent: '#BD260F' }, // full tone bundle, offered by byTone
|
|
208
|
+
extend: ({ theme, scheme }) => ({
|
|
209
|
+
appColor: { textMuted: theme.color.onSurfaceVariant, skeleton: theme.color.surfaceContainerHigh },
|
|
210
|
+
}),
|
|
211
|
+
themes: { brandB: { scheme: 'light', brandColors: BRAND_B } }, // extra named theme
|
|
212
|
+
overrides: {
|
|
213
|
+
light: { radius: { sm: 0, md: 0, lg: 0, xl: 0 }, appColor: { textMuted: '#706966' } },
|
|
214
|
+
},
|
|
215
|
+
});
|
|
129
216
|
```
|
|
130
217
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
218
|
+
Extensions may not reuse a base group name. Override keys must already exist — new tokens
|
|
219
|
+
belong in `extend`.
|
|
220
|
+
|
|
221
|
+
## Integrations
|
|
222
|
+
|
|
223
|
+
- `buildThemes(config)` — the same themes without Unistyles, e.g. for tests or other libraries.
|
|
224
|
+
- `toNavigationTheme(theme, scheme)` — a React Navigation theme (no navigation dependency).
|
|
225
|
+
|
|
226
|
+
## Public Runtime Exports
|
|
227
|
+
|
|
228
|
+
`buildThemes`, `createUnistylesConfig`, `applyThemeMode`, `auditTheme`, `contrastRatio`,
|
|
229
|
+
`withAlpha`, `byTone`, `layout`, `presets`, `toNavigationTheme`, `DEFAULT_BREAKPOINTS`,
|
|
230
|
+
`ThemeConfigError`, `ThemeContrastError`.
|
|
231
|
+
|
|
232
|
+
## Public Type Exports
|
|
233
|
+
|
|
234
|
+
Config: `ThemeConfig`, `BrandColors`, `SeedColors`, `TypographyConfig`, `FontConfig`,
|
|
235
|
+
`NamedThemeConfig`, `ExtendContext`, `UnistylesConfigOptions`, `BuildThemesOptions`.
|
|
236
|
+
Output: `Theme`, `BaseTheme`, `Themes`, `ThemeName`, `UnistylesConfig`, `ColorRoles`,
|
|
237
|
+
`ToneColors`, `Typography`, `TypographyToken`, `FontToken`, token and preset types
|
|
238
|
+
(`SpacingTokens`, `TextPresets`, `BoxPresets`, …), `ContrastFailure`, `NavigationTheme`,
|
|
239
|
+
`ThemeMode`, `ThemeModeRuntime`, `DeepPartial`.
|
|
240
|
+
|
|
241
|
+
## Private Modules
|
|
242
|
+
|
|
243
|
+
Only `@chipmobilesdk/rn-theme` is public. Deep imports (`.../src/...`) are unsupported.
|
|
244
|
+
|
|
245
|
+
## Configuration Validation
|
|
246
|
+
|
|
247
|
+
Invalid input throws `ThemeConfigError` whose `path` names the field, e.g.
|
|
248
|
+
`overrides.dark.color.error`. Checked: brand and seed colours (opaque, supported format),
|
|
249
|
+
tone colours, typography numbers and font maps, named themes, override keys (must exist),
|
|
250
|
+
override colours and numbers, Unistyles options and breakpoints.
|
|
251
|
+
|
|
252
|
+
## Dependency Classification
|
|
253
|
+
|
|
254
|
+
- `dependencies`: none. HCT colour maths is vendored under `src/vendor/mcu` (Apache-2.0).
|
|
255
|
+
- `peerDependencies`: `react`, `react-native`, `react-native-unistyles`.
|
|
256
|
+
|
|
257
|
+
## Unistyles 3 Style Authoring and TypeScript Diagnostics
|
|
258
|
+
|
|
259
|
+
### Style arrays and variants solve different problems
|
|
260
|
+
|
|
261
|
+
Merge independent styles with array syntax (`[styles.container, styles.emphasis]`) — the
|
|
262
|
+
array keeps merge order and each style's Unistyles state. Never object-spread Unistyles
|
|
263
|
+
entries. Use `styles.useVariants(...)` for finite component states such as `selected` or
|
|
264
|
+
`tone`.
|
|
265
|
+
|
|
266
|
+
### Dynamic functions and inference
|
|
267
|
+
|
|
268
|
+
Call a dynamic entry before passing it to `style`, with serializable arguments. If
|
|
269
|
+
TypeScript widens sibling entries to `UnistylesValues | ((...) => UnistylesValues)`, move the
|
|
270
|
+
dynamic entries into a separate `StyleSheet.create`. Every token and preset the package
|
|
271
|
+
exports is typed as React Native `TextStyle`/`ViewStyle` members, so spreading them never
|
|
272
|
+
degrades sibling inference.
|
|
136
273
|
|
|
137
274
|
### Localize vague TypeScript errors
|
|
138
275
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`StyleSheet.create` declaration above the component or extract the suspect entries into
|
|
142
|
-
a smaller stylesheet. This often makes inference fail at the actual initializer. It is
|
|
143
|
-
a debugging technique, not a required declaration order; the real runtime ordering rule
|
|
144
|
-
remains that `StyleSheet.configure` must run before importing themed components.
|
|
276
|
+
When an error is reported at every JSX use, move the stylesheet above the component or split
|
|
277
|
+
the suspect entries out to find the failing initializer.
|
|
145
278
|
|
|
146
|
-
See the
|
|
147
|
-
[
|
|
148
|
-
[variants](https://www.unistyl.es/v3/references/variants/), and
|
|
279
|
+
See the Unistyles 3 guides on [merging styles](https://www.unistyl.es/v3/guides/merging-styles/),
|
|
280
|
+
[variants](https://www.unistyl.es/v3/references/variants/) and
|
|
149
281
|
[dynamic functions](https://www.unistyl.es/v3/references/dynamic-functions/).
|
|
150
282
|
|
|
151
|
-
##
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
- `StyleTheme`
|
|
172
|
-
- `UnistylesThemes`
|
|
173
|
-
- `UnistylesConfigOptions`
|
|
174
|
-
- `UnistylesConfig`
|
|
175
|
-
- `UnistylesThemeFromBrand`
|
|
176
|
-
|
|
177
|
-
## Private Modules
|
|
178
|
-
|
|
179
|
-
Only `@chipmobilesdk/rn-theme` is public. Imports from `@chipmobilesdk/rn-theme/src/...`, `@chipmobilesdk/rn-theme/utils/...`, `@chipmobilesdk/rn-theme/tokens/...`, `@chipmobilesdk/rn-theme/types/...`, or `@chipmobilesdk/rn-theme/unistyles/...` are unsupported.
|
|
180
|
-
|
|
181
|
-
## Configuration Validation
|
|
182
|
-
|
|
183
|
-
The package validates required brand colors, positive typography values, valid color strings, and invalid theme-start combinations before returning a Unistyles config. Missing optional typography fields use documented defaults.
|
|
184
|
-
|
|
185
|
-
## Dependency Classification
|
|
186
|
-
|
|
187
|
-
- `dependencies`: runtime implementation packages imported by package code, currently `tinycolor2`.
|
|
188
|
-
- `peerDependencies`: host framework/integration packages supplied by each app: `react`, `react-native`, and `react-native-unistyles`.
|
|
189
|
-
- `devDependencies`: package-local type and validation tooling such as `@types/tinycolor2`.
|
|
190
|
-
- `react-native-safe-area-context`: app/demo dependency only, because the package does not import it.
|
|
191
|
-
|
|
192
|
-
## Release Validation
|
|
193
|
-
|
|
194
|
-
Run from the repository root:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
npm run lint
|
|
198
|
-
npm test
|
|
199
|
-
npm run typecheck
|
|
200
|
-
npm run typecheck:theme
|
|
201
|
-
npm run pack:theme
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
Record Android and iOS demo evidence under `specs/001-enhance-theme-package/validation/` before another app adopts the package.
|
|
205
|
-
|
|
206
|
-
## Semver and Migration
|
|
207
|
-
|
|
208
|
-
- Patch: internal fix with no public shape or behavior change.
|
|
209
|
-
- Minor: backward-compatible public additions or documentation/package metadata improvements.
|
|
210
|
-
- Major: removed export, renamed token, changed token shape, changed default semantics, or changed required consumer setup.
|
|
211
|
-
|
|
212
|
-
Breaking changes require migration notes and a deprecation decision before release.
|
|
283
|
+
## Migrating from 0.2.x
|
|
284
|
+
|
|
285
|
+
See the mapping table in `CHANGELOG.md` (`## 1.0.0`). In short: rename `typographyConfig` to
|
|
286
|
+
`typography`; replace `color.border` with `outline` (interactive) or `outlineVariant`
|
|
287
|
+
(dividers); use `onSurfaceVariant` for muted text instead of `disabled`; replace the package
|
|
288
|
+
augmentation (`UnistylesThemeFromBrand`) with the one in *Setup Order*; move app tokens from
|
|
289
|
+
spreads and casts into `extend`.
|
|
290
|
+
|
|
291
|
+
## Release Validation
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
npm run validate:theme
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Semver and Migration
|
|
298
|
+
|
|
299
|
+
- Patch: internal fix with no public shape or behaviour change.
|
|
300
|
+
- Minor: backward-compatible additions.
|
|
301
|
+
- Major: removed export, renamed token, changed token shape or default values, or changed
|
|
302
|
+
consumer setup — with a migration table in the changelog.
|