@ankhorage/zora 0.15.4 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0d09c8f: Update SURFACE
8
+
9
+ ## 0.16.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 5f95af4: Add optional ThemeComposer recommendation props and explicit recommendation application UI.
14
+
3
15
  ## 0.15.4
4
16
 
5
17
  ### Patch Changes
@@ -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;AAmB1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgOlD,eAAO,MAAM,aAAa,0DAAyC,CAAC"}
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"}
@@ -11,6 +11,7 @@ import { Text } from '../../components/text';
11
11
  import { ZORA_COLOR_HARMONIES, ZORA_COLOR_TONES, } from '../../theme/types';
12
12
  import { useZoraTheme } from '../../theme/useZoraTheme';
13
13
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
14
+ import { createThemeFromThemeComposerRecommendation, findThemeComposerRecommendation, formatThemeComposerLabel, } from './recommendations';
14
15
  const HEX_RE = /^#[0-9A-Fa-f]{6}$/;
15
16
  function isValidHex(value) {
16
17
  return HEX_RE.test(value);
@@ -45,7 +46,7 @@ const COLOR_TONE_FOREGROUND_TONE = {
45
46
  vaporwave: 'Fluorescent',
46
47
  monochromeAccent: 'Jewel',
47
48
  };
48
- function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeChange, onSubmit, testID, }) {
49
+ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeChange, onSubmit, appCategory, appMood, recommendations, testID, }) {
49
50
  const { theme } = useZoraTheme();
50
51
  const [hexInput, setHexInput] = React.useState(value.primaryColor);
51
52
  const [hexError, setHexError] = React.useState(undefined);
@@ -67,7 +68,41 @@ function ThemeComposerInner({ themeId: _themeId, value, onChange, mode, onModeCh
67
68
  }
68
69
  }
69
70
  const activeMode = mode ?? 'light';
71
+ const recommendation = findThemeComposerRecommendation({
72
+ appCategory,
73
+ appMood,
74
+ recommendations,
75
+ });
70
76
  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.
102
+ </Text>
103
+ </Stack>
104
+ </Card>) : null}
105
+
71
106
  {/* Section: Primary Color */}
72
107
  <Card title="Primary color" description="Set the seed color for your theme palette.">
