@ankhorage/zora 0.16.2 → 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.
Files changed (114) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +27 -22
  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 +101 -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 +3 -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 +128 -114
  38. package/src/patterns/theme-composer/ThemeComposer.tsx +130 -128
  39. package/src/patterns/theme-composer/index.ts +1 -6
  40. package/src/patterns/theme-composer/types.ts +4 -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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 938bcfe: ThemeComposer now edits the full ZoraTheme source model.
8
+ - ThemeComposer adds name editing with empty-name validation.
9
+ - ThemeComposer adds app category editing via a Select using APP_CATEGORIES from @ankhorage/contracts.
10
+ - ThemeComposer supports optional `appCategories` prop for narrowing the category options list.
11
+ - ThemeComposer validates primary color input with parseHexColorOrThrow from @ankhorage/color-theory while keeping public `primaryColor` as `string`.
12
+ - ThemeComposer preview shows name, appCategory, primaryColor, and harmony metadata.
13
+ - README and examples app updated to reflect the new API.
14
+
15
+ ## 1.0.0
16
+
17
+ ### Major Changes
18
+
19
+ - 4d50ada: **Breaking: ZORA core theme model and color stack cleanup**
20
+
21
+ ## Removed APIs
22
+ - `ZoraTheme.colorTone` — removed; `colorTone` is no longer part of the theme seed
23
+ - `ZoraColorTone` type — removed
24
+ - `ZORA_COLOR_TONES` constant — removed
25
+ - `ZoraHexColor` type — removed; ZORA themes now accept normal string hex values
26
+ - `ZoraComputedTheme.mode` — replaced by `light` and `dark` mode objects
27
+ - `ThemeComposerRecommendation` — removed
28
+ - `ThemeComposerAppMood` — removed
29
+ - `ThemeComposerAppCategory` — removed (was an opaque `string` alias)
30
+ - `ThemeComposerProps.appMood` — removed
31
+ - `ThemeComposerProps.recommendations` — removed
32
+ - Internal color stack (`src/internal/color/`) — removed; ZORA no longer owns color math
33
+
34
+ ## Added / Changed APIs
35
+ - `ZoraTheme.name` — now **required** (was optional); ZORA themes must provide a real display name
36
+ - `ZoraTheme.appCategory` — new required field; use `AppCategory` from `@ankhorage/contracts`
37
+ - `ZoraTheme.primaryColor` — remains a public `string`; ZORA validates it internally with `@ankhorage/color-theory`
38
+ - `ZoraComputedTheme` — now has `light: ZoraComputedThemeMode` and `dark: ZoraComputedThemeMode` instead of a single `mode`
39
+ - `ZoraComputedThemeMode` — new type: `{ mode, surfaceTheme, generated, swatches, semanticColors? }`
40
+ - Primary color is now preserved identically for both light and dark `ThemeConfig` modes (no dark-mode mutation)
41
+ - `SurfaceTheme` (from `@ankhorage/surface`) replaces `AnkhTheme` as the resolved runtime theme type
42
+
43
+ ## New dependencies
44
+ - `@ankhorage/color-theory@^0.0.2` — canonical color types and generation utilities
45
+ - `@ankhorage/contracts@^1.1.0` — theme config and app category types
46
+
47
+ ## Removed dependencies
48
+ - `culori` — no longer a direct ZORA dependency; color math is delegated to `@ankhorage/color-theory`
49
+ - `@types/culori` — removed
50
+
51
+ ## Migration
52
+
53
+ ```ts
54
+ // Before
55
+ const theme: ZoraTheme = {
56
+ id: 'my-theme',
57
+ name: 'My Theme',
58
+ primaryColor: '#0f766e',
59
+ harmony: 'analogous',
60
+ colorTone: 'jewel',
61
+ };
62
+
63
+ // After
64
+ const theme: ZoraTheme = {
65
+ id: 'my-theme',
66
+ name: 'My Theme',
67
+ appCategory: 'developer_tools',
68
+ primaryColor: '#0f766e',
69
+ harmony: 'analogous',
70
+ };
71
+ ```
72
+
73
+ The `name` field is now required. `primaryColor` stays app-facing and ergonomic as
74
+ a string, while ZORA validates it when converting the source theme to a runtime
75
+ `ThemeConfig`.
76
+
3
77
  ## 0.16.2
4
78
 
5
79
  ### Patch Changes
package/README.md CHANGED
@@ -90,28 +90,24 @@ export function App({ appTheme }: { appTheme: ZoraTheme }) {
90
90
  }
91
91
  ```
92
92
 
93
- ZORA themes use a single seed `primaryColor`. ZORA derives mode-specific primary
94
- colors for light and dark mode internally.
93
+ ZORA themes use a single seed `primaryColor`. The selected primary color is preserved
94
+ identically for both light and dark mode. Color generation is handled by
95
+ `@ankhorage/color-theory` via Surface.
95
96
 
96
97
  ```tsx
97
98
  <ZoraProvider
98
99
  theme={{
99
100
  id: 'studio',
101
+ name: 'Studio',
102
+ appCategory: 'developer_tools',
100
103
  primaryColor: '#0f766e',
101
104
  harmony: 'analogous',
102
- colorTone: 'jewel',
103
105
  }}
104
106
  >
105
107
  <App />
106
108
  </ZoraProvider>
