@ankhorage/zora 0.16.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +11 -13
  3. package/dist/components/heading/resolveHeadingRecipe.d.ts +2 -2
  4. package/dist/components/heading/resolveHeadingRecipe.d.ts.map +1 -1
  5. package/dist/components/heading/resolveHeadingRecipe.js.map +1 -1
  6. package/dist/components/text/resolveTextRecipe.d.ts +2 -2
  7. package/dist/components/text/resolveTextRecipe.d.ts.map +1 -1
  8. package/dist/components/text/resolveTextRecipe.js.map +1 -1
  9. package/dist/patterns/theme-composer/ThemeComposer.d.ts.map +1 -1
  10. package/dist/patterns/theme-composer/ThemeComposer.js +10 -86
  11. package/dist/patterns/theme-composer/ThemeComposer.js.map +1 -1
  12. package/dist/patterns/theme-composer/index.d.ts +1 -1
  13. package/dist/patterns/theme-composer/index.d.ts.map +1 -1
  14. package/dist/patterns/theme-composer/index.js.map +1 -1
  15. package/dist/patterns/theme-composer/types.d.ts +1 -13
  16. package/dist/patterns/theme-composer/types.d.ts.map +1 -1
  17. package/dist/patterns/theme-composer/types.js.map +1 -1
  18. package/dist/theme/createZoraThemeConfig.d.ts +1 -1
  19. package/dist/theme/createZoraThemeConfig.d.ts.map +1 -1
  20. package/dist/theme/createZoraThemeConfig.js +5 -6
  21. package/dist/theme/createZoraThemeConfig.js.map +1 -1
  22. package/dist/theme/index.d.ts +1 -1
  23. package/dist/theme/index.d.ts.map +1 -1
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/types.d.ts +16 -11
  26. package/dist/theme/types.d.ts.map +1 -1
  27. package/dist/theme/types.js +1 -20
  28. package/dist/theme/types.js.map +1 -1
  29. package/dist/theme/useZoraTheme.d.ts +1 -1
  30. package/dist/theme/zoraDefaultTheme.js +1 -1
  31. package/dist/theme/zoraDefaultTheme.js.map +1 -1
  32. package/package.json +4 -4
  33. package/src/components/heading/resolveHeadingRecipe.test.ts +30 -5
  34. package/src/components/heading/resolveHeadingRecipe.ts +6 -6
  35. package/src/components/text/resolveTextRecipe.test.ts +30 -5
  36. package/src/components/text/resolveTextRecipe.ts +6 -6
  37. package/src/patterns/theme-composer/ThemeComposer.test.ts +9 -141
  38. package/src/patterns/theme-composer/ThemeComposer.tsx +10 -131
  39. package/src/patterns/theme-composer/index.ts +1 -6
  40. package/src/patterns/theme-composer/types.ts +1 -15
  41. package/src/showcaseCoverage.test.ts +14 -0
  42. package/src/theme/createZoraThemeConfig.test.ts +51 -26
  43. package/src/theme/createZoraThemeConfig.ts +7 -7
  44. package/src/theme/index.ts +1 -3
  45. package/src/theme/types.ts +22 -34
  46. package/src/theme/zoraDefaultTheme.ts +1 -1
  47. package/dist/internal/color/colorToneRecipes.d.ts +0 -23
  48. package/dist/internal/color/colorToneRecipes.d.ts.map +0 -1
  49. package/dist/internal/color/colorToneRecipes.js +0 -139
  50. package/dist/internal/color/colorToneRecipes.js.map +0 -1
  51. package/dist/internal/color/harmony.d.ts +0 -12
  52. package/dist/internal/color/harmony.d.ts.map +0 -1
  53. package/dist/internal/color/harmony.js +0 -69
  54. package/dist/internal/color/harmony.js.map +0 -1
  55. package/dist/internal/color/hue.d.ts +0 -3
  56. package/dist/internal/color/hue.d.ts.map +0 -1
  57. package/dist/internal/color/hue.js +0 -7
  58. package/dist/internal/color/hue.js.map +0 -1
  59. package/dist/internal/color/index.d.ts +0 -10
  60. package/dist/internal/color/index.d.ts.map +0 -1
  61. package/dist/internal/color/index.js +0 -10
  62. package/dist/internal/color/index.js.map +0 -1
  63. package/dist/internal/color/oklch.d.ts +0 -6
  64. package/dist/internal/color/oklch.d.ts.map +0 -1
  65. package/dist/internal/color/oklch.js +0 -50
  66. package/dist/internal/color/oklch.js.map +0 -1
  67. package/dist/internal/color/primary.d.ts +0 -3
  68. package/dist/internal/color/primary.d.ts.map +0 -1
  69. package/dist/internal/color/primary.js +0 -44
  70. package/dist/internal/color/primary.js.map +0 -1
  71. package/dist/internal/color/roleHues.d.ts +0 -15
  72. package/dist/internal/color/roleHues.d.ts.map +0 -1
  73. package/dist/internal/color/roleHues.js +0 -103
  74. package/dist/internal/color/roleHues.js.map +0 -1
  75. package/dist/internal/color/roleScales.d.ts +0 -20
  76. package/dist/internal/color/roleScales.d.ts.map +0 -1
  77. package/dist/internal/color/roleScales.js +0 -79
  78. package/dist/internal/color/roleScales.js.map +0 -1
  79. package/dist/internal/color/scales.d.ts +0 -19
  80. package/dist/internal/color/scales.d.ts.map +0 -1
  81. package/dist/internal/color/scales.js +0 -135
  82. package/dist/internal/color/scales.js.map +0 -1
  83. package/dist/internal/color/semanticTokens.d.ts +0 -28
  84. package/dist/internal/color/semanticTokens.d.ts.map +0 -1
  85. package/dist/internal/color/semanticTokens.js +0 -84
  86. package/dist/internal/color/semanticTokens.js.map +0 -1
  87. package/dist/internal/color/types.d.ts +0 -10
  88. package/dist/internal/color/types.d.ts.map +0 -1
  89. package/dist/internal/color/types.js +0 -4
  90. package/dist/internal/color/types.js.map +0 -1
  91. package/dist/patterns/theme-composer/recommendations.d.ts +0 -14
  92. package/dist/patterns/theme-composer/recommendations.d.ts.map +0 -1
  93. package/dist/patterns/theme-composer/recommendations.js +0 -58
  94. package/dist/patterns/theme-composer/recommendations.js.map +0 -1
  95. package/src/internal/color/colorToneRecipes.test.ts +0 -89
  96. package/src/internal/color/colorToneRecipes.ts +0 -167
  97. package/src/internal/color/harmony.test.ts +0 -145
  98. package/src/internal/color/harmony.ts +0 -96
  99. package/src/internal/color/hue.test.ts +0 -28
  100. package/src/internal/color/hue.ts +0 -7
  101. package/src/internal/color/index.ts +0 -44
  102. package/src/internal/color/oklch.ts +0 -65
  103. package/src/internal/color/primary.test.ts +0 -105
  104. package/src/internal/color/primary.ts +0 -64
  105. package/src/internal/color/roleHues.test.ts +0 -197
  106. package/src/internal/color/roleHues.ts +0 -142
  107. package/src/internal/color/roleScales.test.ts +0 -220
  108. package/src/internal/color/roleScales.ts +0 -127
  109. package/src/internal/color/scales.test.ts +0 -151
  110. package/src/internal/color/scales.ts +0 -194
  111. package/src/internal/color/semanticTokens.test.ts +0 -170
  112. package/src/internal/color/semanticTokens.ts +0 -114
  113. package/src/internal/color/types.ts +0 -15
  114. package/src/patterns/theme-composer/recommendations.ts +0 -85