73
108
  <Stack gap="m">
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeComposer.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/ThemeComposer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Box, Stack } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { Badge } from '../../components/badge';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { Heading } from '../../components/heading';\nimport { Input } from '../../components/input';\nimport { Select } from '../../components/select';\nimport { Tabs } from '../../components/tabs';\nimport { Text } from '../../components/text';\nimport {\n ZORA_COLOR_HARMONIES,\n ZORA_COLOR_TONES,\n type ZoraColorTone,\n type ZoraTheme,\n type ZoraThemeMode,\n} from '../../theme/types';\nimport { useZoraTheme } from '../../theme/useZoraTheme';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport type { ThemeComposerProps } from './types';\n\nconst HEX_RE = /^#[0-9A-Fa-f]{6}$/;\n\nfunction isValidHex(value: string): boolean {\n return HEX_RE.test(value);\n}\n\nconst HARMONY_OPTIONS = ZORA_COLOR_HARMONIES.map((h) => ({ value: h, label: h }));\n\nconst TONE_OPTIONS = ZORA_COLOR_TONES.map((t) => ({ value: t, label: t }));\n\nconst MODE_TABS = [\n { value: 'light' as ZoraThemeMode, label: 'Light' },\n { value: 'dark' as ZoraThemeMode, label: 'Dark' },\n];\n\nconst COLOR_TONE_BACKGROUND_TONE: Record<ZoraColorTone, string> = {\n neutral: 'Neutral',\n pastel: 'Pastel',\n earth: 'Earth',\n mineral: 'Mineral',\n muted: 'Muted',\n jewel: 'Neutral',\n fluorescent: 'Obsidian',\n obsidian: 'Obsidian',\n vaporwave: 'Pastel',\n monochromeAccent: 'Neutral',\n};\n\nconst COLOR_TONE_FOREGROUND_TONE: Record<ZoraColorTone, string> = {\n neutral: 'Jewel',\n pastel: 'Jewel',\n earth: 'Mineral',\n mineral: 'Jewel',\n muted: 'Jewel',\n jewel: 'Jewel',\n fluorescent: 'Fluorescent',\n obsidian: 'Fluorescent',\n vaporwave: 'Fluorescent',\n monochromeAccent: 'Jewel',\n};\n\nfunction ThemeComposerInner({\n themeId: _themeId,\n value,\n onChange,\n mode,\n onModeChange,\n onSubmit,\n testID,\n}: ThemeComposerProps) {\n const { theme } = useZoraTheme();\n\n const [hexInput, setHexInput] = React.useState<string>(value.primaryColor);\n const [hexError, setHexError] = React.useState<string | undefined>(undefined);\n\n // Keep local hex input in sync when value.primaryColor changes externally\n React.useEffect(() => {\n setHexInput(value.primaryColor);\n setHexError(undefined);\n }, [value.primaryColor]);\n\n function handleHexChange(text: string) {\n // Ensure leading hash\n const normalized = text.startsWith('#') ? text : `#${text}`;\n setHexInput(normalized);\n\n if (isValidHex(normalized)) {\n setHexError(undefined);\n onChange({ ...value, primaryColor: normalized as ZoraTheme['primaryColor'] });\n } else {\n setHexError('Enter a valid 6-digit hex color (e.g. #0f766e).');\n }\n }\n\n const activeMode = mode ?? 'light';\n\n return (\n <Stack gap=\"l\" testID={testID}>\n {/* Section: Primary Color */}\n <Card title=\"Primary color\" description=\"Set the seed color for your theme palette.\">\n <Stack gap=\"m\">\n <Stack direction=\"row\" gap=\"m\" align=\"center\">\n <Box flex={1}>\n <Input\n value={hexInput}\n onChangeText={handleHexChange}\n placeholder=\"#0f766e\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n maxLength={7}\n invalid={hexError !== undefined}\n testID={testID ? `${testID}-hex-input` : undefined}\n />\n </Box>\n {/* Color preview chip */}\n <Box\n width={36}\n height={36}\n radius=\"m\"\n style={{\n backgroundColor: isValidHex(hexInput) ? hexInput : theme.colors.border,\n borderWidth: 1,\n borderColor: theme.colors.border,\n }}\n />\n </Stack>\n {hexError ? (\n <Text tone=\"danger\" variant=\"bodySmall\">\n {hexError}\n </Text>\n ) : null}\n </Stack>\n </Card>\n\n {/* Section: Harmony */}\n <Card\n title=\"Harmony\"\n description=\"Choose how accent hues are generated from your primary color.\"\n >\n <Select\n value={value.harmony}\n options={HARMONY_OPTIONS}\n onValueChange={(h) => onChange({ ...value, harmony: h })}\n testID={testID ? `${testID}-harmony-select` : undefined}\n />\n </Card>\n\n {/* Section: Color tone */}\n <Card\n title=\"Color tone\"\n description=\"Controls the vibrancy and saturation style of the palette.\"\n >\n <Stack gap=\"s\">\n <Select\n value={value.colorTone}\n options={TONE_OPTIONS}\n onValueChange={(t) => onChange({ ...value, colorTone: t })}\n testID={testID ? `${testID}-tone-select` : undefined}\n />\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Text tone=\"muted\" variant=\"caption\">\n Background:\n </Text>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {COLOR_TONE_BACKGROUND_TONE[value.colorTone]}\n </Badge>\n <Text tone=\"muted\" variant=\"caption\">\n Foreground:\n </Text>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {COLOR_TONE_FOREGROUND_TONE[value.colorTone]}\n </Badge>\n </Stack>\n </Stack>\n </Card>\n\n {/* Section: Mode */}\n <Card title=\"Mode\" description=\"Switch between light and dark presentation.\">\n <Tabs\n value={activeMode}\n items={MODE_TABS}\n onValueChange={(m) => onModeChange?.(m)}\n variant=\"segmented\"\n testID={testID ? `${testID}-mode-tabs` : undefined}\n />\n </Card>\n\n {/* Section: Preview */}\n <Card title=\"Preview\" description=\"A quick look at how your theme renders common controls.\">\n <Stack gap=\"m\">\n <Heading level={4}>Heading</Heading>\n <Text>Body text — this shows default text color and weight.</Text>\n <Text tone=\"muted\" variant=\"bodySmall\">\n Muted caption text.\n </Text>\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Button tone=\"primary\" emphasis=\"solid\" size=\"m\">\n Primary\n </Button>\n <Button tone=\"neutral\" emphasis=\"soft\" size=\"m\">\n Neutral\n </Button>\n <Button tone=\"danger\" emphasis=\"ghost\" size=\"m\">\n Danger\n </Button>\n </Stack>\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Badge tone=\"primary\">Primary</Badge>\n <Badge tone=\"success\" emphasis=\"soft\">\n Success\n </Badge>\n <Badge tone=\"warning\" emphasis=\"soft\">\n Warning\n </Badge>\n <Badge tone=\"danger\" emphasis=\"soft\">\n Danger\n </Badge>\n </Stack>\n <Card\n tone=\"subtle\"\n title=\"Nested card\"\n description=\"Subtle tone inside the preview.\"\n compact\n />\n </Stack>\n </Card>\n\n {/* Submit */}\n {onSubmit ? (\n <Button\n tone=\"primary\"\n emphasis=\"solid\"\n onPress={() => onSubmit(value)}\n testID={testID ? `${testID}-submit` : undefined}\n >\n Apply theme\n </Button>\n ) : null}\n </Stack>\n );\n}\n\nexport const ThemeComposer = withZoraThemeScope(ThemeComposerInner);\n"]}
1
+ {"version":3,"file":"ThemeComposer.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/ThemeComposer.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Box, Stack } from '@ankhorage/surface';\nimport React from 'react';\n\nimport { Badge } from '../../components/badge';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { Heading } from '../../components/heading';\nimport { Input } from '../../components/input';\nimport { Select } from '../../components/select';\nimport { Tabs } from '../../components/tabs';\nimport { Text } from '../../components/text';\nimport {\n ZORA_COLOR_HARMONIES,\n ZORA_COLOR_TONES,\n type ZoraColorTone,\n type ZoraTheme,\n type ZoraThemeMode,\n} from '../../theme/types';\nimport { useZoraTheme } from '../../theme/useZoraTheme';\nimport { withZoraThemeScope } from '../../theme/withZoraThemeScope';\nimport {\n createThemeFromThemeComposerRecommendation,\n findThemeComposerRecommendation,\n formatThemeComposerLabel,\n} from './recommendations';\nimport type { ThemeComposerProps } from './types';\n\nconst HEX_RE = /^#[0-9A-Fa-f]{6}$/;\n\nfunction isValidHex(value: string): boolean {\n return HEX_RE.test(value);\n}\n\nconst HARMONY_OPTIONS = ZORA_COLOR_HARMONIES.map((h) => ({ value: h, label: h }));\n\nconst TONE_OPTIONS = ZORA_COLOR_TONES.map((t) => ({ value: t, label: t }));\n\nconst MODE_TABS = [\n { value: 'light' as ZoraThemeMode, label: 'Light' },\n { value: 'dark' as ZoraThemeMode, label: 'Dark' },\n];\n\nconst COLOR_TONE_BACKGROUND_TONE: Record<ZoraColorTone, string> = {\n neutral: 'Neutral',\n pastel: 'Pastel',\n earth: 'Earth',\n mineral: 'Mineral',\n muted: 'Muted',\n jewel: 'Neutral',\n fluorescent: 'Obsidian',\n obsidian: 'Obsidian',\n vaporwave: 'Pastel',\n monochromeAccent: 'Neutral',\n};\n\nconst COLOR_TONE_FOREGROUND_TONE: Record<ZoraColorTone, string> = {\n neutral: 'Jewel',\n pastel: 'Jewel',\n earth: 'Mineral',\n mineral: 'Jewel',\n muted: 'Jewel',\n jewel: 'Jewel',\n fluorescent: 'Fluorescent',\n obsidian: 'Fluorescent',\n vaporwave: 'Fluorescent',\n monochromeAccent: 'Jewel',\n};\n\nfunction ThemeComposerInner({\n themeId: _themeId,\n value,\n onChange,\n mode,\n onModeChange,\n onSubmit,\n appCategory,\n appMood,\n recommendations,\n testID,\n}: ThemeComposerProps) {\n const { theme } = useZoraTheme();\n\n const [hexInput, setHexInput] = React.useState<string>(value.primaryColor);\n const [hexError, setHexError] = React.useState<string | undefined>(undefined);\n\n // Keep local hex input in sync when value.primaryColor changes externally\n React.useEffect(() => {\n setHexInput(value.primaryColor);\n setHexError(undefined);\n }, [value.primaryColor]);\n\n function handleHexChange(text: string) {\n // Ensure leading hash\n const normalized = text.startsWith('#') ? text : `#${text}`;\n setHexInput(normalized);\n\n if (isValidHex(normalized)) {\n setHexError(undefined);\n onChange({ ...value, primaryColor: normalized as ZoraTheme['primaryColor'] });\n } else {\n setHexError('Enter a valid 6-digit hex color (e.g. #0f766e).');\n }\n }\n\n const activeMode = mode ?? 'light';\n const recommendation = findThemeComposerRecommendation({\n appCategory,\n appMood,\n recommendations,\n });\n\n return (\n <Stack gap=\"l\" testID={testID}>\n {recommendation ? (\n <Card\n title=\"Recommended starting point\"\n description=\"Use this as an optional starting point. It is only applied when you choose it.\"\n actions={\n <Button\n size=\"s\"\n emphasis=\"soft\"\n tone=\"primary\"\n onPress={() =>\n onChange(\n createThemeFromThemeComposerRecommendation({\n value,\n recommendation,\n }),\n )\n }\n testID={testID ? `${testID}-apply-recommendation` : undefined}\n >\n Apply recommendation\n </Button>\n }\n >\n <Stack gap=\"s\">\n <Stack direction=\"row\" gap=\"s\" wrap=\"wrap\">\n <Badge tone=\"primary\" emphasis=\"soft\">\n {formatThemeComposerLabel(recommendation.appMood)} mood\n </Badge>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {formatThemeComposerLabel(recommendation.suggestedColorTone)} color tone\n </Badge>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {formatThemeComposerLabel(recommendation.suggestedHarmony)} harmony\n </Badge>\n {recommendation.suggestedPrimaryHueDegrees === undefined ? null : (\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {recommendation.suggestedPrimaryHueDegrees}° hue\n </Badge>\n )}\n </Stack>\n <Text tone=\"muted\" variant=\"bodySmall\">\n Suggested for {formatThemeComposerLabel(recommendation.appCategory)}. The color tone\n controls palette character, harmony controls accent relationships, and hue sets the\n starting primary color when available.\n </Text>\n </Stack>\n </Card>\n ) : null}\n\n {/* Section: Primary Color */}\n <Card title=\"Primary color\" description=\"Set the seed color for your theme palette.\">\n <Stack gap=\"m\">\n <Stack direction=\"row\" gap=\"m\" align=\"center\">\n <Box flex={1}>\n <Input\n value={hexInput}\n onChangeText={handleHexChange}\n placeholder=\"#0f766e\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n maxLength={7}\n invalid={hexError !== undefined}\n testID={testID ? `${testID}-hex-input` : undefined}\n />\n </Box>\n {/* Color preview chip */}\n <Box\n width={36}\n height={36}\n radius=\"m\"\n style={{\n backgroundColor: isValidHex(hexInput) ? hexInput : theme.colors.border,\n borderWidth: 1,\n borderColor: theme.colors.border,\n }}\n />\n </Stack>\n {hexError ? (\n <Text tone=\"danger\" variant=\"bodySmall\">\n {hexError}\n </Text>\n ) : null}\n </Stack>\n </Card>\n\n {/* Section: Harmony */}\n <Card\n title=\"Harmony\"\n description=\"Choose how accent hues are generated from your primary color.\"\n >\n <Select\n value={value.harmony}\n options={HARMONY_OPTIONS}\n onValueChange={(h) => onChange({ ...value, harmony: h })}\n testID={testID ? `${testID}-harmony-select` : undefined}\n />\n </Card>\n\n {/* Section: Color tone */}\n <Card\n title=\"Color tone\"\n description=\"Controls the vibrancy and saturation style of the palette.\"\n >\n <Stack gap=\"s\">\n <Select\n value={value.colorTone}\n options={TONE_OPTIONS}\n onValueChange={(t) => onChange({ ...value, colorTone: t })}\n testID={testID ? `${testID}-tone-select` : undefined}\n />\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Text tone=\"muted\" variant=\"caption\">\n Background:\n </Text>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {COLOR_TONE_BACKGROUND_TONE[value.colorTone]}\n </Badge>\n <Text tone=\"muted\" variant=\"caption\">\n Foreground:\n </Text>\n <Badge tone=\"neutral\" emphasis=\"soft\">\n {COLOR_TONE_FOREGROUND_TONE[value.colorTone]}\n </Badge>\n </Stack>\n </Stack>\n </Card>\n\n {/* Section: Mode */}\n <Card title=\"Mode\" description=\"Switch between light and dark presentation.\">\n <Tabs\n value={activeMode}\n items={MODE_TABS}\n onValueChange={(m) => onModeChange?.(m)}\n variant=\"segmented\"\n testID={testID ? `${testID}-mode-tabs` : undefined}\n />\n </Card>\n\n {/* Section: Preview */}\n <Card title=\"Preview\" description=\"A quick look at how your theme renders common controls.\">\n <Stack gap=\"m\">\n <Heading level={4}>Heading</Heading>\n <Text>Body text — this shows default text color and weight.</Text>\n <Text tone=\"muted\" variant=\"bodySmall\">\n Muted caption text.\n </Text>\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Button tone=\"primary\" emphasis=\"solid\" size=\"m\">\n Primary\n </Button>\n <Button tone=\"neutral\" emphasis=\"soft\" size=\"m\">\n Neutral\n </Button>\n <Button tone=\"danger\" emphasis=\"ghost\" size=\"m\">\n Danger\n </Button>\n </Stack>\n <Stack direction=\"row\" gap=\"s\" align=\"center\">\n <Badge tone=\"primary\">Primary</Badge>\n <Badge tone=\"success\" emphasis=\"soft\">\n Success\n </Badge>\n <Badge tone=\"warning\" emphasis=\"soft\">\n Warning\n </Badge>\n <Badge tone=\"danger\" emphasis=\"soft\">\n Danger\n </Badge>\n </Stack>\n <Card\n tone=\"subtle\"\n title=\"Nested card\"\n description=\"Subtle tone inside the preview.\"\n compact\n />\n </Stack>\n </Card>\n\n {/* Submit */}\n {onSubmit ? (\n <Button\n tone=\"primary\"\n emphasis=\"solid\"\n onPress={() => onSubmit(value)}\n testID={testID ? `${testID}-submit` : undefined}\n >\n Apply theme\n </Button>\n ) : null}\n </Stack>\n );\n}\n\nexport const ThemeComposer = withZoraThemeScope(ThemeComposerInner);\n"]}
@@ -1,3 +1,3 @@
1
1
  export { ThemeComposer } from './ThemeComposer';