107
109
  ```
108
110
 
109
- > **`colorTone` vs component `tone`** — `colorTone` is a theme-seed field that
110
- > selects the color-world / palette tone for the whole theme (e.g. `'jewel'`,
111
- > `'pastel'`). Component `tone` props (e.g. `<Text tone="muted" />`,
112
- > `<Button tone="primary" />`) express semantic color intent inside that theme
113
- > and are independent of `colorTone`.
114
-
115
111
  `mode` and `themeId` are available on public ZORA components through `ZoraBaseProps`.
116
112
  Use component props for local component/subtree overrides.
117
113
 
@@ -1372,7 +1368,13 @@ Pass your current theme as `value` and handle updates through `onChange`. Wrap b
1372
1368
  in a `ZoraProvider` so the preview area reflects every change immediately.
1373
1369
 
1374
1370
  ```tsx
1375
- const [theme, setTheme] = React.useState<ZoraTheme>(zoraDefaultTheme);
1371
+ const [theme, setTheme] = React.useState<ZoraTheme>({
1372
+ id: 'my-app',
1373
+ name: 'My App',
1374
+ appCategory: 'developer_tools',
1375
+ primaryColor: '#0f766e',
1376
+ harmony: 'analogous',
1377
+ });
1376
1378
  const [mode, setMode] = React.useState<ZoraThemeMode>('light');
1377
1379
 