@@ -1,23 +1,28 @@
1
- import type { ColorHarmony, ColorTone, ThemeConfig } from '@ankhorage/surface';
1
+ import type { ColorHarmony, GeneratedThemeModeColors, GeneratedThemeSwatches, SemanticColorToken } from '@ankhorage/color-theory';
2
+ import type { AppCategory, ThemeConfig } from '@ankhorage/contracts';
3
+ import type { SurfaceTheme } from '@ankhorage/surface';
2
4
  export type ZoraThemeId = string;
3
5
  export type ZoraThemeMode = 'light' | 'dark';
4
- export type ZoraHexColor = `#${string}`;
5
- export declare const ZORA_COLOR_HARMONIES: readonly ["monochromatic", "analogous", "complementary", "splitComplementary", "triadic", "tetradic"];
6
- export type ZoraColorHarmony = ColorHarmony;
7
- export declare const ZORA_COLOR_TONES: readonly ["neutral", "pastel", "earth", "mineral", "muted", "jewel", "fluorescent", "obsidian", "vaporwave", "monochromeAccent"];
8
- export type ZoraColorTone = ColorTone;
9
6
  export interface ZoraTheme {
10
7
  id: ZoraThemeId;
11
- name?: string;
12
- primaryColor: ZoraHexColor;
13
- harmony: ZoraColorHarmony;
14
- colorTone: ZoraColorTone;
8
+ name: string;
9
+ appCategory: AppCategory;
10
+ primaryColor: string;
11
+ harmony: ColorHarmony;
12
+ }
13
+ export interface ZoraComputedThemeMode {
14
+ mode: ZoraThemeMode;
15
+ surfaceTheme: SurfaceTheme;
16
+ generated: GeneratedThemeModeColors;
17
+ swatches: GeneratedThemeSwatches;
18
+ semanticColors?: Record<SemanticColorToken, string>;
15
19
  }