2
- export type { ThemeComposerProps } from './types';
2
+ export type { ThemeComposerAppCategory, ThemeComposerAppMood, ThemeComposerProps, ThemeComposerRecommendation, } from './types';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,SAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["export { ThemeComposer } from './ThemeComposer';\nexport type { ThemeComposerProps } from './types';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["export { ThemeComposer } from './ThemeComposer';\nexport type {\n ThemeComposerAppCategory,\n ThemeComposerAppMood,\n ThemeComposerProps,\n ThemeComposerRecommendation,\n} from './types';\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ZoraHexColor, ZoraTheme } from '../../theme/types';
2
+ import type { ThemeComposerAppCategory, ThemeComposerAppMood, ThemeComposerRecommendation } from './types';
3
+ export declare function findThemeComposerRecommendation(options: {
4
+ appCategory?: ThemeComposerAppCategory;
5
+ appMood?: ThemeComposerAppMood;
6
+ recommendations?: readonly ThemeComposerRecommendation[];
7
+ }): ThemeComposerRecommendation | undefined;
8
+ export declare function formatThemeComposerLabel(value: string): string;
9
+ export declare function hueDegreesToZoraHexColor(hueDegrees: number): ZoraHexColor;
10
+ export declare function createThemeFromThemeComposerRecommendation(options: {
11
+ value: ZoraTheme;
12
+ recommendation: ThemeComposerRecommendation;
13
+ }): ZoraTheme;
14
+ //# sourceMappingURL=recommendations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommendations.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/recommendations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAEjB,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACvD,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,eAAe,CAAC,EAAE,SAAS,2BAA2B,EAAE,CAAC;CAC1D,GAAG,2BAA2B,GAAG,SAAS,CAU1C;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAW9D;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CA+BzE;AAED,wBAAgB,0CAA0C,CAAC,OAAO,EAAE;IAClE,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,EAAE,2BAA2B,CAAC;CAC7C,GAAG,SAAS,CAYZ"}
@@ -0,0 +1,58 @@
1
+ export function findThemeComposerRecommendation(options) {
2
+ if (options.appCategory === undefined || options.recommendations === undefined) {
3
+ return undefined;
4
+ }
5
+ return options.recommendations.find((recommendation) => recommendation.appCategory === options.appCategory &&
6
+ (options.appMood === undefined || recommendation.appMood === options.appMood));
7
+ }
8
+ export function formatThemeComposerLabel(value) {
9
+ const spaced = value
10
+ .replace(/_/g, ' ')
11
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
12
+ .trim();
13
+ if (spaced.length === 0) {
14
+ return value;
15
+ }
16
+ return `${spaced.slice(0, 1).toUpperCase()}${spaced.slice(1)}`;
17
+ }
18
+ export function hueDegreesToZoraHexColor(hueDegrees) {
19
+ const normalizedHue = ((hueDegrees % 360) + 360) % 360;
20
+ const chroma = 0.56;
21
+ const lightness = 0.46;
22
+ const second = chroma * (1 - Math.abs(((normalizedHue / 60) % 2) - 1));
23
+ const match = lightness - chroma / 2;
24
+ const hueSector = Math.floor(normalizedHue / 60);
25
+ const [redPrime, greenPrime, bluePrime] = (() => {
26
+ switch (hueSector) {
27
+ case 0:
28
+ return [chroma, second, 0];
29
+ case 1:
30
+ return [second, chroma, 0];
31
+ case 2:
32
+ return [0, chroma, second];
33
+ case 3:
34
+ return [0, second, chroma];
35
+ case 4:
36
+ return [second, 0, chroma];
37
+ default:
38
+ return [chroma, 0, second];
39
+ }
40
+ })();
41
+ const toHexChannel = (channel) => {
42
+ const value = Math.round(Math.min(1, Math.max(0, channel + match)) * 255);
43
+ return value.toString(16).padStart(2, '0');
44
+ };
45
+ return `#${toHexChannel(redPrime)}${toHexChannel(greenPrime)}${toHexChannel(bluePrime)}`;
46
+ }
47
+ export function createThemeFromThemeComposerRecommendation(options) {
48
+ const suggestedPrimaryColor = options.recommendation.suggestedPrimaryHueDegrees === undefined
49
+ ? options.value.primaryColor
50
+ : hueDegreesToZoraHexColor(options.recommendation.suggestedPrimaryHueDegrees);
51
+ return {
52
+ ...options.value,
53
+ primaryColor: suggestedPrimaryColor,
54
+ harmony: options.recommendation.suggestedHarmony,
55
+ colorTone: options.recommendation.suggestedColorTone,
56
+ };
57
+ }
58
+ //# sourceMappingURL=recommendations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommendations.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/recommendations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ZoraHexColor, ZoraTheme } from '../../theme/types';\nimport type {\n ThemeComposerAppCategory,\n ThemeComposerAppMood,\n ThemeComposerRecommendation,\n} from './types';\n\nexport function findThemeComposerRecommendation(options: {\n appCategory?: ThemeComposerAppCategory;\n appMood?: ThemeComposerAppMood;\n recommendations?: readonly ThemeComposerRecommendation[];\n}): ThemeComposerRecommendation | undefined {\n if (options.appCategory === undefined || options.recommendations === undefined) {\n return undefined;\n }\n\n return options.recommendations.find(\n (recommendation) =>\n recommendation.appCategory === options.appCategory &&\n (options.appMood === undefined || recommendation.appMood === options.appMood),\n );\n}\n\nexport function formatThemeComposerLabel(value: string): string {\n const spaced = value\n .replace(/_/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .trim();\n\n if (spaced.length === 0) {\n return value;\n }\n\n return `${spaced.slice(0, 1).toUpperCase()}${spaced.slice(1)}`;\n}\n\nexport function hueDegreesToZoraHexColor(hueDegrees: number): ZoraHexColor {\n const normalizedHue = ((hueDegrees % 360) + 360) % 360;\n const chroma = 0.56;\n const lightness = 0.46;\n const second = chroma * (1 - Math.abs(((normalizedHue / 60) % 2) - 1));\n const match = lightness - chroma / 2;\n\n const hueSector = Math.floor(normalizedHue / 60);\n const [redPrime, greenPrime, bluePrime] = (() => {\n switch (hueSector) {\n case 0:\n return [chroma, second, 0] as const;\n case 1:\n return [second, chroma, 0] as const;\n case 2:\n return [0, chroma, second] as const;\n case 3:\n return [0, second, chroma] as const;\n case 4:\n return [second, 0, chroma] as const;\n default:\n return [chroma, 0, second] as const;\n }\n })();\n\n const toHexChannel = (channel: number): string => {\n const value = Math.round(Math.min(1, Math.max(0, channel + match)) * 255);\n return value.toString(16).padStart(2, '0');\n };\n\n return `#${toHexChannel(redPrime)}${toHexChannel(greenPrime)}${toHexChannel(bluePrime)}`;\n}\n\nexport function createThemeFromThemeComposerRecommendation(options: {\n value: ZoraTheme;\n recommendation: ThemeComposerRecommendation;\n}): ZoraTheme {\n const suggestedPrimaryColor =\n options.recommendation.suggestedPrimaryHueDegrees === undefined\n ? options.value.primaryColor\n : hueDegreesToZoraHexColor(options.recommendation.suggestedPrimaryHueDegrees);\n\n return {\n ...options.value,\n primaryColor: suggestedPrimaryColor,\n harmony: options.recommendation.suggestedHarmony,\n colorTone: options.recommendation.suggestedColorTone,\n };\n}\n"]}
@@ -1,10 +1,22 @@
1
- import type { ZoraTheme, ZoraThemeMode } from '../../theme/types';
1
+ import type { ZoraColorHarmony, ZoraColorTone, ZoraTheme, ZoraThemeMode } from '../../theme/types';
2
2
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
+ export type ThemeComposerAppCategory = string;
4
+ export type ThemeComposerAppMood = string;
5
+ export interface ThemeComposerRecommendation {
6
+ appCategory: ThemeComposerAppCategory;
7
+ appMood: ThemeComposerAppMood;
8
+ suggestedColorTone: ZoraColorTone;
9
+ suggestedHarmony: ZoraColorHarmony;
10
+ suggestedPrimaryHueDegrees?: number;
11
+ }
3
12
  export interface ThemeComposerProps extends ZoraBaseProps {
4
13
  value: ZoraTheme;
5
14
  onChange: (theme: ZoraTheme) => void;
6
15
  mode?: ZoraThemeMode;
7
16
  onModeChange?: (mode: ZoraThemeMode) => void;
8
17
  onSubmit?: (theme: ZoraTheme) => void;
18
+ appCategory?: ThemeComposerAppCategory;
19
+ appMood?: ThemeComposerAppMood;
20
+ recommendations?: readonly ThemeComposerRecommendation[];
9
21
  }
