@chipmobilesdk/rn-theme 1.0.0 → 1.0.1
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 +10 -1
- package/README.md +2 -2
- package/package.json +2 -2
- package/skills/sdk-rn-theme/SKILL.md +1 -2
- package/src/color/contrast.ts +10 -7
- package/src/color/parse.ts +4 -5
- package/src/index.ts +1 -4
- package/src/theme/buildThemes.ts +10 -2
- package/src/theme/overrides.ts +6 -1
- package/src/theme/validateThemeConfig.ts +8 -2
- package/src/tokens/typography.ts +5 -2
- package/src/types/colors.ts +2 -14
- package/src/types/theme.ts +4 -2
- package/src/types/typography.ts +1 -3
- package/src/unistyles/createUnistylesConfig.ts +13 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @chipmobilesdk/rn-theme
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Documentation and packaging only; no code change. The README no longer writes a
|
|
8
|
+
version into its header (the changelog is the one place a version lives), the agent
|
|
9
|
+
skill no longer carries one (the consuming app's skill sync stamps the installed
|
|
10
|
+
version), and the changelog goes straight from its title to its releases. ([#15](https://github.com/operationd8979/chip-mobile-sdk/pull/15))
|
|
2
11
|
|
|
3
12
|
## 1.0.0 - 2026-10-17
|
|
4
13
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Native apps, configured for `react-native-unistyles` 3.
|
|
|
7
7
|
> applications; no open-source license is granted. Includes Apache-2.0 code from
|
|
8
8
|
> material-color-utilities — see `NOTICE`.
|
|
9
9
|
|
|
10
|
-
- **Version**:
|
|
10
|
+
- **Version**: see [CHANGELOG](CHANGELOG.md)
|
|
11
11
|
- **Entrypoint**: `@chipmobilesdk/rn-theme`
|
|
12
12
|
- **Demo**: `src/screens/ExampleScreen.tsx`
|
|
13
13
|
|
|
@@ -291,7 +291,7 @@ spreads and casts into `extend`.
|
|
|
291
291
|
## Release Validation
|
|
292
292
|
|
|
293
293
|
```bash
|
|
294
|
-
npm run validate
|
|
294
|
+
npm run validate
|
|
295
295
|
```
|
|
296
296
|
|
|
297
297
|
## Semver and Migration
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipmobilesdk/rn-theme",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"scripts": {
|
|
32
32
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
-
"pack:
|
|
33
|
+
"pack:check": "npm pack --dry-run"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=19",
|
|
@@ -3,7 +3,6 @@ name: sdk-rn-theme
|
|
|
3
3
|
description: "Theming for @chipmobilesdk/rn-theme: accessible brand palettes, Unistyles config, colour/typography/spacing tokens, tones and byTone variants, layout and text presets, app token extension, dark mode and theme mode. Use when setting up theming, writing or shortening a stylesheet, adding a design token, wiring light/dark, or debugging Unistyles setup, stale styles or type errors."
|
|
4
4
|
metadata:
|
|
5
5
|
package: "@chipmobilesdk/rn-theme"
|
|
6
|
-
packageVersion: "1.0.0"
|
|
7
6
|
user-invocable: true
|
|
8
7
|
disable-model-invocation: false
|
|
9
8
|
---
|
|
@@ -46,7 +45,7 @@ Unistyles 3. Single source of truth for visual tokens; app tokens join through `
|
|
|
46
45
|
package) augments `UnistylesThemes` / `UnistylesBreakpoints` from `typeof config`.
|
|
47
46
|
- Merge Unistyles styles with arrays, never object spread of Unistyles entries.
|
|
48
47
|
|
|
49
|
-
## Public API
|
|
48
|
+
## Public API
|
|
50
49
|
|
|
51
50
|
Runtime: `createUnistylesConfig`, `buildThemes`, `applyThemeMode`, `byTone`, `layout`,
|
|
52
51
|
`presets`, `auditTheme`, `contrastRatio`, `withAlpha`, `toNavigationTheme`,
|
package/src/color/contrast.ts
CHANGED
|
@@ -107,7 +107,11 @@ function guaranteedPairs(theme: object): ContrastPair[] {
|
|
|
107
107
|
const tone = `tone.${name}`;
|
|
108
108
|
pairs.push(
|
|
109
109
|
...onBase(`${tone}.fg`),
|
|
110
|
-
{
|
|
110
|
+
{
|
|
111
|
+
foreground: `${tone}.onContainer`,
|
|
112
|
+
background: `${tone}.container`,
|
|
113
|
+
target: TEXT_CONTRAST,
|
|
114
|
+
},
|
|
111
115
|
{ foreground: `${tone}.onFill`, background: `${tone}.fill`, target: TEXT_CONTRAST },
|
|
112
116
|
{ foreground: `${tone}.border`, background: 'color.background', target: NON_TEXT_CONTRAST },
|
|
113
117
|
);
|
|
@@ -161,11 +165,7 @@ function channelLuminance(channel: number): number {
|
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
function relativeLuminance({ r, g, b }: Rgba): number {
|
|
164
|
-
return (
|
|
165
|
-
0.2126 * channelLuminance(r) +
|
|
166
|
-
0.7152 * channelLuminance(g) +
|
|
167
|
-
0.0722 * channelLuminance(b)
|
|
168
|
-
);
|
|
168
|
+
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
function composite(top: Rgba, bottom: Rgba): Rgba {
|
|
@@ -190,7 +190,10 @@ export function contrastRatio(foreground: string, background: string, base?: str
|
|
|
190
190
|
|
|
191
191
|
const opaqueBackground = composite(bg, composite(baseColor, WHITE));
|
|
192
192
|
const opaqueForeground = composite(fg, opaqueBackground);
|
|
193
|
-
const lighter = Math.max(
|
|
193
|
+
const lighter = Math.max(
|
|
194
|
+
relativeLuminance(opaqueForeground),
|
|
195
|
+
relativeLuminance(opaqueBackground),
|
|
196
|
+
);
|
|
194
197
|
const darker = Math.min(relativeLuminance(opaqueForeground), relativeLuminance(opaqueBackground));
|
|
195
198
|
|
|
196
199
|
return (lighter + LUMINANCE_OFFSET) / (darker + LUMINANCE_OFFSET);
|
package/src/color/parse.ts
CHANGED
|
@@ -50,12 +50,11 @@ function parseHex(digits: string): Rgba {
|
|
|
50
50
|
const full =
|
|
51
51
|
digits.length <= 4
|
|
52
52
|
? digits
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
.split('')
|
|
54
|
+
.map(digit => digit + digit)
|
|
55
|
+
.join('')
|
|
56
56
|
: digits;
|
|
57
|
-
const channel = (index: number) =>
|
|
58
|
-
parseInt(full.slice(index * 2, index * 2 + 2), 16);
|
|
57
|
+
const channel = (index: number) => parseInt(full.slice(index * 2, index * 2 + 2), 16);
|
|
59
58
|
|
|
60
59
|
return {
|
|
61
60
|
r: channel(0),
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,7 @@ export { ThemeConfigError, ThemeContrastError } from './theme/errors';
|
|
|
5
5
|
|
|
6
6
|
// ─── Unistyles ──────────────────────────────────────────────────────────────
|
|
7
7
|
export { createUnistylesConfig } from './unistyles/createUnistylesConfig';
|
|
8
|
-
export type {
|
|
9
|
-
UnistylesConfig,
|
|
10
|
-
UnistylesConfigOptions,
|
|
11
|
-
} from './unistyles/createUnistylesConfig';
|
|
8
|
+
export type { UnistylesConfig, UnistylesConfigOptions } from './unistyles/createUnistylesConfig';
|
|
12
9
|
export { applyThemeMode } from './unistyles/themeMode';
|
|
13
10
|
export type { ThemeMode, ThemeModeRuntime } from './unistyles/themeMode';
|
|
14
11
|
export { DEFAULT_BREAKPOINTS } from './tokens/defaults';
|
package/src/theme/buildThemes.ts
CHANGED
|
@@ -143,7 +143,10 @@ export function buildThemes<
|
|
|
143
143
|
TExt extends object = EmptyExtension,
|
|
144
144
|
TTone extends string = never,
|
|
145
145
|
TName extends string = never,
|
|
146
|
-
>(
|
|
146
|
+
>(
|
|
147
|
+
config: ThemeConfig<TExt, TTone, TName>,
|
|
148
|
+
options?: BuildThemesOptions,
|
|
149
|
+
): Themes<TExt, TTone, TName> {
|
|
147
150
|
validateThemeConfig(config);
|
|
148
151
|
|
|
149
152
|
const loose = config as unknown as ThemeConfig<object, string, string>;
|
|
@@ -161,7 +164,12 @@ export function buildThemes<
|
|
|
161
164
|
tones: { ...loose.tones, ...named.tones } as Record<string, string>,
|
|
162
165
|
};
|
|
163
166
|
|
|
164
|
-
return {
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
scheme: named.scheme,
|
|
170
|
+
config: namedConfig,
|
|
171
|
+
palettes: buildPalettes(namedConfig),
|
|
172
|
+
};
|
|
165
173
|
}),
|
|
166
174
|
];
|
|
167
175
|
const themes: Record<string, object> = {};
|
package/src/theme/overrides.ts
CHANGED
|
@@ -85,7 +85,12 @@ function mergeEntry(entry: Entry): unknown {
|
|
|
85
85
|
* keys must exist in `base`, colours must parse, numbers must be finite.
|
|
86
86
|
* Arrays are replaced whole. `base` itself is never mutated.
|
|
87
87
|
*/
|
|
88
|
-
export function mergeOverrides<T>(
|
|
88
|
+
export function mergeOverrides<T>(
|
|
89
|
+
base: T,
|
|
90
|
+
overrides: unknown,
|
|
91
|
+
path: string,
|
|
92
|
+
segments: string[] = [],
|
|
93
|
+
): T {
|
|
89
94
|
if (overrides == null) {
|
|
90
95
|
return base;
|
|
91
96
|
}
|
|
@@ -70,7 +70,10 @@ function validateFontMap(value: unknown, path: string): void {
|
|
|
70
70
|
|
|
71
71
|
for (const [weight, family] of Object.entries(value)) {
|
|
72
72
|
if (!FONT_WEIGHT_NAMES.includes(weight as FontWeightName)) {
|
|
73
|
-
throw new ThemeConfigError(
|
|
73
|
+
throw new ThemeConfigError(
|
|
74
|
+
`${path}.${weight}`,
|
|
75
|
+
`is not one of ${FONT_WEIGHT_NAMES.join(', ')}.`,
|
|
76
|
+
);
|
|
74
77
|
}
|
|
75
78
|
assertNonEmptyString(family, `${path}.${weight}`);
|
|
76
79
|
}
|
|
@@ -114,7 +117,10 @@ function validateNamedThemes(value: unknown): void {
|
|
|
114
117
|
const path = `themes.${name}`;
|
|
115
118
|
|
|
116
119
|
if (RESERVED_THEME_NAMES.has(name)) {
|
|
117
|
-
throw new ThemeConfigError(
|
|
120
|
+
throw new ThemeConfigError(
|
|
121
|
+
path,
|
|
122
|
+
'light and dark are built in; customise them with overrides.',
|
|
123
|
+
);
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
assertObject(theme, path);
|
package/src/tokens/typography.ts
CHANGED
|
@@ -84,7 +84,9 @@ export function buildFonts(config: TypographyConfig | undefined, platform: Platf
|
|
|
84
84
|
// iOS picks weights inside one family, so a partial iOS map only renames
|
|
85
85
|
// the weights it lists; Android needs a file per weight, hence nearest.
|
|
86
86
|
const nearest =
|
|
87
|
-
platform === 'ios'
|
|
87
|
+
platform === 'ios'
|
|
88
|
+
? perWeight?.[weight] && weight
|
|
89
|
+
: perWeight && nearestWeight(weight, perWeight);
|
|
88
90
|
|
|
89
91
|
if (!perWeight || !nearest) {
|
|
90
92
|
return { fontFamily: family, fontWeight: FONT_WEIGHTS[weight] };
|
|
@@ -117,7 +119,8 @@ export function buildTypography(
|
|
|
117
119
|
font: Record<FontWeightName, FontToken>,
|
|
118
120
|
family: string,
|
|
119
121
|
): Typography {
|
|
120
|
-
const scale =
|
|
122
|
+
const scale =
|
|
123
|
+
((config?.baseFontSize ?? DEFAULT_BODY_SIZE) / DEFAULT_BODY_SIZE) * (config?.fontScale ?? 1);
|
|
121
124
|
const typography = { fontFamily: family } as Typography;
|
|
122
125
|
|
|
123
126
|
for (const role of Object.keys(SCALE) as TypographyRole[]) {
|
package/src/types/colors.ts
CHANGED
|
@@ -2,13 +2,7 @@ export type ColorScheme = 'light' | 'dark';
|
|
|
2
2
|
|
|
3
3
|
/** Colours whose roles come in sets of four: X, onX, XContainer, onXContainer. */
|
|
4
4
|
export type AccentRoleName =
|
|
5
|
-
| '
|
|
6
|
-
| 'secondary'
|
|
7
|
-
| 'tertiary'
|
|
8
|
-
| 'error'
|
|
9
|
-
| 'success'
|
|
10
|
-
| 'warning'
|
|
11
|
-
| 'info';
|
|
5
|
+
'primary' | 'secondary' | 'tertiary' | 'error' | 'success' | 'warning' | 'info';
|
|
12
6
|
|
|
13
7
|
export interface ColorRoles {
|
|
14
8
|
primary: string;
|
|
@@ -69,13 +63,7 @@ export interface ColorRoles {
|
|
|
69
63
|
disabled: string;
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
export type StandardToneName =
|
|
73
|
-
| 'neutral'
|
|
74
|
-
| 'primary'
|
|
75
|
-
| 'info'
|
|
76
|
-
| 'success'
|
|
77
|
-
| 'warning'
|
|
78
|
-
| 'error';
|
|
66
|
+
export type StandardToneName = 'neutral' | 'primary' | 'info' | 'success' | 'warning' | 'error';
|
|
79
67
|
|
|
80
68
|
/** A matched colour set for one tone, for use with `byTone`. */
|
|
81
69
|
export interface ToneColors {
|
package/src/types/theme.ts
CHANGED
|
@@ -114,8 +114,10 @@ export interface BaseTheme<TTone extends string = never> {
|
|
|
114
114
|
|
|
115
115
|
export type EmptyExtension = Record<never, never>;
|
|
116
116
|
|
|
117
|
-
export type Theme<
|
|
118
|
-
TExt
|
|
117
|
+
export type Theme<
|
|
118
|
+
TExt extends object = EmptyExtension,
|
|
119
|
+
TTone extends string = never,
|
|
120
|
+
> = BaseTheme<TTone> & TExt;
|
|
119
121
|
|
|
120
122
|
export type ThemeName<TName extends string = never> = 'light' | 'dark' | TName;
|
|
121
123
|
|
package/src/types/typography.ts
CHANGED
|
@@ -32,9 +32,7 @@ export interface TypographyConfig {
|
|
|
32
32
|
export type TypographyRole = 'display' | 'headline' | 'title' | 'body' | 'label' | 'caption';
|
|
33
33
|
|
|
34
34
|
export type TypographyStyleName =
|
|
35
|
-
| TypographyRole
|
|
36
|
-
| `${TypographyRole}Small`
|
|
37
|
-
| `${TypographyRole}Large`;
|
|
35
|
+
TypographyRole | `${TypographyRole}Small` | `${TypographyRole}Large`;
|
|
38
36
|
|
|
39
37
|
export interface TypographyToken {
|
|
40
38
|
fontSize: number;
|
|
@@ -21,15 +21,15 @@ export type UnistylesConfigOptions<
|
|
|
21
21
|
breakpoints?: TBreakpoints;
|
|
22
22
|
} & (
|
|
23
23
|
| {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/** Theme active on first launch. Defaults to 'light'. */
|
|
25
|
+
initialTheme?: ThemeName<TName> | (() => ThemeName<TName>);
|
|
26
|
+
adaptiveThemes?: false;
|
|
27
|
+
}
|
|
28
28
|
| {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/** Follow the system colour scheme between light and dark. */
|
|
30
|
+
adaptiveThemes: boolean;
|
|
31
|
+
initialTheme?: never;
|
|
32
|
+
}
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
type UnistylesSettings<TName extends string> = UnistylesBaseSettings &
|
|
@@ -103,7 +103,11 @@ function validateOptions(options: unknown, themeNames: string[]): void {
|
|
|
103
103
|
throw new ThemeConfigError('options.CSSVars', 'must be a boolean.');
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if (
|
|
106
|
+
if (
|
|
107
|
+
nativeBreakpointsMode != null &&
|
|
108
|
+
nativeBreakpointsMode !== 'pixels' &&
|
|
109
|
+
nativeBreakpointsMode !== 'points'
|
|
110
|
+
) {
|
|
107
111
|
throw new ThemeConfigError('options.nativeBreakpointsMode', "must be 'pixels' or 'points'.");
|
|
108
112
|
}
|
|
109
113
|
|