16
20
  export interface ZoraComputedTheme {
17
21
  id: ZoraThemeId;
18
22
  name: string;
19
- mode: ZoraThemeMode;
20
23
  source: ZoraTheme;
21
24
  surfaceConfig: ThemeConfig;
25
+ light: ZoraComputedThemeMode;
26
+ dark: ZoraComputedThemeMode;
22
27
  }
23
28
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG,IAAI,MAAM,EAAE,CAAC;AAExC,eAAO,MAAM,oBAAoB,uGAOW,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE5C,eAAO,MAAM,gBAAgB,kIAWY,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC;AAEtC,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,aAAa,EAAE,WAAW,CAAC;CAC5B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,wBAAwB,CAAC;IACpC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,aAAa,EAAE,WAAW,CAAC;IAC3B,KAAK,EAAE,qBAAqB,CAAC;IAC7B,IAAI,EAAE,qBAAqB,CAAC;CAC7B"}
@@ -1,21 +1,2 @@
1
- export const ZORA_COLOR_HARMONIES = [
2
- 'monochromatic',
3
- 'analogous',
4
- 'complementary',
5
- 'splitComplementary',
6
- 'triadic',
7
- 'tetradic',
8
- ];
9
- export const ZORA_COLOR_TONES = [
10
- 'neutral',
11
- 'pastel',
12
- 'earth',
13
- 'mineral',
14
- 'muted',
15
- 'jewel',
16
- 'fluorescent',
17
- 'obsidian',
18
- 'vaporwave',
19
- 'monochromeAccent',
20
- ];
1
+ export {};
21
2
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,eAAe;IACf,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,SAAS;IACT,UAAU;CACgC,CAAC;AAI7C,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,SAAS;IACT,QAAQ;IACR,OAAO;IACP,SAAS;IACT,OAAO;IACP,OAAO;IACP,aAAa;IACb,UAAU;IACV,WAAW;IACX,kBAAkB;CACqB,CAAC","sourcesContent":["import type { ColorHarmony, ColorTone, ThemeConfig } from '@ankhorage/surface';\n\nexport type ZoraThemeId = string;\n\nexport type ZoraThemeMode = 'light' | 'dark';\n\nexport type ZoraHexColor = `#${string}`;\n\nexport const ZORA_COLOR_HARMONIES = [\n 'monochromatic',\n 'analogous',\n 'complementary',\n 'splitComplementary',\n 'triadic',\n 'tetradic',\n] as const satisfies readonly ColorHarmony[];\n\nexport type ZoraColorHarmony = ColorHarmony;\n\nexport const ZORA_COLOR_TONES = [\n 'neutral',\n 'pastel',\n 'earth',\n 'mineral',\n 'muted',\n 'jewel',\n 'fluorescent',\n 'obsidian',\n 'vaporwave',\n 'monochromeAccent',\n] as const satisfies readonly ColorTone[];\n\nexport type ZoraColorTone = ColorTone;\n\nexport interface ZoraTheme {\n id: ZoraThemeId;\n name?: string;\n primaryColor: ZoraHexColor;\n harmony: ZoraColorHarmony;\n colorTone: ZoraColorTone;\n}\n\nexport interface ZoraComputedTheme {\n id: ZoraThemeId;\n name: string;\n mode: ZoraThemeMode;\n source: ZoraTheme;\n surfaceConfig: ThemeConfig;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ColorHarmony,\n GeneratedThemeModeColors,\n GeneratedThemeSwatches,\n SemanticColorToken,\n} from '@ankhorage/color-theory';\nimport type { AppCategory, ThemeConfig } from '@ankhorage/contracts';\nimport type { SurfaceTheme } from '@ankhorage/surface';\n\nexport type ZoraThemeId = string;\n\nexport type ZoraThemeMode = 'light' | 'dark';\n\nexport interface ZoraTheme {\n id: ZoraThemeId;\n name: string;\n appCategory: AppCategory;\n primaryColor: string;\n harmony: ColorHarmony;\n}\n\nexport interface ZoraComputedThemeMode {\n mode: ZoraThemeMode;\n surfaceTheme: SurfaceTheme;\n generated: GeneratedThemeModeColors;\n swatches: GeneratedThemeSwatches;\n semanticColors?: Record<SemanticColorToken, string>;\n}\n\nexport interface ZoraComputedTheme {\n id: ZoraThemeId;\n name: string;\n source: ZoraTheme;\n surfaceConfig: ThemeConfig;\n light: ZoraComputedThemeMode;\n dark: ZoraComputedThemeMode;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  export declare function useZoraTheme(): {
2
- theme: import("@ankhorage/surface").AnkhTheme;
2
+ theme: import("@ankhorage/surface").SurfaceTheme;
3
3
  mode: "light" | "dark";
4
4
  setThemeConfig: (config: Partial<import("@ankhorage/contracts").ThemeConfig>) => void;
5
5
  setMode: (mode: "light" | "dark") => void;
@@ -1,8 +1,8 @@
1
1
  export const zoraDefaultTheme = {
2
2
  id: 'zora',
3
3
  name: 'ZORA',
4
+ appCategory: 'developer_tools',
4
5
  primaryColor: '#0f766e',
5
6
  harmony: 'analogous',
6
- colorTone: 'jewel',
7
7
  };
8
8
  //# sourceMappingURL=zoraDefaultTheme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zoraDefaultTheme.js","sourceRoot":"","sources":["../../src/theme/zoraDefaultTheme.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,MAAM;IACZ,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,WAAW;IACpB,SAAS,EAAE,OAAO;CACnB,CAAC","sourcesContent":["import type { ZoraTheme } from './types';\n\nexport const zoraDefaultTheme: ZoraTheme = {\n id: 'zora',\n name: 'ZORA',\n primaryColor: '#0f766e',\n harmony: 'analogous',\n colorTone: 'jewel',\n};\n"]}
1
+ {"version":3,"file":"zoraDefaultTheme.js","sourceRoot":"","sources":["../../src/theme/zoraDefaultTheme.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,iBAAiB;IAC9B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,WAAW;CACrB,CAAC","sourcesContent":["import type { ZoraTheme } from './types';\n\nexport const zoraDefaultTheme: ZoraTheme = {\n id: 'zora',\n name: 'ZORA',\n appCategory: 'developer_tools',\n primaryColor: '#0f766e',\n harmony: 'analogous',\n};\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "0.16.2",
4
+ "version": "1.0.0",
5
5
  "description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
6
6
  "homepage": "https://github.com/ankhorage/zora#readme",
7
7
  "bugs": {
@@ -43,8 +43,9 @@
43
43
  }
44
44
  },
45
45
  "dependencies": {
46
- "@ankhorage/surface": "^0.2.3",
47
- "culori": "^4.0.2"
46
+ "@ankhorage/color-theory": "^0.0.2",
47
+ "@ankhorage/contracts": "^1.1.0",
48
+ "@ankhorage/surface": "^1.0.0"
48
49
  },
49
50
  "files": [
50
51
  "dist",
@@ -86,7 +87,6 @@
86
87
  "@changesets/cli": "^2.31.0",
87
88
  "@expo/vector-icons": "^15.1.1",
88
89
  "@react-native-picker/picker": "^2.11.4",
89
- "@types/culori": "^4.0.1",
90
90
  "@types/bun": "^1.3.13",
91
91
  "@types/node": "^25.6.0",
92
92
  "@types/react": "^19.2.14",
@@ -1,4 +1,4 @@
1
- import type { AnkhTheme, FontWeight, RoleSemantics } from '@ankhorage/surface';
1
+ import type { FontWeight, RoleSemantics, SurfaceTheme } from '@ankhorage/surface';
2
2
  import { describe, expect, test } from 'bun:test';
3
3
 
4
4
  import { resolveHeadingRecipe, resolveHeadingSizeFromLevel } from './resolveHeadingRecipe';
@@ -30,7 +30,7 @@ function createRole(base: string): RoleSemantics {
30
30
  };
31
31
  }
32
32
 
33
- function createTestTheme(): AnkhTheme {
33
+ function createTestTheme(): SurfaceTheme {
34
34
  return {
35
35
  colors: {
36
36
  primary: '#0f766e',
@@ -52,12 +52,10 @@ function createTestTheme(): AnkhTheme {
52
52
  light: {
53
53
  primaryColor: '#0f766e',
54
54
  harmony: 'analogous',
55
- colorTone: 'jewel',
56
55
  },
57
56
  dark: {
58
57
  primaryColor: '#2dd4bf',
59
58
  harmony: 'analogous',
60
- colorTone: 'jewel',
61
59
  },
62
60
  },
63
61
  radii: {
@@ -67,7 +65,34 @@ function createTestTheme(): AnkhTheme {
67
65
  l: 16,
68
66
  full: 9999,
69
67
  },
70
- scales: {},
68
+ swatches: {
69
+ primary: {
70
+ 50: '#f0fdfa',
71
+ 100: '#ccfbf1',
72
+ 200: '#99f6e4',
73
+ 300: '#5eead4',
74
+ 400: '#2dd4bf',
75
+ 500: '#14b8a6',
76
+ 600: '#0d9488',
77
+ 700: '#0f766e',
78
+ 800: '#115e59',
79
+ 900: '#134e4a',
80
+ 950: '#042f2e',
81
+ },
82
+ neutral: {
83
+ 50: '#f9fafb',
84
+ 100: '#f3f4f6',
85
+ 200: '#e5e7eb',
86
+ 300: '#d1d5db',
87
+ 400: '#9ca3af',
88
+ 500: '#6b7280',
89
+ 600: '#4b5563',
90
+ 700: '#374151',
91
+ 800: '#1f2937',
92
+ 900: '#111827',
93
+ 950: '#030712',
94
+ },
95
+ },
71
96
  semantics: {
72
97
  neutral: {
73
98
  bg: '#ffffff',
@@ -1,4 +1,4 @@
1
- import type { AnkhTheme, FontWeight } from '@ankhorage/surface';
1
+ import type { FontWeight, SurfaceTheme } from '@ankhorage/surface';
2
2
  import type { TextStyle } from 'react-native';
3
3
 
4
4
  import type { HeadingAlign, HeadingLevel, HeadingSize, HeadingTone, HeadingWeight } from './types';
@@ -52,7 +52,7 @@ function resolveHeadingLevelFromSize(size: Exclude<HeadingSize, 'display'>): Hea
52
52
  }
53
53
  }
54
54
 
55
- function resolveSizeRecipe(theme: AnkhTheme, size: HeadingSize): HeadingRecipe {
55
+ function resolveSizeRecipe(theme: SurfaceTheme, size: HeadingSize): HeadingRecipe {
56
56
  if (size === 'display') {
57
57
  const fontSize = theme.typography.sizes['3xl'];
58
58
 
@@ -72,7 +72,7 @@ function resolveSizeRecipe(theme: AnkhTheme, size: HeadingSize): HeadingRecipe {
72
72
  };
73
73
  }
74
74
 
75
- function resolveToneColor(theme: AnkhTheme, tone: HeadingTone): string {
75
+ function resolveToneColor(theme: SurfaceTheme, tone: HeadingTone): string {
76
76
  switch (tone) {
77
77
  case 'muted':
78
78
  return theme.semantics.content.muted;
@@ -94,7 +94,7 @@ function resolveToneColor(theme: AnkhTheme, tone: HeadingTone): string {
94
94
  }
95
95
  }
96
96
 
97
- function resolveWeight(theme: AnkhTheme, weight: HeadingWeight): FontWeight {
97
+ function resolveWeight(theme: SurfaceTheme, weight: HeadingWeight): FontWeight {
98
98
  return theme.typography.weights[weight];
99
99
  }
100
100
 
@@ -103,7 +103,7 @@ function resolveFontFamily({
103
103
  weight,
104
104
  italic,
105
105
  }: {
106
- theme: AnkhTheme;
106
+ theme: SurfaceTheme;
107
107
  weight: FontWeight;
108
108
  italic: boolean;
109
109
  }): string | undefined {
@@ -111,7 +111,7 @@ function resolveFontFamily({
111
111
  }
112
112
 
113
113
  export function resolveHeadingRecipe(
114
- theme: AnkhTheme,
114
+ theme: SurfaceTheme,
115
115
  { align, italic = false, level, size, tone = 'default', weight }: ResolveHeadingRecipeOptions,
116
116
  ): TextStyle {
117
117
  const recipe = resolveSizeRecipe(theme, size ?? resolveHeadingSizeFromLevel(level));
@@ -1,9 +1,9 @@
1
1
  import type {
2
- AnkhTheme,
3
2
  Breakpoint,
4
3
  FontWeight,
5
4
  Responsive,
6
5
  RoleSemantics,
6
+ SurfaceTheme,
7
7
  } from '@ankhorage/surface';
8
8
  import { describe, expect, mock, test } from 'bun:test';
9
9
 
@@ -64,7 +64,7 @@ function createRole(base: string): RoleSemantics {
64
64
  };
65
65
  }
66
66
 
67
- function createTestTheme(): AnkhTheme {
67
+ function createTestTheme(): SurfaceTheme {
68
68
  return {
69
69
  colors: {
70
70
  primary: '#0f766e',
@@ -86,12 +86,10 @@ function createTestTheme(): AnkhTheme {
86
86
  light: {
87
87
  primaryColor: '#0f766e',
88
88
  harmony: 'analogous',
89
- colorTone: 'jewel',
90
89
  },
91
90
  dark: {
92
91
  primaryColor: '#2dd4bf',
93
92
  harmony: 'analogous',
94
- colorTone: 'jewel',
95
93
  },
96
94
  },
97
95
  radii: {
@@ -101,7 +99,34 @@ function createTestTheme(): AnkhTheme {
101
99
  l: 16,
102
100
  full: 9999,
103
101
  },
104
- scales: {},
102
+ swatches: {
103
+ primary: {
104
+ 50: '#f0fdfa',
105
+ 100: '#ccfbf1',
106
+ 200: '#99f6e4',
107
+ 300: '#5eead4',
108
+ 400: '#2dd4bf',
109
+ 500: '#14b8a6',
110
+ 600: '#0d9488',
111
+ 700: '#0f766e',
112
+ 800: '#115e59',
113
+ 900: '#134e4a',
114
+ 950: '#042f2e',
115
+ },
116
+ neutral: {
117
+ 50: '#f9fafb',
118
+ 100: '#f3f4f6',
119
+ 200: '#e5e7eb',
120
+ 300: '#d1d5db',
121
+ 400: '#9ca3af',
122
+ 500: '#6b7280',
123
+ 600: '#4b5563',
124
+ 700: '#374151',
125
+ 800: '#1f2937',
126
+ 900: '#111827',
127
+ 950: '#030712',
128
+ },
129
+ },
105
130
  semantics: {
106
131
  neutral: {
107
132
  bg: '#ffffff',
@@ -1,9 +1,9 @@
1
1
  import {
2
- type AnkhTheme,
3
2
  type Breakpoint,
4
3
  type FontWeight,
5
4
  resolveResponsive,
6
5
  type Responsive,
6
+ type SurfaceTheme,
7
7
  } from '@ankhorage/surface';
8
8
  import type { TextStyle } from 'react-native';
9
9
 
@@ -18,7 +18,7 @@ interface VariantRecipe {
18
18
  }
19
19
 
20
20
  interface ResolveTextStyleOptions {
21
- theme: AnkhTheme;
21
+ theme: SurfaceTheme;
22
22
  breakpoint: Breakpoint;
23
23
  variant?: Responsive<TextVariant>;
24
24
  tone?: Responsive<TextTone>;
@@ -31,7 +31,7 @@ function isMediumBreakpointOrLarger(breakpoint: Breakpoint): boolean {
31
31
  return breakpoint === 'md' || breakpoint === 'lg' || breakpoint === 'xl';
32
32
  }
33
33
 
34
- function resolveWeight(theme: AnkhTheme, weight: TextWeight): FontWeight {
34
+ function resolveWeight(theme: SurfaceTheme, weight: TextWeight): FontWeight {
35
35
  return theme.typography.weights[weight];
36
36
  }
37
37
 
@@ -41,7 +41,7 @@ function resolveFontFamily({
41
41
  weight,
42
42
  italic,
43
43
  }: {
44
- theme: AnkhTheme;
44
+ theme: SurfaceTheme;
45
45
  variant: TextVariant;
46
46
  weight: FontWeight;
47
47
  italic: boolean;
@@ -54,7 +54,7 @@ function resolveFontFamily({
54
54
  }
55
55
 
56
56
  function resolveVariantRecipe(
57
- theme: AnkhTheme,
57
+ theme: SurfaceTheme,
58
58
  variant: TextVariant,
59
59
  breakpoint: Breakpoint,
60
60
  ): VariantRecipe {
@@ -109,7 +109,7 @@ function resolveVariantRecipe(
109
109
  }
110
110
  }
111
111
 
112
- function resolveToneColor(theme: AnkhTheme, tone: TextTone): string {
112
+ function resolveToneColor(theme: SurfaceTheme, tone: TextTone): string {
113
113
  switch (tone) {
114
114
  case 'muted':
115
115
  return theme.semantics.content.muted;
@@ -1,14 +1,8 @@
1
+ import { COLOR_HARMONIES } from '@ankhorage/color-theory';
1
2
  import { describe, expect, test } from 'bun:test';
2
3
 
3
- import { ZORA_COLOR_HARMONIES, ZORA_COLOR_TONES } from '../../theme/types';
4
4
  import { zoraDefaultTheme } from '../../theme/zoraDefaultTheme';
5
- import {
6
- createThemeFromThemeComposerRecommendation,
7
- findThemeComposerRecommendation,
8
- formatThemeComposerLabel,
9
- hueDegreesToZoraHexColor,
10
- } from './recommendations';
11
- import type { ThemeComposerProps, ThemeComposerRecommendation } from './types';
5
+ import type { ThemeComposerProps } from './types';
12
6
 
13
7
  // Validate the exported types compile correctly by asserting shape
14
8
  describe('ThemeComposerProps', () => {
@@ -17,9 +11,10 @@ describe('ThemeComposerProps', () => {
17
11
  value: zoraDefaultTheme,
18
12
  onChange: () => undefined,
19
13
  };
20
- expect(props.value.primaryColor).toBe('#0f766e');
14
+ expect(props.value.primaryColor).toBe(zoraDefaultTheme.primaryColor);
21
15
  expect(props.value.harmony).toBe('analogous');
22
- expect(props.value.colorTone).toBe('jewel');
16
+ expect(props.value.appCategory).toBe('developer_tools');
17
+ expect(props.value.name).toBe('ZORA');
23
18
  });
24
19
 
25
20
  test('accepts optional mode and onModeChange', () => {
@@ -44,56 +39,19 @@ describe('ThemeComposerProps', () => {
44
39
  props.onSubmit?.(zoraDefaultTheme);
45
40
  expect(submitted).toBe(true);
46
41
  });
47
-
48
- test('accepts optional recommendation inputs', () => {
49
- const recommendation: ThemeComposerRecommendation = {
50
- appCategory: 'finance_money',
51
- appMood: 'trustworthy',
52
- suggestedColorTone: 'mineral',
53
- suggestedHarmony: 'analogous',
54
- suggestedPrimaryHueDegrees: 195,
55
- };
56
- const props: ThemeComposerProps = {
57
- value: zoraDefaultTheme,
58
- onChange: () => undefined,
59
- appCategory: 'finance_money',
60
- appMood: 'trustworthy',
61
- recommendations: [recommendation],
62
- };
63
-
64
- expect(props.recommendations?.[0]?.suggestedColorTone).toBe('mineral');
65
- });
66
42
  });
67
43
 
68
- describe('ZORA_COLOR_HARMONIES coverage for ThemeComposer', () => {
69
- test('all harmony values are present for the Select options', () => {
44
+ describe('COLOR_HARMONIES coverage for ThemeComposer', () => {
45
+ test('all canonical harmony values are present for the Select options', () => {
70
46
  const expected = [
71
47
  'monochromatic',
72
48
  'analogous',
73
49
  'complementary',
74
- 'splitComplementary',
75
50
  'triadic',
76
51
  'tetradic',
52
+ 'splitComplementary',
77
53
  ] as const;
78
- expect(ZORA_COLOR_HARMONIES).toEqual(expected);
79
- });
80
- });
81
-
82
- describe('ZORA_COLOR_TONES coverage for ThemeComposer', () => {
83
- test('all color tones are present for the Select options', () => {
84
- const expected = [
85
- 'neutral',
86
- 'pastel',
87
- 'earth',
88
- 'mineral',
89
- 'muted',
90
- 'jewel',
91
- 'fluorescent',
92
- 'obsidian',
93
- 'vaporwave',
94
- 'monochromeAccent',
95
- ] as const;
96
- expect(ZORA_COLOR_TONES).toEqual(expected);
54
+ expect(COLOR_HARMONIES).toEqual(expected);
97
55
  });
98
56
  });
99
57
 
@@ -109,94 +67,4 @@ describe('onChange propagation contract', () => {
109
67
  expect(received).toHaveLength(1);
110
68
  expect(received[0]?.harmony).toBe('triadic');
111
69
  });
112
-
113
- test('onChange receives updated theme with new colorTone', () => {
114
- const received: (typeof zoraDefaultTheme)[] = [];
115
- const props: ThemeComposerProps = {
116
- value: zoraDefaultTheme,
117
- onChange: (t) => received.push(t),
118
- };
119
- props.onChange({ ...zoraDefaultTheme, colorTone: 'obsidian' });
120
- expect(received[0]?.colorTone).toBe('obsidian');
121
- });
122
- });
123
-
124
- describe('ThemeComposer recommendation helpers', () => {
125
- const recommendations: readonly ThemeComposerRecommendation[] = [
126
- {
127
- appCategory: 'finance_money',
128
- appMood: 'trustworthy',
129
- suggestedColorTone: 'mineral',
130
- suggestedHarmony: 'analogous',
131
- suggestedPrimaryHueDegrees: 195,
132
- },
133
- {
134
- appCategory: 'graphics_design',
135
- appMood: 'creative',
136
- suggestedColorTone: 'vaporwave',
137
- suggestedHarmony: 'splitComplementary',
138
- },
139
- ];
140
-
141
- test('finds a recommendation by app category and mood', () => {
142
- const found = findThemeComposerRecommendation({
143
- appCategory: 'finance_money',
144
- appMood: 'trustworthy',
145
- recommendations,
146
- });
147
-
148
- expect(found?.suggestedColorTone).toBe('mineral');
149
- });
150
-
151
- test('does not find a recommendation with mismatched mood', () => {
152
- const found = findThemeComposerRecommendation({
153
- appCategory: 'finance_money',
154
- appMood: 'creative',
155
- recommendations,
156
- });
157
-
158
- expect(found).toBeUndefined();
159
- });
160
-
161
- test('creates an updated theme only when recommendation is explicitly applied', () => {
162
- const [recommendation] = recommendations;
163
- if (recommendation === undefined) {
164
- throw new Error('Expected fixture recommendation.');
165
- }
166
-
167
- const updated = createThemeFromThemeComposerRecommendation({
168
- value: zoraDefaultTheme,
169
- recommendation,
170
- });
171
-
172
- expect(zoraDefaultTheme.colorTone).toBe('jewel');
173
- expect(updated.colorTone).toBe('mineral');
174
- expect(updated.harmony).toBe('analogous');
175
- expect(updated.primaryColor).not.toBe(zoraDefaultTheme.primaryColor);
176
- });
177
-
178
- test('keeps the current primary color when recommendation has no hue', () => {
179
- const [, recommendation] = recommendations;
180
- if (recommendation === undefined) {
181
- throw new Error('Expected fixture recommendation.');
182
- }
183
-
184
- const updated = createThemeFromThemeComposerRecommendation({
185
- value: zoraDefaultTheme,
186
- recommendation,
187
- });
188
-
189
- expect(updated.primaryColor).toBe(zoraDefaultTheme.primaryColor);
190
- expect(updated.colorTone).toBe('vaporwave');
191
- });
192
-
193
- test('formats serialized labels for display', () => {
194
- expect(formatThemeComposerLabel('finance_money')).toBe('Finance money');
195
- expect(formatThemeComposerLabel('splitComplementary')).toBe('Split Complementary');
196
- });
197
-
198
- test('converts hue degrees to lowercase hex', () => {
199
- expect(hueDegreesToZoraHexColor(195)).toMatch(/^#[0-9a-f]{6}$/);
200
- expect(hueDegreesToZoraHexColor(555)).toBe(hueDegreesToZoraHexColor(195));
201
- });
202
70
  });