10
22
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/patterns/theme-composer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,kBAAkB,EAAE,aAAa,CAAC;IAClC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B,eAAe,CAAC,EAAE,SAAS,2BAA2B,EAAE,CAAC;CAC1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ZoraTheme, ZoraThemeMode } from '../../theme/types';\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport interface ThemeComposerProps extends ZoraBaseProps {\n value: ZoraTheme;\n onChange: (theme: ZoraTheme) => void;\n mode?: ZoraThemeMode;\n onModeChange?: (mode: ZoraThemeMode) => void;\n onSubmit?: (theme: ZoraTheme) => void;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/patterns/theme-composer/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ZoraColorHarmony, ZoraColorTone, ZoraTheme, ZoraThemeMode } from '../../theme/types';\nimport type { ZoraBaseProps } from '../../theme/ZoraBaseProps';\n\nexport type ThemeComposerAppCategory = string;\nexport type ThemeComposerAppMood = string;\n\nexport interface ThemeComposerRecommendation {\n appCategory: ThemeComposerAppCategory;\n appMood: ThemeComposerAppMood;\n suggestedColorTone: ZoraColorTone;\n suggestedHarmony: ZoraColorHarmony;\n suggestedPrimaryHueDegrees?: number;\n}\n\nexport interface ThemeComposerProps extends ZoraBaseProps {\n value: ZoraTheme;\n onChange: (theme: ZoraTheme) => void;\n mode?: ZoraThemeMode;\n onModeChange?: (mode: ZoraThemeMode) => void;\n onSubmit?: (theme: ZoraTheme) => void;\n appCategory?: ThemeComposerAppCategory;\n appMood?: ThemeComposerAppMood;\n recommendations?: readonly ThemeComposerRecommendation[];\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "0.15.4",
4
+ "version": "0.16.1",
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,7 +43,7 @@
43
43
  }