1378
1380
  return (
@@ -1393,14 +1395,15 @@ return (
1393
1395
 
1394
1396
  ZORA props:
1395
1397
 
1396
- | Prop | Type | Default | Notes |
1397
- | -------------- | ------------------------------- | ------- | ---------------------------------------------------- |
1398
- | `value` | `ZoraTheme` | - | Required controlled theme seed. |
1399
- | `onChange` | `(theme: ZoraTheme) => void` | - | Required. Fires on every valid change. |
1400
- | `mode` | `ZoraThemeMode` | - | Current light/dark mode shown in the mode toggle. |
1401
- | `onModeChange` | `(mode: ZoraThemeMode) => void` | - | Called when the user switches the mode toggle. |
1402
- | `onSubmit` | `(theme: ZoraTheme) => void` | - | Optional. Renders an "Apply theme" button if set. |
1403
- | `testID` | `string` | - | Forwarded to the root element and child test points. |
1398
+ | Prop | Type | Default | Notes |
1399
+ | --------------- | ------------------------------- | ---------------- | ---------------------------------------------------- |
1400
+ | `value` | `ZoraTheme` | - | Required controlled theme seed. |
1401
+ | `onChange` | `(theme: ZoraTheme) => void` | - | Required. Fires on every valid change. |
1402
+ | `mode` | `ZoraThemeMode` | - | Current light/dark mode shown in the mode toggle. |
1403
+ | `onModeChange` | `(mode: ZoraThemeMode) => void` | - | Called when the user switches the mode toggle. |
1404
+ | `onSubmit` | `(theme: ZoraTheme) => void` | - | Optional. Renders an "Apply theme" button if set. |
1405
+ | `appCategories` | `readonly AppCategory[]` | `APP_CATEGORIES` | Optional override for the app category options list. |
1406
+ | `testID` | `string` | - | Forwarded to the root element and child test points. |
1404
1407
 
1405
1408
  </details>
1406
1409
 
@@ -1425,9 +1428,9 @@ Pass a theme seed to define your app theme:
1425
1428
  theme={{
1426
1429
  id: 'studio',
1427
1430
  name: 'Studio',
1431
+ appCategory: 'developer_tools',
1428
1432
  primaryColor: '#0f766e',
1429
1433
  harmony: 'analogous',
1430
- colorTone: 'jewel',
1431
1434
  }}
1432
1435
  >
1433
1436
  <App />
@@ -1453,15 +1456,17 @@ No inherited props. `ZoraProviderProps` is declared directly by ZORA.
1453
1456
 
1454
1457
  ### `createZoraThemeConfig`
1455
1458
 
1456
- Creates a Surface-compatible `ThemeConfig` from a ZORA theme seed.
1459
+ Creates a `ThemeConfig` (from `@ankhorage/contracts`) from a ZORA theme seed.
1460
+ The primary color is preserved identically for both light and dark mode configs.
1461
+ Color generation is handled downstream by `@ankhorage/color-theory` via Surface.
1457
1462
 
1458
1463
  ```tsx
1459
1464
  const themeConfig = createZoraThemeConfig({
1460
1465
  id: 'studio',
1461
1466
  name: 'Studio',
1467
+ appCategory: 'developer_tools',
1462
1468
  primaryColor: '#0f766e',
1463
1469
  harmony: 'analogous',
1464
- colorTone: 'jewel',
1465
1470
  });
1466
1471
  ```
1467
1472
 
@@ -1485,9 +1490,9 @@ Default ZORA theme seed.
1485
1490
  const zoraDefaultTheme: ZoraTheme = {
1486
1491
  id: 'zora',
1487
1492
  name: 'ZORA',
1493
+ appCategory: 'developer_tools',
1488
1494
  primaryColor: '#0f766e',
1489
1495
  harmony: 'analogous',
1490
- colorTone: 'jewel',
1491
1496
  };
1492
1497
  ```
1493
1498
 
@@ -1,4 +1,4 @@
1
- import type { AnkhTheme } from '@ankhorage/surface';
1
+ import type { SurfaceTheme } from '@ankhorage/surface';
2
2
  import type { TextStyle } from 'react-native';
3
3
  import type { HeadingAlign, HeadingLevel, HeadingSize, HeadingTone, HeadingWeight } from './types';
4
4
  interface ResolveHeadingRecipeOptions {
@@ -10,6 +10,6 @@ interface ResolveHeadingRecipeOptions {
10
10
  italic?: boolean;
11
11
  }
12
12
  export declare function resolveHeadingSizeFromLevel(level: HeadingLevel): HeadingSize;
13
- export declare function resolveHeadingRecipe(theme: AnkhTheme, { align, italic, level, size, tone, weight }: ResolveHeadingRecipeOptions): TextStyle;
13
+ export declare function resolveHeadingRecipe(theme: SurfaceTheme, { align, italic, level, size, tone, weight }: ResolveHeadingRecipeOptions): TextStyle;
14
14
  export {};
15
15
  //# sourceMappingURL=resolveHeadingRecipe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveHeadingRecipe.d.ts","sourceRoot":"","sources":["../../../src/components/heading/resolveHeadingRecipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnG,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAQD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,CAe5E;AA6ED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,EAChB,EAAE,KAAK,EAAE,MAAc,EAAE,KAAK,EAAE,IAAI,EAAE,IAAgB,EAAE,MAAM,EAAE,EAAE,2BAA2B,GAC5F,SAAS,CAcX"}
1
+ {"version":3,"file":"resolveHeadingRecipe.d.ts","sourceRoot":"","sources":["../../../src/components/heading/resolveHeadingRecipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnG,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAQD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,CAe5E;AA6ED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,EAAE,KAAK,EAAE,MAAc,EAAE,KAAK,EAAE,IAAI,EAAE,IAAgB,EAAE,MAAM,EAAE,EAAE,2BAA2B,GAC5F,SAAS,CAcX"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveHeadingRecipe.js","sourceRoot":"","sources":["../../../src/components/heading/resolveHeadingRecipe.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnkhTheme, FontWeight } from '@ankhorage/surface';\nimport type { TextStyle } from 'react-native';\n\nimport type { HeadingAlign, HeadingLevel, HeadingSize, HeadingTone, HeadingWeight } from './types';\n\ninterface ResolveHeadingRecipeOptions {\n level: HeadingLevel;\n size?: HeadingSize;\n tone?: HeadingTone;\n align?: HeadingAlign;\n weight?: HeadingWeight;\n italic?: boolean;\n}\n\ninterface HeadingRecipe {\n fontSize: number;\n lineHeight: number;\n weight: HeadingWeight;\n}\n\nexport function resolveHeadingSizeFromLevel(level: HeadingLevel): HeadingSize {\n switch (level) {\n case 1:\n return 'h1';\n case 2:\n return 'h2';\n case 3:\n return 'h3';\n case 4:\n return 'h4';\n case 5:\n return 'h5';\n case 6:\n return 'h6';\n }\n}\n\nfunction resolveHeadingLevelFromSize(size: Exclude<HeadingSize, 'display'>): HeadingLevel {\n switch (size) {\n case 'h1':\n return 1;\n case 'h2':\n return 2;\n case 'h3':\n return 3;\n case 'h4':\n return 4;\n case 'h5':\n return 5;\n case 'h6':\n return 6;\n }\n}\n\nfunction resolveSizeRecipe(theme: AnkhTheme, size: HeadingSize): HeadingRecipe {\n if (size === 'display') {\n const fontSize = theme.typography.sizes['3xl'];\n\n return {\n fontSize,\n lineHeight: fontSize + 8,\n weight: 'bold',\n };\n }\n\n const heading = theme.typography.headings[resolveHeadingLevelFromSize(size)];\n\n return {\n fontSize: heading.size,\n lineHeight: heading.lineHeight,\n weight: heading.weight,\n };\n}\n\nfunction resolveToneColor(theme: AnkhTheme, tone: HeadingTone): string {\n switch (tone) {\n case 'muted':\n return theme.semantics.content.muted;\n case 'subtle':\n return theme.semantics.content.subtle;\n case 'inverse':\n return theme.semantics.content.inverse;\n case 'primary':\n return theme.semantics.brand.base;\n case 'danger':\n return theme.semantics.danger.base;\n case 'success':\n return theme.semantics.success.base;\n case 'warning':\n return theme.semantics.warning.base;\n case 'default':\n default:\n return theme.semantics.content.default;\n }\n}\n\nfunction resolveWeight(theme: AnkhTheme, weight: HeadingWeight): FontWeight {\n return theme.typography.weights[weight];\n}\n\nfunction resolveFontFamily({\n theme,\n weight,\n italic,\n}: {\n theme: AnkhTheme;\n weight: FontWeight;\n italic: boolean;\n}): string | undefined {\n return theme.typography.fonts[italic ? 'italic' : 'normal'][weight];\n}\n\nexport function resolveHeadingRecipe(\n theme: AnkhTheme,\n { align, italic = false, level, size, tone = 'default', weight }: ResolveHeadingRecipeOptions,\n): TextStyle {\n const recipe = resolveSizeRecipe(theme, size ?? resolveHeadingSizeFromLevel(level));\n const resolvedWeight = resolveWeight(theme, weight ?? recipe.weight);\n\n return {\n color: resolveToneColor(theme, tone),\n elevation: 0,\n fontFamily: resolveFontFamily({ theme, weight: resolvedWeight, italic }),\n fontSize: recipe.fontSize,\n fontStyle: italic ? 'italic' : 'normal',\n fontWeight: resolvedWeight,\n lineHeight: recipe.lineHeight,\n textAlign: align,\n };\n}\n"]}
1
+ {"version":3,"file":"resolveHeadingRecipe.js","sourceRoot":"","sources":["../../../src/components/heading/resolveHeadingRecipe.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FontWeight, SurfaceTheme } from '@ankhorage/surface';\nimport type { TextStyle } from 'react-native';\n\nimport type { HeadingAlign, HeadingLevel, HeadingSize, HeadingTone, HeadingWeight } from './types';\n\ninterface ResolveHeadingRecipeOptions {\n level: HeadingLevel;\n size?: HeadingSize;\n tone?: HeadingTone;\n align?: HeadingAlign;\n weight?: HeadingWeight;\n italic?: boolean;\n}\n\ninterface HeadingRecipe {\n fontSize: number;\n lineHeight: number;\n weight: HeadingWeight;\n}\n\nexport function resolveHeadingSizeFromLevel(level: HeadingLevel): HeadingSize {\n switch (level) {\n case 1:\n return 'h1';\n case 2:\n return 'h2';\n case 3:\n return 'h3';\n case 4:\n return 'h4';\n case 5:\n return 'h5';\n case 6:\n return 'h6';\n }\n}\n\nfunction resolveHeadingLevelFromSize(size: Exclude<HeadingSize, 'display'>): HeadingLevel {\n switch (size) {\n case 'h1':\n return 1;\n case 'h2':\n return 2;\n case 'h3':\n return 3;\n case 'h4':\n return 4;\n case 'h5':\n return 5;\n case 'h6':\n return 6;\n }\n}\n\nfunction resolveSizeRecipe(theme: SurfaceTheme, size: HeadingSize): HeadingRecipe {\n if (size === 'display') {\n const fontSize = theme.typography.sizes['3xl'];\n\n return {\n fontSize,\n lineHeight: fontSize + 8,\n weight: 'bold',\n };\n }\n\n const heading = theme.typography.headings[resolveHeadingLevelFromSize(size)];\n\n return {\n fontSize: heading.size,\n lineHeight: heading.lineHeight,\n weight: heading.weight,\n };\n}\n\nfunction resolveToneColor(theme: SurfaceTheme, tone: HeadingTone): string {\n switch (tone) {\n case 'muted':\n return theme.semantics.content.muted;\n case 'subtle':\n return theme.semantics.content.subtle;\n case 'inverse':\n return theme.semantics.content.inverse;\n case 'primary':\n return theme.semantics.brand.base;\n case 'danger':\n return theme.semantics.danger.base;\n case 'success':\n return theme.semantics.success.base;\n case 'warning':\n return theme.semantics.warning.base;\n case 'default':\n default:\n return theme.semantics.content.default;\n }\n}\n\nfunction resolveWeight(theme: SurfaceTheme, weight: HeadingWeight): FontWeight {\n return theme.typography.weights[weight];\n}\n\nfunction resolveFontFamily({\n theme,\n weight,\n italic,\n}: {\n theme: SurfaceTheme;\n weight: FontWeight;\n italic: boolean;\n}): string | undefined {\n return theme.typography.fonts[italic ? 'italic' : 'normal'][weight];\n}\n\nexport function resolveHeadingRecipe(\n theme: SurfaceTheme,\n { align, italic = false, level, size, tone = 'default', weight }: ResolveHeadingRecipeOptions,\n): TextStyle {\n const recipe = resolveSizeRecipe(theme, size ?? resolveHeadingSizeFromLevel(level));\n const resolvedWeight = resolveWeight(theme, weight ?? recipe.weight);\n\n return {\n color: resolveToneColor(theme, tone),\n elevation: 0,\n fontFamily: resolveFontFamily({ theme, weight: resolvedWeight, italic }),\n fontSize: recipe.fontSize,\n fontStyle: italic ? 'italic' : 'normal',\n fontWeight: resolvedWeight,\n lineHeight: recipe.lineHeight,\n textAlign: align,\n };\n}\n"]}
@@ -1,8 +1,8 @@
1
- import { type AnkhTheme, type Breakpoint, type Responsive } from '@ankhorage/surface';
1
+ import { type Breakpoint, type Responsive, type SurfaceTheme } from '@ankhorage/surface';
2
2
  import type { TextStyle } from 'react-native';
3
3
  import type { TextAlign, TextTone, TextVariant, TextWeight } from './types';
4
4
  interface ResolveTextStyleOptions {
5
- theme: AnkhTheme;
5
+ theme: SurfaceTheme;
6
6
  breakpoint: Breakpoint;
7
7
  variant?: Responsive<TextVariant>;
8
8
  tone?: Responsive<TextTone>;
@@ -1 +1 @@
1
- {"version":3,"file":"resolveTextRecipe.d.ts","sourceRoot":"","sources":["../../../src/components/text/resolveTextRecipe.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EAGf,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAU5E,UAAU,uBAAuB;IAC/B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0GD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,UAAU,EACV,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,MAAc,GACf,EAAE,uBAAuB,GAAG,SAAS,CA2BrC"}
1
+ {"version":3,"file":"resolveTextRecipe.d.ts","sourceRoot":"","sources":["../../../src/components/text/resolveTextRecipe.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAU5E,UAAU,uBAAuB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0GD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,UAAU,EACV,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,MAAc,GACf,EAAE,uBAAuB,GAAG,SAAS,CA2BrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveTextRecipe.js","sourceRoot":"","sources":["../../../src/components/text/resolveTextRecipe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AnkhTheme,\n type Breakpoint,\n type FontWeight,\n resolveResponsive,\n type Responsive,\n} from '@ankhorage/surface';\nimport type { TextStyle } from 'react-native';\n\nimport type { TextAlign, TextTone, TextVariant, TextWeight } from './types';\n\ninterface VariantRecipe {\n fontSize: number;\n lineHeight: number;\n weight: TextWeight;\n textTransform?: TextStyle['textTransform'];\n letterSpacing?: number;\n}\n\ninterface ResolveTextStyleOptions {\n theme: AnkhTheme;\n breakpoint: Breakpoint;\n variant?: Responsive<TextVariant>;\n tone?: Responsive<TextTone>;\n weight?: Responsive<TextWeight>;\n align?: Responsive<TextAlign>;\n italic?: boolean;\n}\n\nfunction isMediumBreakpointOrLarger(breakpoint: Breakpoint): boolean {\n return breakpoint === 'md' || breakpoint === 'lg' || breakpoint === 'xl';\n}\n\nfunction resolveWeight(theme: AnkhTheme, weight: TextWeight): FontWeight {\n return theme.typography.weights[weight];\n}\n\nfunction resolveFontFamily({\n theme,\n variant,\n weight,\n italic,\n}: {\n theme: AnkhTheme;\n variant: TextVariant;\n weight: FontWeight;\n italic: boolean;\n}): string | undefined {\n if (variant === 'code') {\n return 'monospace';\n }\n\n return theme.typography.fonts[italic ? 'italic' : 'normal'][weight];\n}\n\nfunction resolveVariantRecipe(\n theme: AnkhTheme,\n variant: TextVariant,\n breakpoint: Breakpoint,\n): VariantRecipe {\n switch (variant) {\n case 'lead':\n return {\n fontSize: isMediumBreakpointOrLarger(breakpoint)\n ? theme.typography.sizes.l\n : theme.typography.sizes.m,\n lineHeight: isMediumBreakpointOrLarger(breakpoint) ? 28 : 24,\n weight: 'regular',\n };\n case 'bodySmall':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 20,\n weight: 'regular',\n };\n case 'caption':\n return {\n fontSize: theme.typography.sizes.xs,\n lineHeight: 16,\n weight: 'regular',\n };\n case 'label':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 18,\n weight: 'medium',\n };\n case 'eyebrow':\n return {\n fontSize: theme.typography.sizes.xs,\n lineHeight: 16,\n weight: 'semiBold',\n textTransform: 'uppercase',\n letterSpacing: 0.8,\n };\n case 'code':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 20,\n weight: 'regular',\n };\n case 'body':\n default:\n return {\n fontSize: theme.typography.sizes.m,\n lineHeight: 24,\n weight: 'regular',\n };\n }\n}\n\nfunction resolveToneColor(theme: AnkhTheme, tone: TextTone): string {\n switch (tone) {\n case 'muted':\n return theme.semantics.content.muted;\n case 'subtle':\n return theme.semantics.content.subtle;\n case 'inverse':\n return theme.semantics.content.inverse;\n case 'primary':\n return theme.semantics.brand.base;\n case 'danger':\n return theme.semantics.danger.base;\n case 'success':\n return theme.semantics.success.base;\n case 'warning':\n return theme.semantics.warning.base;\n case 'default':\n default:\n return theme.semantics.content.default;\n }\n}\n\nexport function resolveTextStyle({\n theme,\n breakpoint,\n variant,\n tone,\n weight,\n align,\n italic = false,\n}: ResolveTextStyleOptions): TextStyle {\n const resolvedVariant = resolveResponsive(variant, breakpoint) ?? 'body';\n const resolvedTone = resolveResponsive(tone, breakpoint) ?? 'default';\n const resolvedAlign = resolveResponsive(align, breakpoint);\n const recipe = resolveVariantRecipe(theme, resolvedVariant, breakpoint);\n const resolvedWeight = resolveWeight(\n theme,\n resolveResponsive(weight, breakpoint) ?? recipe.weight,\n );\n\n return {\n color: resolveToneColor(theme, resolvedTone),\n elevation: 0,\n fontFamily: resolveFontFamily({\n theme,\n variant: resolvedVariant,\n weight: resolvedWeight,\n italic,\n }),\n fontSize: recipe.fontSize,\n fontStyle: italic ? 'italic' : 'normal',\n fontWeight: resolvedWeight,\n letterSpacing: recipe.letterSpacing,\n lineHeight: recipe.lineHeight,\n textAlign: resolvedAlign,\n textTransform: recipe.textTransform,\n };\n}\n"]}
1
+ {"version":3,"file":"resolveTextRecipe.js","sourceRoot":"","sources":["../../../src/components/text/resolveTextRecipe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Breakpoint,\n type FontWeight,\n resolveResponsive,\n type Responsive,\n type SurfaceTheme,\n} from '@ankhorage/surface';\nimport type { TextStyle } from 'react-native';\n\nimport type { TextAlign, TextTone, TextVariant, TextWeight } from './types';\n\ninterface VariantRecipe {\n fontSize: number;\n lineHeight: number;\n weight: TextWeight;\n textTransform?: TextStyle['textTransform'];\n letterSpacing?: number;\n}\n\ninterface ResolveTextStyleOptions {\n theme: SurfaceTheme;\n breakpoint: Breakpoint;\n variant?: Responsive<TextVariant>;\n tone?: Responsive<TextTone>;\n weight?: Responsive<TextWeight>;\n align?: Responsive<TextAlign>;\n italic?: boolean;\n}\n\nfunction isMediumBreakpointOrLarger(breakpoint: Breakpoint): boolean {\n return breakpoint === 'md' || breakpoint === 'lg' || breakpoint === 'xl';\n}\n\nfunction resolveWeight(theme: SurfaceTheme, weight: TextWeight): FontWeight {\n return theme.typography.weights[weight];\n}\n\nfunction resolveFontFamily({\n theme,\n variant,\n weight,\n italic,\n}: {\n theme: SurfaceTheme;\n variant: TextVariant;\n weight: FontWeight;\n italic: boolean;\n}): string | undefined {\n if (variant === 'code') {\n return 'monospace';\n }\n\n return theme.typography.fonts[italic ? 'italic' : 'normal'][weight];\n}\n\nfunction resolveVariantRecipe(\n theme: SurfaceTheme,\n variant: TextVariant,\n breakpoint: Breakpoint,\n): VariantRecipe {\n switch (variant) {\n case 'lead':\n return {\n fontSize: isMediumBreakpointOrLarger(breakpoint)\n ? theme.typography.sizes.l\n : theme.typography.sizes.m,\n lineHeight: isMediumBreakpointOrLarger(breakpoint) ? 28 : 24,\n weight: 'regular',\n };\n case 'bodySmall':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 20,\n weight: 'regular',\n };\n case 'caption':\n return {\n fontSize: theme.typography.sizes.xs,\n lineHeight: 16,\n weight: 'regular',\n };\n case 'label':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 18,\n weight: 'medium',\n };\n case 'eyebrow':\n return {\n fontSize: theme.typography.sizes.xs,\n lineHeight: 16,\n weight: 'semiBold',\n textTransform: 'uppercase',\n letterSpacing: 0.8,\n };\n case 'code':\n return {\n fontSize: theme.typography.sizes.s,\n lineHeight: 20,\n weight: 'regular',\n };\n case 'body':\n default:\n return {\n fontSize: theme.typography.sizes.m,\n lineHeight: 24,\n weight: 'regular',\n };\n }\n}\n\nfunction resolveToneColor(theme: SurfaceTheme, tone: TextTone): string {\n switch (tone) {\n case 'muted':\n return theme.semantics.content.muted;\n case 'subtle':\n return theme.semantics.content.subtle;\n case 'inverse':\n return theme.semantics.content.inverse;\n case 'primary':\n return theme.semantics.brand.base;\n case 'danger':\n return theme.semantics.danger.base;\n case 'success':\n return theme.semantics.success.base;\n case 'warning':\n return theme.semantics.warning.base;\n case 'default':\n default:\n return theme.semantics.content.default;\n }\n}\n\nexport function resolveTextStyle({\n theme,\n breakpoint,\n variant,\n tone,\n weight,\n align,\n italic = false,\n}: ResolveTextStyleOptions): TextStyle {\n const resolvedVariant = resolveResponsive(variant, breakpoint) ?? 'body';\n const resolvedTone = resolveResponsive(tone, breakpoint) ?? 'default';\n const resolvedAlign = resolveResponsive(align, breakpoint);\n const recipe = resolveVariantRecipe(theme, resolvedVariant, breakpoint);\n const resolvedWeight = resolveWeight(\n theme,\n resolveResponsive(weight, breakpoint) ?? recipe.weight,\n );\n\n return {\n color: resolveToneColor(theme, resolvedTone),\n elevation: 0,\n fontFamily: resolveFontFamily({\n theme,\n variant: resolvedVariant,\n weight: resolvedWeight,\n italic,\n }),\n fontSize: recipe.fontSize,\n fontStyle: italic ? 'italic' : 'normal',\n fontWeight: resolvedWeight,\n letterSpacing: recipe.letterSpacing,\n lineHeight: recipe.lineHeight,\n textAlign: resolvedAlign,\n textTransform: recipe.textTransform,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeComposer.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/ThemeComposer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAwB1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAyRlD,eAAO,MAAM,aAAa,0DAAyC,CAAC"}
1
+ {"version":3,"file":"ThemeComposer.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/ThemeComposer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAmSlD,eAAO,MAAM,aAAa,0DAAyC,CAAC"}
@@ -1,3 +1,5 @@
1
+ import { COLOR_HARMONIES, parseHexColorOrThrow } from '@ankhorage/color-theory';
2
+ import { APP_CATEGORIES } from '@ankhorage/contracts';
1
3
  import { Box, Stack } from '@ankhorage/surface';
2
4
  import React from 'react';
3
5
  import { Badge } from '../../components/badge';
@@ -8,53 +10,55 @@ import { Input } from '../../components/input';
8
10
  import { Select } from '../../components/select';
9
11
  import { Tabs } from '../../components/tabs';
10
12
  import { Text } from '../../components/text';
11
- import { ZORA_COLOR_HARMONIES, ZORA_COLOR_TONES, } from '../../theme/types';
12
13
  import { useZoraTheme } from '../../theme/useZoraTheme';
13
14
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
14
- import { createThemeFromThemeComposerRecommendation, findThemeComposerRecommendation, formatThemeComposerLabel, } from './recommendations';
15
- const HEX_RE = /^#[0-9A-Fa-f]{6}$/;
15
+ const HEX_ERROR_MESSAGE = 'Enter a valid 6-digit hex color (e.g. #0f766e).';
16
+ const NAME_ERROR_MESSAGE = 'Theme name cannot be empty.';
16
17
  function isValidHex(value) {
17
- return HEX_RE.test(value);
18
+ try {
19
+ parseHexColorOrThrow(value);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function formatAppCategoryLabel(category) {
27
+ return category
28
+ .split('_')
29
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
30
+ .join(' ');
18
31
  }
19
- const HARMONY_OPTIONS = ZORA_COLOR_HARMONIES.map((h) => ({ value: h, label: h }));
20
- const TONE_OPTIONS = ZORA_COLOR_TONES.map((t) => ({ value: t, label: t }));
32
+ const HARMONY_OPTIONS = COLOR_HARMONIES.map((h) => ({ value: h, label: h }));
21
33
  const MODE_TABS = [
22
34
  { value: 'light', label: 'Light' },
23
35
  { value: 'dark', label: 'Dark' },
24
36
  ];
25
- const COLOR_TONE_BACKGROUND_TONE = {
26
- neutral: 'Neutral',
27
- pastel: 'Pastel',
28
- earth: 'Earth',
29
- mineral: 'Mineral',
30
- muted: 'Muted',
31
- jewel: 'Neutral',
32
- fluorescent: 'Obsidian',
33
- obsidian: 'Obsidian',
34
- vaporwave: 'Pastel',
35
- monochromeAccent: 'Neutral',
36
- };
37
- const COLOR_TONE_FOREGROUND_TONE = {
38
- neutral: 'Jewel',
39
- pastel: 'Jewel',
40
- earth: 'Mineral',
41
- mineral: 'Jewel',
42
- muted: 'Jewel',
43
- jewel: 'Jewel',
44
- fluorescent: 'Fluorescent',
45
- obsidian: 'Fluorescent',
46
- vaporwave: 'Fluorescent',
47
- monochromeAccent: 'Jewel',
48
- };
49
- function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeChange, onSubmit, appCategory, appMood, recommendations, testID, }) {
37
+ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeChange, onSubmit, appCategories, testID, }) {
50
38
  const { theme } = useZoraTheme();
51
39
  const [hexInput, setHexInput] = React.useState(value.primaryColor);
52
40
  const [hexError, setHexError] = React.useState(undefined);
53
- // Keep local hex input in sync when value.primaryColor changes externally
41
+ const [nameInput, setNameInput] = React.useState(value.name);
42
+ const [nameError, setNameError] = React.useState(undefined);
43
+ // Keep local inputs in sync when value changes externally
54
44
  React.useEffect(() => {
55
45
  setHexInput(value.primaryColor);
56
46
  setHexError(undefined);
57
47
  }, [value.primaryColor]);
48
+ React.useEffect(() => {
49
+ setNameInput(value.name);
50
+ setNameError(undefined);
51
+ }, [value.name]);
52
+ function handleNameChange(text) {
53
+ setNameInput(text);
54
+ if (text.trim().length === 0) {
55
+ setNameError(NAME_ERROR_MESSAGE);
56
+ }
57
+ else {
58
+ setNameError(undefined);
59
+ onChange({ ...value, name: text });
60
+ }
61
+ }
58
62
  function handleHexChange(text) {
59
63
  // Ensure leading hash
60
64
  const normalized = text.startsWith('#') ? text : `#${text}`;
@@ -64,44 +68,56 @@ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeCh
64
68
  onChange({ ...value, primaryColor: normalized });
65
69
  }
66
70
  else {
67
- setHexError('Enter a valid 6-digit hex color (e.g. #0f766e).');
71
+ setHexError(HEX_ERROR_MESSAGE);
72
+ }
73
+ }
74
+ function handleSubmit() {
75
+ const hasValidName = nameInput.trim().length > 0;
76
+ const hasValidHex = isValidHex(hexInput);
77
+ if (!hasValidName) {
78
+ setNameError(NAME_ERROR_MESSAGE);
79
+ }
80
+ if (!hasValidHex) {
81
+ setHexError(HEX_ERROR_MESSAGE);
82
+ }
83
+ if (!hasValidName || !hasValidHex) {
84
+ return;
68
85
  }
86
+ onSubmit?.({
87
+ ...value,
88
+ name: nameInput,
89
+ primaryColor: hexInput,
90
+ });
69
91
  }
