@iveri/tokens 0.0.0-stage → 0.2.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 CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # @iveri/tokens
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Iveri design tokens — **one** copy of the palette, the radii and the motion scale, readable
4
+ from a browser bundle, a Tailwind theme and React Native alike. Zero runtime dependencies.
5
+ Published as native ES modules only; Metro and Node 26 load it through `require` as well.
6
+
7
+ ```bash
8
+ pnpm add @iveri/tokens
9
+ ```
10
+
11
+ ```ts
12
+ import { color, productColor, radius, toRgbChannels } from '@iveri/tokens';
13
+
14
+ color.accent; // '#1B2D72'
15
+ toRgbChannels(color.accent); // '27 45 114' — the form a CSS custom property needs
16
+ productColor.gold; // '#F6C76D'
17
+ radius.card; // 12
18
+ ```
19
+
20
+ ## Why it exists
21
+
22
+ Every panel used to declare the palette in its own `index.css`, and the six copies had already
23
+ drifted into four variants. `@iveri/react-kit@0.6.0` reduced them to one — **for the web**. Both
24
+ Expo apps kept a hand-maintained `theme.ts`, because the kit is a React **DOM** package whose
25
+ stylesheet imports webfonts: depending on it from React Native pulls a `react-dom` peer and a CSS
26
+ file Metro has no use for.
27
+
28
+ So the fleet went from six copies to three, and the three could only be kept in step by a spec in
29
+ each repository that said, in its own comment, **that it could not see drift against the others**.
30
+
31
+ ## What is in it, and what is not
32
+
33
+ | Export | For |
34
+ | ---------------------------------------------- | ---------------------------------------------------------------------------------- |
35
+ | `color` | The semantic palette every surface draws from |
36
+ | `productColor` | The navy hero and the gold that goes on it — a **product** voice, not panel chrome |
37
+ | `radius`, `duration`, `easing`, `easingPoints` | The non-colour scales both platforms share |
38
+ | `toRgbChannels` | `#1B2D72` → `27 45 114` |
39
+
40
+ **Hex is canonical and everything else is derived.** The web needs channel triplets so a custom
41
+ property can take an alpha (`rgb(var(--color-accent) / 0.16)`); React Native needs a plain colour
42
+ string. A package shipping triplets would make the mobile apps parse them; one shipping CSS would
43
+ be unusable from React Native at all.
44
+
45
+ **Not here:** typefaces, shadows and the type scale. A shadow is `box-shadow` on the web and
46
+ `shadowOffset`/`elevation` on React Native — the same intent expressed twice, with nothing shared
47
+ to extract — and a font stack is a list of family names on one platform and a chosen single face
48
+ on the other. Those live in `@iveri/react-kit` and in each app's `typography.ts`, where they are
49
+ honest.
50
+
51
+ **`productColor` is a separate export on purpose.** An admin console does not open on a navy card
52
+ with a gold rule, and should not start by accident.
53
+
54
+ ## The two deliberate collapses
55
+
56
+ - **`productColor.hero` holds the same value as `color.accentHover`.** The two product web apps
57
+ had been writing `#17285d` inline — fourteen occurrences between them, a navy three parts in 255
58
+ from the one the palette already had. Keeping the near-miss would have made it a twenty-fifth
59
+ colour nobody chose.
60
+ - **`positive` / `caution` / `critical`, not `success` / `warning` / `danger`.** The design
61
+ standard and six web panels already speak the first set, so the two mobile apps' local names
62
+ become aliases rather than the other way round.
63
+
64
+ ## Changing a value
65
+
66
+ The radii and the motion scale are a **copy** of `design-system/tokens.json`, which is outside any
67
+ package and is not published — so a change belongs in both places, deliberately. `color.spec.ts`
68
+ pins them, which is what makes the next invented number fail a test rather than ship.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The Iveri palette, as hex.
3
+ *
4
+ * **Hex is the canonical form and the other shapes are derived from it.** The web needs channel
5
+ * triplets so a CSS custom property can be used with an alpha (`rgb(var(--color-accent) / 0.1)`),
6
+ * and React Native needs a plain colour string; a package that shipped triplets would make the
7
+ * mobile apps parse them, and one that shipped only CSS would be unusable from React Native at
8
+ * all. `toRgbChannels` is the one conversion, so there is one place to be wrong.
9
+ *
10
+ * **Why this package exists.** Every panel used to declare the palette in its own `index.css`,
11
+ * and `@iveri/react-kit@0.6.0` reduced six copies to one — for the web. Both Expo apps still kept
12
+ * a hand-maintained `theme.ts`, because the kit is a React **DOM** package whose stylesheet
13
+ * imports webfonts: depending on it from React Native pulls a `react-dom` peer and a CSS file
14
+ * Metro has no use for. So the fleet went from six copies to three, and the three could only be
15
+ * kept in step by a spec in each repo that said, in its own comment, that it could not see drift
16
+ * against the others.
17
+ */
18
+ /** A `#rrggbb` string. Lowercase is not required; `toRgbChannels` accepts either. */
19
+ export type HexColor = `#${string}`;
20
+ /**
21
+ * The semantic palette every Iveri surface draws from.
22
+ *
23
+ * The names are the design standard's, which is why the two mobile apps' local `success` /
24
+ * `warning` / `danger` become aliases rather than the other way round: `design.md` and six web
25
+ * panels already speak `positive` / `caution` / `critical`, and a token package that renamed
26
+ * them would make the standard and the package disagree on the day it shipped.
27
+ */
28
+ export declare const color: {
29
+ readonly canvas: "#F7F8FB";
30
+ readonly surface: "#FFFFFF";
31
+ readonly raised: "#F1F3F8";
32
+ /** A well *below* the surface — a table header, a grouped field, code. */
33
+ readonly sunken: "#EDEFF5";
34
+ readonly line: "#E4E7EF";
35
+ /** For a divider that has to be seen rather than felt, and an input border on hover. */
36
+ readonly lineStrong: "#CFD4E2";
37
+ readonly ink: "#111729";
38
+ readonly inkSoft: "#3A4257";
39
+ readonly muted: "#5C6579";
40
+ readonly faint: "#8A92A6";
41
+ readonly accent: "#1B2D72";
42
+ readonly accentHover: "#14235C";
43
+ readonly accentInk: "#FFFFFF";
44
+ /** The selected-row and hover wash. Navy at 10% alpha goes grey; a mixed tint stays blue. */
45
+ readonly accentSoft: "#EEF1FA";
46
+ readonly accentLine: "#C7CFE8";
47
+ /** A lighter navy for links: the brand navy at body size reads as near-black. */
48
+ readonly link: "#2A46A8";
49
+ readonly positive: "#157A4B";
50
+ readonly positiveSoft: "#E3F5EA";
51
+ readonly caution: "#9A6412";
52
+ readonly cautionSoft: "#FBF1DC";
53
+ readonly critical: "#BE2C3C";
54
+ readonly criticalSoft: "#FBE9EB";
55
+ readonly info: "#1F6FA8";
56
+ readonly infoSoft: "#E5F1FA";
57
+ };
58
+ /**
59
+ * The louder half of the system: the navy hero panel and the warm accent that goes on it.
60
+ *
61
+ * **These are a product voice, not panel chrome.** Taxi, Delivery and the shop open on a navy
62
+ * card with a gold rule; an admin console does not, and should not start. They are a separate
63
+ * export so a panel cannot reach for them by accident, and so the next product app has them.
64
+ *
65
+ * ⚠ **`hero` deliberately holds the same value as `color.accentHover`.** The two product web
66
+ * apps had been writing `#17285d` inline — eight occurrences in one, six in the other, a navy
67
+ * three parts in 255 away from the one the palette already had. Keeping the near-miss would have
68
+ * made it a twenty-fifth colour nobody chose; collapsing it onto the deep navy that already
69
+ * existed is the honest reading, and the difference is not visible.
70
+ */
71
+ export declare const productColor: {
72
+ /** The deep navy a hero card is painted in. */
73
+ readonly hero: "#14235C";
74
+ /** Text and rules on `hero`. */
75
+ readonly heroInk: "#FFFFFF";
76
+ /** An error on a hero card, where `critical` would be unreadable. */
77
+ readonly heroCritical: "#FFD2DA";
78
+ /** Iveri's warm accent. Used sparingly, and never to carry status. */
79
+ readonly gold: "#F6C76D";
80
+ readonly goldHover: "#FFDA91";
81
+ /** The rule beside a page heading, and a route marker's ring. */
82
+ readonly goldLine: "#E3AD50";
83
+ readonly goldSoft: "#FFF2D6";
84
+ /** Text on `gold` and on `goldSoft`. */
85
+ readonly goldInk: "#8D5C16";
86
+ };
87
+ export type ColorName = keyof typeof color;
88
+ export type ProductColorName = keyof typeof productColor;
89
+ /**
90
+ * `#1B2D72` → `27 45 114`, the form a CSS custom property needs.
91
+ *
92
+ * Space-separated rather than comma-separated, because that is the syntax
93
+ * `rgb(var(--color-accent) / 0.16)` requires — a comma-separated triplet silently fails to parse
94
+ * there, and the browser drops the declaration, leaving the inherited colour. Which usually looks
95
+ * close enough to be deliberate.
96
+ */
97
+ export declare function toRgbChannels(hex: HexColor): string;
package/dist/color.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The Iveri palette, as hex.
3
+ *
4
+ * **Hex is the canonical form and the other shapes are derived from it.** The web needs channel
5
+ * triplets so a CSS custom property can be used with an alpha (`rgb(var(--color-accent) / 0.1)`),
6
+ * and React Native needs a plain colour string; a package that shipped triplets would make the
7
+ * mobile apps parse them, and one that shipped only CSS would be unusable from React Native at
8
+ * all. `toRgbChannels` is the one conversion, so there is one place to be wrong.
9
+ *
10
+ * **Why this package exists.** Every panel used to declare the palette in its own `index.css`,
11
+ * and `@iveri/react-kit@0.6.0` reduced six copies to one — for the web. Both Expo apps still kept
12
+ * a hand-maintained `theme.ts`, because the kit is a React **DOM** package whose stylesheet
13
+ * imports webfonts: depending on it from React Native pulls a `react-dom` peer and a CSS file
14
+ * Metro has no use for. So the fleet went from six copies to three, and the three could only be
15
+ * kept in step by a spec in each repo that said, in its own comment, that it could not see drift
16
+ * against the others.
17
+ */
18
+ /**
19
+ * The semantic palette every Iveri surface draws from.
20
+ *
21
+ * The names are the design standard's, which is why the two mobile apps' local `success` /
22
+ * `warning` / `danger` become aliases rather than the other way round: `design.md` and six web
23
+ * panels already speak `positive` / `caution` / `critical`, and a token package that renamed
24
+ * them would make the standard and the package disagree on the day it shipped.
25
+ */
26
+ export const color = {
27
+ /* ---- Neutrals ---------------------------------------------------------------------
28
+ * Cool and very slightly blue-shifted, and deliberately **not** Tailwind's `slate`: that
29
+ * ramp is the most recognisable palette on the web, and a product wearing it reads as
30
+ * generated before a single word is read. */
31
+ canvas: '#F7F8FB',
32
+ surface: '#FFFFFF',
33
+ raised: '#F1F3F8',
34
+ /** A well *below* the surface — a table header, a grouped field, code. */
35
+ sunken: '#EDEFF5',
36
+ line: '#E4E7EF',
37
+ /** For a divider that has to be seen rather than felt, and an input border on hover. */
38
+ lineStrong: '#CFD4E2',
39
+ /* ---- Text -------------------------------------------------------------------------
40
+ * Four genuine levels. `faint` carried `muted`'s value in all six panels until 0.6.0, so
41
+ * the hierarchy the standard describes was two levels wearing three names. */
42
+ ink: '#111729',
43
+ inkSoft: '#3A4257',
44
+ muted: '#5C6579',
45
+ faint: '#8A92A6',
46
+ /* ---- Brand ------------------------------------------------------------------------ */
47
+ accent: '#1B2D72',
48
+ accentHover: '#14235C',
49
+ accentInk: '#FFFFFF',
50
+ /** The selected-row and hover wash. Navy at 10% alpha goes grey; a mixed tint stays blue. */
51
+ accentSoft: '#EEF1FA',
52
+ accentLine: '#C7CFE8',
53
+ /** A lighter navy for links: the brand navy at body size reads as near-black. */
54
+ link: '#2A46A8',
55
+ /* ---- Status -----------------------------------------------------------------------
56
+ * Each deepened enough to carry 14px text on its own subtle background, and `info` is a
57
+ * real blue rather than a second copy of the accent — which is what made an info badge and
58
+ * an accent badge the same badge. */
59
+ positive: '#157A4B',
60
+ positiveSoft: '#E3F5EA',
61
+ caution: '#9A6412',
62
+ cautionSoft: '#FBF1DC',
63
+ critical: '#BE2C3C',
64
+ criticalSoft: '#FBE9EB',
65
+ info: '#1F6FA8',
66
+ infoSoft: '#E5F1FA',
67
+ };
68
+ /**
69
+ * The louder half of the system: the navy hero panel and the warm accent that goes on it.
70
+ *
71
+ * **These are a product voice, not panel chrome.** Taxi, Delivery and the shop open on a navy
72
+ * card with a gold rule; an admin console does not, and should not start. They are a separate
73
+ * export so a panel cannot reach for them by accident, and so the next product app has them.
74
+ *
75
+ * ⚠ **`hero` deliberately holds the same value as `color.accentHover`.** The two product web
76
+ * apps had been writing `#17285d` inline — eight occurrences in one, six in the other, a navy
77
+ * three parts in 255 away from the one the palette already had. Keeping the near-miss would have
78
+ * made it a twenty-fifth colour nobody chose; collapsing it onto the deep navy that already
79
+ * existed is the honest reading, and the difference is not visible.
80
+ */
81
+ export const productColor = {
82
+ /** The deep navy a hero card is painted in. */
83
+ hero: '#14235C',
84
+ /** Text and rules on `hero`. */
85
+ heroInk: '#FFFFFF',
86
+ /** An error on a hero card, where `critical` would be unreadable. */
87
+ heroCritical: '#FFD2DA',
88
+ /** Iveri's warm accent. Used sparingly, and never to carry status. */
89
+ gold: '#F6C76D',
90
+ goldHover: '#FFDA91',
91
+ /** The rule beside a page heading, and a route marker's ring. */
92
+ goldLine: '#E3AD50',
93
+ goldSoft: '#FFF2D6',
94
+ /** Text on `gold` and on `goldSoft`. */
95
+ goldInk: '#8D5C16',
96
+ };
97
+ /**
98
+ * `#1B2D72` → `27 45 114`, the form a CSS custom property needs.
99
+ *
100
+ * Space-separated rather than comma-separated, because that is the syntax
101
+ * `rgb(var(--color-accent) / 0.16)` requires — a comma-separated triplet silently fails to parse
102
+ * there, and the browser drops the declaration, leaving the inherited colour. Which usually looks
103
+ * close enough to be deliberate.
104
+ */
105
+ export function toRgbChannels(hex) {
106
+ const value = hex.replace('#', '');
107
+ if (!/^[0-9a-f]{6}$/i.test(value)) {
108
+ throw new Error(`Not a six-digit hex colour: ${hex}`);
109
+ }
110
+ const channels = [value.slice(0, 2), value.slice(2, 4), value.slice(4, 6)];
111
+ return channels.map((channel) => Number.parseInt(channel, 16)).join(' ');
112
+ }
@@ -0,0 +1,2 @@
1
+ export * from './color.js';
2
+ export * from './scale.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './color.js';
2
+ export * from './scale.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The non-colour scales both platforms share.
3
+ *
4
+ * ⚠ **These values are `design-system/tokens.json`'s, by its own names**, and this package
5
+ * cannot read that file: the standard lives outside any package and is not published. So this is
6
+ * a copy, and a change belongs in both places deliberately — which is exactly the mistake
7
+ * `@iveri/react-kit`'s first preset made, inventing 10px, 14px, 180ms and 260ms where the
8
+ * standard already said 12px, 16px, 200ms and 320ms.
9
+ */
10
+ /** Corner radii, in pixels. The web converts to rem; React Native wants the number. */
11
+ export declare const radius: {
12
+ /** A button, an input, a badge. */
13
+ readonly control: 8;
14
+ /** A card. */
15
+ readonly card: 12;
16
+ /** A panel or a sheet. */
17
+ readonly panel: 16;
18
+ };
19
+ /** Transition durations, in milliseconds. */
20
+ export declare const duration: {
21
+ /** A small state change — a hover, a focus ring. */
22
+ readonly fast: 120;
23
+ /** The standard transition. */
24
+ readonly normal: 200;
25
+ /** A panel or an overlay, and nothing smaller. */
26
+ readonly slow: 320;
27
+ };
28
+ /**
29
+ * The standard's easing, verbatim.
30
+ *
31
+ * A decelerating curve, so a thing arrives rather than coasting to a stop. React Native's
32
+ * `Easing.bezier` takes the same four numbers, which is why they are exported as numbers too.
33
+ */
34
+ export declare const easing: {
35
+ readonly standard: "cubic-bezier(0.2, 0, 0, 1)";
36
+ };
37
+ /** `easing.standard`'s control points, for a platform that cannot parse the CSS form. */
38
+ export declare const easingPoints: {
39
+ readonly standard: readonly [0.2, 0, 0, 1];
40
+ };
41
+ export type RadiusName = keyof typeof radius;
42
+ export type DurationName = keyof typeof duration;
package/dist/scale.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The non-colour scales both platforms share.
3
+ *
4
+ * ⚠ **These values are `design-system/tokens.json`'s, by its own names**, and this package
5
+ * cannot read that file: the standard lives outside any package and is not published. So this is
6
+ * a copy, and a change belongs in both places deliberately — which is exactly the mistake
7
+ * `@iveri/react-kit`'s first preset made, inventing 10px, 14px, 180ms and 260ms where the
8
+ * standard already said 12px, 16px, 200ms and 320ms.
9
+ */
10
+ /** Corner radii, in pixels. The web converts to rem; React Native wants the number. */
11
+ export const radius = {
12
+ /** A button, an input, a badge. */
13
+ control: 8,
14
+ /** A card. */
15
+ card: 12,
16
+ /** A panel or a sheet. */
17
+ panel: 16,
18
+ };
19
+ /** Transition durations, in milliseconds. */
20
+ export const duration = {
21
+ /** A small state change — a hover, a focus ring. */
22
+ fast: 120,
23
+ /** The standard transition. */
24
+ normal: 200,
25
+ /** A panel or an overlay, and nothing smaller. */
26
+ slow: 320,
27
+ };
28
+ /**
29
+ * The standard's easing, verbatim.
30
+ *
31
+ * A decelerating curve, so a thing arrives rather than coasting to a stop. React Native's
32
+ * `Easing.bezier` takes the same four numbers, which is why they are exported as numbers too.
33
+ */
34
+ export const easing = {
35
+ standard: 'cubic-bezier(0.2, 0, 0, 1)',
36
+ };
37
+ /** `easing.standard`'s control points, for a platform that cannot parse the CSS form. */
38
+ export const easingPoints = {
39
+ standard: [0.2, 0, 0, 1],
40
+ };
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@iveri/tokens",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "The Iveri design tokens — one copy of the palette, the radii and the motion scale, for the web and for React Native. Zero runtime dependencies.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/iverihq/iveri-tokens.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "engines": {
15
+ "node": ">=26"
16
+ },
17
+ "packageManager": "pnpm@12.18.0",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "clean": "rimraf dist",
32
+ "build": "pnpm clean && tsc -p tsconfig.build.json && node ./scripts/verify-zero-runtime-deps.mjs && node ./scripts/verify-loadable.mjs",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "test": "vitest run",
35
+ "lint": "eslint .",
36
+ "format": "prettier --write .",
37
+ "format:check": "prettier --check ."
38
+ },
39
+ "prettier": "@iveri/prettier-config",
40
+ "devDependencies": {
41
+ "@iveri/eslint-config": "^0.2.3",
42
+ "@iveri/prettier-config": "^0.1.0",
43
+ "@iveri/tsconfig": "^0.2.1",
44
+ "eslint": "^10.14.0",
45
+ "prettier": "^3.9.10",
46
+ "rimraf": "^6.1.4",
47
+ "typescript": "~6.0.3",
48
+ "vitest": "^5.1.0"
49
+ }
50
+ }