44
44
  },
45
45
  "dependencies": {
46
- "@ankhorage/surface": "^0.2.2",
46
+ "@ankhorage/surface": "^0.2.3",
47
47
  "culori": "^4.0.2"
48
48
  },
49
49
  "files": [
@@ -2,7 +2,13 @@ import { describe, expect, test } from 'bun:test';
2
2
 
3
3
  import { ZORA_COLOR_HARMONIES, ZORA_COLOR_TONES } from '../../theme/types';
4
4
  import { zoraDefaultTheme } from '../../theme/zoraDefaultTheme';
5
- import type { ThemeComposerProps } from './types';
5
+ import {
6
+ createThemeFromThemeComposerRecommendation,
7
+ findThemeComposerRecommendation,
8
+ formatThemeComposerLabel,
9
+ hueDegreesToZoraHexColor,
10
+ } from './recommendations';
11
+ import type { ThemeComposerProps, ThemeComposerRecommendation } from './types';
6
12
 
7
13
  // Validate the exported types compile correctly by asserting shape
8
14
  describe('ThemeComposerProps', () => {
@@ -38,6 +44,25 @@ describe('ThemeComposerProps', () => {
38
44
  props.onSubmit?.(zoraDefaultTheme);
39
45
  expect(submitted).toBe(true);
40
46
  });
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
+ });
41
66
  });