70
92
  const activeMode = mode ?? 'light';
71
- const recommendation = findThemeComposerRecommendation({
72
- appCategory,
73
- appMood,
74
- recommendations,
75
- });
93
+ const categoryOptions = (appCategories ?? APP_CATEGORIES).map((c) => ({
94
+ value: c,
95
+ label: formatAppCategoryLabel(c),
96
+ }));
76
97
  return (<Stack gap="l" testID={testID}>
77
- {recommendation ? (<Card title="Recommended starting point" description="Use this as an optional starting point. It is only applied when you choose it." actions={<Button size="s" emphasis="soft" tone="primary" onPress={() => onChange(createThemeFromThemeComposerRecommendation({
78
- value,
79
- recommendation,
80
- }))} testID={testID ? `${testID}-apply-recommendation` : undefined}>
81
- Apply recommendation
82
- </Button>}>
83
- <Stack gap="s">
84
- <Stack direction="row" gap="s" wrap="wrap">
85
- <Badge tone="primary" emphasis="soft">
86
- {formatThemeComposerLabel(recommendation.appMood)} mood
87
- </Badge>
88
- <Badge tone="neutral" emphasis="soft">
89
- {formatThemeComposerLabel(recommendation.suggestedColorTone)} color tone
90
- </Badge>
91
- <Badge tone="neutral" emphasis="soft">
92
- {formatThemeComposerLabel(recommendation.suggestedHarmony)} harmony
93
- </Badge>
94
- {recommendation.suggestedPrimaryHueDegrees === undefined ? null : (<Badge tone="neutral" emphasis="soft">
95
- {recommendation.suggestedPrimaryHueDegrees}° hue
96
- </Badge>)}
97
- </Stack>
98
- <Text tone="muted" variant="bodySmall">
99
- Suggested for {formatThemeComposerLabel(recommendation.appCategory)}. The color tone
100
- controls palette character, harmony controls accent relationships, and hue sets the
101
- starting primary color when available.
98
+ {/* Section: Theme identity */}
99
+ <Card title="Theme identity" description="Name your theme. The ID is assigned automatically and shown for reference.">
100
+ <Stack gap="m">
101
+ <Stack gap="xs">
102
+ <Text variant="label">Name</Text>
103
+ <Input value={nameInput} onChangeText={handleNameChange} placeholder="My theme" autoCorrect={false} invalid={nameError !== undefined} testID={testID ? `${testID}-name-input` : undefined}/>
104
+ {nameError ? (<Text tone="danger" variant="bodySmall">
105
+ {nameError}
106
+ </Text>) : null}
107
+ </Stack>
108
+ <Stack gap="xs">
109
+ <Text variant="label">ID</Text>
110
+ <Text tone="muted" variant="bodySmall" testID={testID ? `${testID}-id-display` : undefined}>
111
+ {value.id}
102
112
  </Text>
103
113
  </Stack>
104
- </Card>) : null}
114
+ </Stack>
115
+ </Card>
116
+
117
+ {/* Section: App category */}
118
+ <Card title="App category" description="Choose the category that best describes this app.">
119
+ <Select value={value.appCategory} options={categoryOptions} onValueChange={(c) => onChange({ ...value, appCategory: c })} testID={testID ? `${testID}-category-select` : undefined}/>
120
+ </Card>
105
121
 
106
122
  {/* Section: Primary Color */}
107
123
  <Card title="Primary color" description="Set the seed color for your theme palette.">
@@ -128,27 +144,6 @@ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeCh
128
144
  <Select value={value.harmony} options={HARMONY_OPTIONS} onValueChange={(h) => onChange({ ...value, harmony: h })} testID={testID ? `${testID}-harmony-select` : undefined}/>
129
145
  </Card>
130
146
 
131
- {/* Section: Color tone */}
132
- <Card title="Color tone" description="Controls the vibrancy and saturation style of the palette.">
133
- <Stack gap="s">
134
- <Select value={value.colorTone} options={TONE_OPTIONS} onValueChange={(t) => onChange({ ...value, colorTone: t })} testID={testID ? `${testID}-tone-select` : undefined}/>
135
- <Stack direction="row" gap="s" align="center">
136
- <Text tone="muted" variant="caption">
137
- Background:
138
- </Text>
139
- <Badge tone="neutral" emphasis="soft">
140
- {COLOR_TONE_BACKGROUND_TONE[value.colorTone]}
141
- </Badge>
142
- <Text tone="muted" variant="caption">
143
- Foreground:
144
- </Text>
145
- <Badge tone="neutral" emphasis="soft">
146
- {COLOR_TONE_FOREGROUND_TONE[value.colorTone]}
147
- </Badge>
148
- </Stack>
149
- </Stack>
150
- </Card>
151
-
152
147
  {/* Section: Mode */}
153
148
  <Card title="Mode" description="Switch between light and dark presentation.">
154
149
  <Tabs value={activeMode} items={MODE_TABS} onValueChange={(m) => onModeChange?.(m)} variant="segmented" testID={testID ? `${testID}-mode-tabs` : undefined}/>
@@ -157,6 +152,26 @@ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeCh
157
152
  {/* Section: Preview */}
158
153
  <Card title="Preview" description="A quick look at how your theme renders common controls.">
159
154
  <Stack gap="m">
155
+ <Stack gap="xs">
156
+ <Text variant="label">Name</Text>
157
+ <Text>{value.name}</Text>
158
+ </Stack>
159
+ <Stack gap="xs">
160
+ <Text variant="label">Category</Text>
161
+ <Text>{formatAppCategoryLabel(value.appCategory)}</Text>
162
+ </Stack>
163
+ <Stack gap="xs">
164
+ <Text variant="label">Primary color</Text>
165
+ <Text tone="muted" variant="bodySmall">
166
+ {value.primaryColor}
167
+ </Text>
168
+ </Stack>
169
+ <Stack gap="xs">
170
+ <Text variant="label">Harmony</Text>
171
+ <Text tone="muted" variant="bodySmall">
172
+ {value.harmony}
173
+ </Text>
174
+ </Stack>
160
175
  <Heading level={4}>Heading</Heading>
161
176
  <Text>Body text — this shows default text color and weight.</Text>
162
177
  <Text tone="muted" variant="bodySmall">
@@ -190,7 +205,7 @@ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeCh
190
205
  </Card>
191
206
 
192
207
  {/* Submit */}
193
- {onSubmit ? (<Button tone="primary" emphasis="solid" onPress={() => onSubmit(value)} testID={testID ? `${testID}-submit` : undefined}>
208
+ {onSubmit ? (<Button tone="primary" emphasis="solid" onPress={handleSubmit} testID={testID ? `${testID}-submit` : undefined}>
194
209
  Apply theme
195
210
  </Button>) : null}
196
211
  </Stack>);