42
67
 
43
68
  describe('ZORA_COLOR_HARMONIES coverage for ThemeComposer', () => {
@@ -74,7 +99,7 @@ describe('ZORA_COLOR_TONES coverage for ThemeComposer', () => {
74
99
 
75
100
  describe('onChange propagation contract', () => {
76
101
  test('onChange receives updated theme with new harmony', () => {
77
- const received: unknown[] = [];
102
+ const received: (typeof zoraDefaultTheme)[] = [];
78
103
  const props: ThemeComposerProps = {
79
104
  value: zoraDefaultTheme,
80
105
  onChange: (t) => received.push(t),
@@ -82,16 +107,96 @@ describe('onChange propagation contract', () => {
82
107
  // Simulate what the component does internally
83
108
  props.onChange({ ...zoraDefaultTheme, harmony: 'triadic' });
84
109
  expect(received).toHaveLength(1);
85
- expect((received[0] as typeof zoraDefaultTheme).harmony).toBe('triadic');
110
+ expect(received[0]?.harmony).toBe('triadic');
86
111
  });
87
112
 
88
113
  test('onChange receives updated theme with new colorTone', () => {
89
- const received: unknown[] = [];
114
+ const received: (typeof zoraDefaultTheme)[] = [];
90
115
  const props: ThemeComposerProps = {
91
116
  value: zoraDefaultTheme,
92
117
  onChange: (t) => received.push(t),
93
118
  };
94
119
  props.onChange({ ...zoraDefaultTheme, colorTone: 'obsidian' });
95
- expect((received[0] as typeof zoraDefaultTheme).colorTone).toBe('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));
96
201
  });
97
202
  });
@@ -18,6 +18,11 @@ import {
18
18
  } from '../../theme/types';
19
19
  import { useZoraTheme } from '../../theme/useZoraTheme';
20
20
  import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
21
+ import {
22
+ createThemeFromThemeComposerRecommendation,
23
+ findThemeComposerRecommendation,
24
+ formatThemeComposerLabel,
25
+ } from './recommendations';
21
26
  import type { ThemeComposerProps } from './types';
22
27
 
23
28
  const HEX_RE = /^#[0-9A-Fa-f]{6}$/;
@@ -68,6 +73,9 @@ function ThemeComposerInner({
68
73
  mode,
69
74
  onModeChange,
70
75
  onSubmit,
76
+ appCategory,
77
+ appMood,
78
+ recommendations,
71
79
  testID,
72
80
  }: ThemeComposerProps) {
73
81
  const { theme } = useZoraTheme();
@@ -95,9 +103,63 @@ function ThemeComposerInner({
95
103
  }
96
104
 
97
105
  const activeMode = mode ?? 'light';
106
+ const recommendation = findThemeComposerRecommendation({
107
+ appCategory,
108
+ appMood,
109
+ recommendations,
110
+ });
98
111
 
99
112
  return (
100
113
  <Stack gap="l" testID={testID}>
114
+ {recommendation ? (
115
+ <Card
116
+ title="Recommended starting point"
117
+ description="Use this as an optional starting point. It is only applied when you choose it."
118
+ actions={
119
+ <Button
120
+ size="s"
121
+ emphasis="soft"
122
+ tone="primary"
123
+ onPress={() =>
124
+ onChange(
125
+ createThemeFromThemeComposerRecommendation({
126
+ value,
127
+ recommendation,
128
+ }),
129
+ )
130
+ }
131
+ testID={testID ? `${testID}-apply-recommendation` : undefined}
132
+ >
133
+ Apply recommendation
134
+ </Button>
135
+ }
136
+ >
137
+ <Stack gap="s">
138
+ <Stack direction="row" gap="s" wrap="wrap">
139
+ <Badge tone="primary" emphasis="soft">
140
+ {formatThemeComposerLabel(recommendation.appMood)} mood
141
+ </Badge>
142
+ <Badge tone="neutral" emphasis="soft">
143
+ {formatThemeComposerLabel(recommendation.suggestedColorTone)} color tone
144
+ </Badge>
145
+ <Badge tone="neutral" emphasis="soft">
146
+ {formatThemeComposerLabel(recommendation.suggestedHarmony)} harmony
147
+ </Badge>
148
+ {recommendation.suggestedPrimaryHueDegrees === undefined ? null : (
149
+ <Badge tone="neutral" emphasis="soft">
150
+ {recommendation.suggestedPrimaryHueDegrees}° hue
151
+ </Badge>
152
+ )}
153
+ </Stack>
154
+ <Text tone="muted" variant="bodySmall">
155
+ Suggested for {formatThemeComposerLabel(recommendation.appCategory)}. The color tone
156
+ controls palette character, harmony controls accent relationships, and hue sets the
157
+ starting primary color when available.
158
+ </Text>
159
+ </Stack>
160
+ </Card>
161
+ ) : null}
162
+
101
163
  {/* Section: Primary Color */}
102
164
  <Card title="Primary color" description="Set the seed color for your theme palette.">
103
165
  <Stack gap="m">
@@ -1,2 +1,7 @@
1
1
  export { ThemeComposer } from './ThemeComposer';
2
- export type { ThemeComposerProps } from './types';
2
+ export type {
3
+ ThemeComposerAppCategory,
4
+ ThemeComposerAppMood,
5
+ ThemeComposerProps,
6
+ ThemeComposerRecommendation,
7
+ } from './types';
@@ -0,0 +1,85 @@
1
+ import type { ZoraHexColor, ZoraTheme } from '../../theme/types';
2
+ import type {
3
+ ThemeComposerAppCategory,
4
+ ThemeComposerAppMood,
5
+ ThemeComposerRecommendation,
6
+ } from './types';
7
+
8
+ export function findThemeComposerRecommendation(options: {
9
+ appCategory?: ThemeComposerAppCategory;
10
+ appMood?: ThemeComposerAppMood;
11
+ recommendations?: readonly ThemeComposerRecommendation[];
12
+ }): ThemeComposerRecommendation | undefined {
13
+ if (options.appCategory === undefined || options.recommendations === undefined) {
14
+ return undefined;
15
+ }
16
+
17
+ return options.recommendations.find(
18
+ (recommendation) =>
19
+ recommendation.appCategory === options.appCategory &&
20
+ (options.appMood === undefined || recommendation.appMood === options.appMood),
21
+ );
22
+ }
23
+
24
+ export function formatThemeComposerLabel(value: string): string {
25
+ const spaced = value
26
+ .replace(/_/g, ' ')
27
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
28
+ .trim();
29
+
30
+ if (spaced.length === 0) {
31
+ return value;
32
+ }
33
+
34
+ return `${spaced.slice(0, 1).toUpperCase()}${spaced.slice(1)}`;
35
+ }
36
+
37
+ export function hueDegreesToZoraHexColor(hueDegrees: number): ZoraHexColor {
38
+ const normalizedHue = ((hueDegrees % 360) + 360) % 360;
39
+ const chroma = 0.56;
40
+ const lightness = 0.46;
41
+ const second = chroma * (1 - Math.abs(((normalizedHue / 60) % 2) - 1));
42
+ const match = lightness - chroma / 2;
43
+
44
+ const hueSector = Math.floor(normalizedHue / 60);
45
+ const [redPrime, greenPrime, bluePrime] = (() => {
46
+ switch (hueSector) {
47
+ case 0:
48
+ return [chroma, second, 0] as const;
49
+ case 1:
50
+ return [second, chroma, 0] as const;
51
+ case 2:
52
+ return [0, chroma, second] as const;
53
+ case 3:
54
+ return [0, second, chroma] as const;
55
+ case 4:
56
+ return [second, 0, chroma] as const;
57
+ default:
58
+ return [chroma, 0, second] as const;
59
+ }
60
+ })();
61
+
62
+ const toHexChannel = (channel: number): string => {
63
+ const value = Math.round(Math.min(1, Math.max(0, channel + match)) * 255);
64
+ return value.toString(16).padStart(2, '0');
65
+ };
66
+
67
+ return `#${toHexChannel(redPrime)}${toHexChannel(greenPrime)}${toHexChannel(bluePrime)}`;
68
+ }
69
+
70
+ export function createThemeFromThemeComposerRecommendation(options: {
71
+ value: ZoraTheme;
72
+ recommendation: ThemeComposerRecommendation;
73
+ }): ZoraTheme {
74
+ const suggestedPrimaryColor =
75
+ options.recommendation.suggestedPrimaryHueDegrees === undefined
76
+ ? options.value.primaryColor
77
+ : hueDegreesToZoraHexColor(options.recommendation.suggestedPrimaryHueDegrees);
78
+
79
+ return {
80
+ ...options.value,
81
+ primaryColor: suggestedPrimaryColor,
82
+ harmony: options.recommendation.suggestedHarmony,
83
+ colorTone: options.recommendation.suggestedColorTone,
84
+ };
85
+ }
@@ -1,10 +1,24 @@
1
- import type { ZoraTheme, ZoraThemeMode } from '../../theme/types';
1
+ import type { ZoraColorHarmony, ZoraColorTone, ZoraTheme, ZoraThemeMode } from '../../theme/types';
2
2
  import type { ZoraBaseProps } from '../../theme/ZoraBaseProps';
3
3
 
4
+ export type ThemeComposerAppCategory = string;
5
+ export type ThemeComposerAppMood = string;
6
+
7
+ export interface ThemeComposerRecommendation {
8
+ appCategory: ThemeComposerAppCategory;
9
+ appMood: ThemeComposerAppMood;
10
+ suggestedColorTone: ZoraColorTone;
11
+ suggestedHarmony: ZoraColorHarmony;
12
+ suggestedPrimaryHueDegrees?: number;
13
+ }
14
+
4
15
  export interface ThemeComposerProps extends ZoraBaseProps {
5
16
  value: ZoraTheme;
6
17
  onChange: (theme: ZoraTheme) => void;
7
18
  mode?: ZoraThemeMode;
8
19
  onModeChange?: (mode: ZoraThemeMode) => void;
9
20
  onSubmit?: (theme: ZoraTheme) => void;
21
+ appCategory?: ThemeComposerAppCategory;
22
+ appMood?: ThemeComposerAppMood;
23
+ recommendations?: readonly ThemeComposerRecommendation[];
10
24
  }