@ankhorage/surface 1.3.1 → 2.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 (213) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/components/badge/Badge.d.ts +1 -1
  3. package/dist/components/badge/Badge.d.ts.map +1 -1
  4. package/dist/components/badge/Badge.js +3 -3
  5. package/dist/components/badge/Badge.js.map +1 -1
  6. package/dist/components/badge/types.d.ts +2 -2
  7. package/dist/components/badge/types.d.ts.map +1 -1
  8. package/dist/components/badge/types.js.map +1 -1
  9. package/dist/components/button/Button.d.ts +1 -1
  10. package/dist/components/button/Button.d.ts.map +1 -1
  11. package/dist/components/button/Button.js +6 -3
  12. package/dist/components/button/Button.js.map +1 -1
  13. package/dist/components/button/types.d.ts +2 -2
  14. package/dist/components/button/types.d.ts.map +1 -1
  15. package/dist/components/button/types.js.map +1 -1
  16. package/dist/components/checkbox/Checkbox.d.ts +1 -1
  17. package/dist/components/checkbox/Checkbox.d.ts.map +1 -1
  18. package/dist/components/checkbox/Checkbox.js +7 -4
  19. package/dist/components/checkbox/Checkbox.js.map +1 -1
  20. package/dist/components/checkbox/types.d.ts +2 -2
  21. package/dist/components/checkbox/types.d.ts.map +1 -1
  22. package/dist/components/checkbox/types.js.map +1 -1
  23. package/dist/components/field/Field.d.ts.map +1 -1
  24. package/dist/components/field/Field.js +6 -2
  25. package/dist/components/field/Field.js.map +1 -1
  26. package/dist/components/helper-text/HelperText.d.ts +1 -1
  27. package/dist/components/helper-text/HelperText.d.ts.map +1 -1
  28. package/dist/components/helper-text/HelperText.js +2 -2
  29. package/dist/components/helper-text/HelperText.js.map +1 -1
  30. package/dist/components/helper-text/types.d.ts +3 -2
  31. package/dist/components/helper-text/types.d.ts.map +1 -1
  32. package/dist/components/helper-text/types.js.map +1 -1
  33. package/dist/components/icon-button/IconButton.d.ts +1 -1
  34. package/dist/components/icon-button/IconButton.d.ts.map +1 -1
  35. package/dist/components/icon-button/IconButton.js +2 -2
  36. package/dist/components/icon-button/IconButton.js.map +1 -1
  37. package/dist/components/icon-button/types.d.ts +2 -2
  38. package/dist/components/icon-button/types.d.ts.map +1 -1
  39. package/dist/components/icon-button/types.js.map +1 -1
  40. package/dist/components/label/Label.d.ts +1 -1
  41. package/dist/components/label/Label.d.ts.map +1 -1
  42. package/dist/components/label/Label.js +3 -3
  43. package/dist/components/label/Label.js.map +1 -1
  44. package/dist/components/label/types.d.ts +3 -2
  45. package/dist/components/label/types.d.ts.map +1 -1
  46. package/dist/components/label/types.js.map +1 -1
  47. package/dist/components/list-item/ListItem.js +1 -1
  48. package/dist/components/list-item/ListItem.js.map +1 -1
  49. package/dist/components/menu/Menu.d.ts.map +1 -1
  50. package/dist/components/menu/Menu.js +1 -3
  51. package/dist/components/menu/Menu.js.map +1 -1
  52. package/dist/components/navigation/NavigationItem.d.ts.map +1 -1
  53. package/dist/components/navigation/NavigationItem.js +1 -1
  54. package/dist/components/navigation/NavigationItem.js.map +1 -1
  55. package/dist/components/navigation/TabBarItem.d.ts.map +1 -1
  56. package/dist/components/navigation/TabBarItem.js +1 -1
  57. package/dist/components/navigation/TabBarItem.js.map +1 -1
  58. package/dist/components/radio/Radio.d.ts +1 -1
  59. package/dist/components/radio/Radio.d.ts.map +1 -1
  60. package/dist/components/radio/Radio.js +4 -3
  61. package/dist/components/radio/Radio.js.map +1 -1
  62. package/dist/components/radio/types.d.ts +2 -2
  63. package/dist/components/radio/types.d.ts.map +1 -1
  64. package/dist/components/radio/types.js.map +1 -1
  65. package/dist/components/switch/Switch.d.ts +1 -1
  66. package/dist/components/switch/Switch.d.ts.map +1 -1
  67. package/dist/components/switch/Switch.js +4 -3
  68. package/dist/components/switch/Switch.js.map +1 -1
  69. package/dist/components/switch/types.d.ts +2 -2
  70. package/dist/components/switch/types.d.ts.map +1 -1
  71. package/dist/components/switch/types.js.map +1 -1
  72. package/dist/components/tabs/Tab.d.ts.map +1 -1
  73. package/dist/components/tabs/Tab.js +1 -1
  74. package/dist/components/tabs/Tab.js.map +1 -1
  75. package/dist/components/toast/Toast.d.ts +1 -1
  76. package/dist/components/toast/Toast.d.ts.map +1 -1
  77. package/dist/components/toast/Toast.js +34 -9
  78. package/dist/components/toast/Toast.js.map +1 -1
  79. package/dist/components/toast/ToastProvider.js +1 -1
  80. package/dist/components/toast/ToastProvider.js.map +1 -1
  81. package/dist/components/toast/index.d.ts +1 -1
  82. package/dist/components/toast/index.d.ts.map +1 -1
  83. package/dist/components/toast/index.js.map +1 -1
  84. package/dist/components/toast/types.d.ts +3 -2
  85. package/dist/components/toast/types.d.ts.map +1 -1
  86. package/dist/components/toast/types.js.map +1 -1
  87. package/dist/components/tooltip/Tooltip.js +1 -1
  88. package/dist/components/tooltip/Tooltip.js.map +1 -1
  89. package/dist/examples/DocsExamples.js +4 -4
  90. package/dist/examples/DocsExamples.js.map +1 -1
  91. package/dist/index.d.ts +3 -1
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -0
  94. package/dist/index.js.map +1 -1
  95. package/dist/internal/resolvers/index.d.ts +1 -1
  96. package/dist/internal/resolvers/index.d.ts.map +1 -1
  97. package/dist/internal/resolvers/index.js +1 -1
  98. package/dist/internal/resolvers/index.js.map +1 -1
  99. package/dist/internal/resolvers/resolveFieldPresentation.d.ts +5 -5
  100. package/dist/internal/resolvers/resolveFieldPresentation.d.ts.map +1 -1
  101. package/dist/internal/resolvers/resolveFieldPresentation.js +8 -6
  102. package/dist/internal/resolvers/resolveFieldPresentation.js.map +1 -1
  103. package/dist/internal/resolvers/resolveInteractiveColors.d.ts +3 -3
  104. package/dist/internal/resolvers/resolveInteractiveColors.d.ts.map +1 -1
  105. package/dist/internal/resolvers/resolveInteractiveColors.js +4 -6
  106. package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
  107. package/dist/internal/resolvers/resolveSelectionControlColors.d.ts +3 -3
  108. package/dist/internal/resolvers/resolveSelectionControlColors.d.ts.map +1 -1
  109. package/dist/internal/resolvers/resolveSelectionControlColors.js +4 -4
  110. package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
  111. package/dist/internal/resolvers/resolveSurfaceColor.d.ts +4 -0
  112. package/dist/internal/resolvers/resolveSurfaceColor.d.ts.map +1 -0
  113. package/dist/internal/resolvers/resolveSurfaceColor.js +26 -0
  114. package/dist/internal/resolvers/resolveSurfaceColor.js.map +1 -0
  115. package/dist/internal/resolvers/resolveTextColor.d.ts +2 -3
  116. package/dist/internal/resolvers/resolveTextColor.d.ts.map +1 -1
  117. package/dist/internal/resolvers/resolveTextColor.js +7 -11
  118. package/dist/internal/resolvers/resolveTextColor.js.map +1 -1
  119. package/dist/layout/ScrollArea.d.ts +9 -0
  120. package/dist/layout/ScrollArea.d.ts.map +1 -0
  121. package/dist/layout/ScrollArea.js +52 -0
  122. package/dist/layout/ScrollArea.js.map +1 -0
  123. package/dist/layout/index.d.ts +2 -0
  124. package/dist/layout/index.d.ts.map +1 -1
  125. package/dist/layout/index.js +1 -0
  126. package/dist/layout/index.js.map +1 -1
  127. package/dist/primitives/heading/Heading.d.ts.map +1 -1
  128. package/dist/primitives/heading/Heading.js +2 -2
  129. package/dist/primitives/heading/Heading.js.map +1 -1
  130. package/dist/primitives/heading/resolveHeadingStyle.d.ts +2 -1
  131. package/dist/primitives/heading/resolveHeadingStyle.d.ts.map +1 -1
  132. package/dist/primitives/heading/resolveHeadingStyle.js +2 -2
  133. package/dist/primitives/heading/resolveHeadingStyle.js.map +1 -1
  134. package/dist/primitives/heading/types.d.ts +3 -0
  135. package/dist/primitives/heading/types.d.ts.map +1 -1
  136. package/dist/primitives/heading/types.js.map +1 -1
  137. package/dist/primitives/text/Text.d.ts +1 -1
  138. package/dist/primitives/text/Text.d.ts.map +1 -1
  139. package/dist/primitives/text/Text.js +2 -2
  140. package/dist/primitives/text/Text.js.map +1 -1
  141. package/dist/primitives/text/types.d.ts +3 -3
  142. package/dist/primitives/text/types.d.ts.map +1 -1
  143. package/dist/primitives/text/types.js.map +1 -1
  144. package/dist/surfaceColor.d.ts +9 -0
  145. package/dist/surfaceColor.d.ts.map +1 -0
  146. package/dist/surfaceColor.js +15 -0
  147. package/dist/surfaceColor.js.map +1 -0
  148. package/dist/theme/colorEngine.d.ts.map +1 -1
  149. package/dist/theme/colorEngine.js +9 -1
  150. package/dist/theme/colorEngine.js.map +1 -1
  151. package/dist/theme/types.d.ts +5 -0
  152. package/dist/theme/types.d.ts.map +1 -1
  153. package/dist/theme/types.js.map +1 -1
  154. package/package.json +1 -1
  155. package/src/components/badge/Badge.tsx +8 -3
  156. package/src/components/badge/types.ts +2 -2
  157. package/src/components/button/Button.tsx +9 -3
  158. package/src/components/button/types.ts +2 -2
  159. package/src/components/checkbox/Checkbox.tsx +13 -4
  160. package/src/components/checkbox/types.ts +2 -2
  161. package/src/components/field/Field.tsx +11 -3
  162. package/src/components/helper-text/HelperText.tsx +2 -2
  163. package/src/components/helper-text/types.ts +3 -2
  164. package/src/components/icon-button/IconButton.tsx +2 -2
  165. package/src/components/icon-button/types.ts +2 -2
  166. package/src/components/label/Label.tsx +9 -3
  167. package/src/components/label/types.ts +3 -2
  168. package/src/components/list-item/ListItem.tsx +1 -1
  169. package/src/components/menu/Menu.tsx +1 -8
  170. package/src/components/navigation/NavigationItem.tsx +2 -1
  171. package/src/components/navigation/TabBarItem.tsx +2 -1
  172. package/src/components/radio/Radio.tsx +5 -3
  173. package/src/components/radio/types.ts +2 -2
  174. package/src/components/switch/Switch.tsx +5 -3
  175. package/src/components/switch/types.ts +2 -2
  176. package/src/components/tabs/Tab.tsx +1 -5
  177. package/src/components/toast/Toast.tsx +38 -10
  178. package/src/components/toast/ToastProvider.tsx +1 -1
  179. package/src/components/toast/index.ts +1 -1
  180. package/src/components/toast/types.ts +4 -2
  181. package/src/components/tooltip/Tooltip.tsx +1 -1
  182. package/src/examples/DocsExamples.tsx +4 -4
  183. package/src/index.test.ts +1 -0
  184. package/src/index.ts +13 -1
  185. package/src/internal/resolvers/index.ts +1 -1
  186. package/src/internal/resolvers/resolveFieldPresentation.test.ts +12 -10
  187. package/src/internal/resolvers/resolveFieldPresentation.ts +13 -11
  188. package/src/internal/resolvers/resolveInteractiveColors.test.ts +4 -4
  189. package/src/internal/resolvers/resolveInteractiveColors.ts +6 -7
  190. package/src/internal/resolvers/resolveSelectionControlColors.test.ts +8 -8
  191. package/src/internal/resolvers/resolveSelectionControlColors.ts +6 -5
  192. package/src/internal/resolvers/resolveSurfaceColor.test.ts +21 -0
  193. package/src/internal/resolvers/resolveSurfaceColor.ts +31 -0
  194. package/src/internal/resolvers/resolveTextColor.test.ts +16 -5
  195. package/src/internal/resolvers/resolveTextColor.ts +10 -22
  196. package/src/layout/ScrollArea.tsx +115 -0
  197. package/src/layout/index.ts +2 -0
  198. package/src/primitives/heading/Heading.tsx +3 -1
  199. package/src/primitives/heading/resolveHeadingStyle.test.ts +1 -1
  200. package/src/primitives/heading/resolveHeadingStyle.ts +4 -1
  201. package/src/primitives/heading/types.ts +4 -0
  202. package/src/primitives/text/Text.tsx +2 -2
  203. package/src/primitives/text/types.ts +3 -3
  204. package/src/surfaceColor.test.ts +46 -0
  205. package/src/surfaceColor.ts +22 -0
  206. package/src/theme/colorEngine.test.ts +6 -0
  207. package/src/theme/colorEngine.ts +9 -1
  208. package/src/theme/types.ts +5 -0
  209. package/dist/internal/resolvers/resolveTone.d.ts +0 -4
  210. package/dist/internal/resolvers/resolveTone.d.ts.map +0 -1
  211. package/dist/internal/resolvers/resolveTone.js +0 -16
  212. package/dist/internal/resolvers/resolveTone.js.map +0 -1
  213. package/src/internal/resolvers/resolveTone.ts +0 -19
@@ -1,11 +1,13 @@
1
1
  import type React from 'react';
2
2
 
3
- export type ToastTone = 'default' | 'success' | 'danger';
3
+ import type { SurfaceStatusColor } from '../../surfaceColor';
4
+
5
+ export type ToastStatus = 'default' | SurfaceStatusColor;
4
6
 
5
7
  export interface ToastProps {
6
8
  title?: React.ReactNode;
7
9
  description?: React.ReactNode;
8
- tone?: ToastTone;
10
+ status?: ToastStatus;
9
11
  onDismiss?: (() => void) | undefined;
10
12
  testID?: string;
11
13
  }
@@ -98,7 +98,7 @@ export function Tooltip({
98
98
  testID={testID}
99
99
  variant="raised"
100
100
  >
101
- <Text color={theme.semantics.content.inverse} variant="caption">
101
+ <Text emphasis="inverse" variant="caption">
102
102
  {content}
103
103
  </Text>
104
104
  </Surface>
@@ -42,8 +42,8 @@ export function ProviderExample() {
42
42
  <ThemeProvider initialConfig={docsThemeConfig}>
43
43
  <Stack gap="m" p="l">
44
44
  <Text variant="body">Surface starter</Text>
45
- <Badge content="Foundation" tone="success" />
46
- <Button tone="warning" variant="soft">
45
+ <Badge color="success" content="Foundation" />
46
+ <Button color="warning" variant="soft">
47
47
  Continue
48
48
  </Button>
49
49
  </Stack>
@@ -66,7 +66,7 @@ export function FormAndOverlayExample() {
66
66
  <Checkbox defaultChecked>Weekly updates</Checkbox>
67
67
  <Radio>Product announcements</Radio>
68
68
  <Switch readOnly>Read-only setting</Switch>
69
- <HelperText tone="danger">Invalid fields should reuse the same danger tone.</HelperText>
69
+ <HelperText color="error">Invalid fields should reuse the same error color.</HelperText>
70
70
  </Stack>
71
71
  <Modal visible={false}>
72
72
  <Text>Modal content</Text>
@@ -106,7 +106,7 @@ export function NavigationExample() {
106
106
  trigger={<Text>Open menu</Text>}
107
107
  />
108
108
  <Box>
109
- <Text tone="success">Theme overrides can stay semantic.</Text>
109
+ <Text color="success">Theme overrides can stay semantic.</Text>
110
110
  </Box>
111
111
  </Stack>
112
112
  </ThemeProvider>
package/src/index.test.ts CHANGED
@@ -34,6 +34,7 @@ const expectedRootExports = [
34
34
  "export * from './core/responsive';",
35
35
  "export * from './layout';",
36
36
  "export { Image } from './primitives/image';",
37
+ "export {\n SURFACE_COLORS,\n SURFACE_EMPHASES,\n SURFACE_PALETTE_COLORS,\n SURFACE_STATUS_COLORS,\n} from './surfaceColor';",
37
38
  "export * from './theme';",
38
39
  ] as const;
39
40
 
package/src/index.ts CHANGED
@@ -50,7 +50,7 @@ export type { TextInputProps } from './components/text-input';
50
50
  export { TextInput } from './components/text-input';
51
51
  export type { TextareaProps } from './components/textarea';
52
52
  export { Textarea } from './components/textarea';
53
- export type { ToastOptions, ToastProps, ToastTone } from './components/toast';
53
+ export type { ToastOptions, ToastProps, ToastStatus } from './components/toast';
54
54
  export { Toast, ToastProvider, useToast } from './components/toast';
55
55
  export type { TooltipProps } from './components/tooltip';
56
56
  export { Tooltip } from './components/tooltip';
@@ -70,6 +70,18 @@ export type { ImageFit, ImageProps, SurfaceImageSource } from './primitives/imag
70
70
  export { Image } from './primitives/image';
71
71
  export type { TextProps } from './primitives/text';
72
72
  export { Text } from './primitives/text';
73
+ export type {
74
+ SurfaceColor,
75
+ SurfaceEmphasis,
76
+ SurfacePaletteColor,
77
+ SurfaceStatusColor,
78
+ } from './surfaceColor';
79
+ export {
80
+ SURFACE_COLORS,
81
+ SURFACE_EMPHASES,
82
+ SURFACE_PALETTE_COLORS,
83
+ SURFACE_STATUS_COLORS,
84
+ } from './surfaceColor';
73
85
  export * from './theme';
74
86
  export { isDeepEqual } from './utils/deepEqual';
75
87
  export { deepMerge } from './utils/deepMerge';
@@ -10,6 +10,6 @@ export * from './resolveOverlayAnimation';
10
10
  export * from './resolveOverlayZIndex';
11
11
  export * from './resolveSelectionControlBehavior';
12
12
  export * from './resolveSelectionControlColors';
13
+ export * from './resolveSurfaceColor';
13
14
  export * from './resolveTextColor';
14
15
  export * from './resolveTextStyles';
15
- export * from './resolveTone';
@@ -4,28 +4,30 @@ import { resolveFieldPresentation } from './resolveFieldPresentation';
4
4
  import { resolveFieldState } from './resolveFieldState';
5
5
 
6
6
  describe('resolveFieldPresentation', () => {
7
- it('keeps label and helper tones aligned for default fields', () => {
7
+ it('keeps label and helper text presentation aligned for default fields', () => {
8
8
  expect(resolveFieldPresentation(resolveFieldState({}))).toEqual({
9
- helperTone: 'default',
10
- labelTone: 'default',
9
+ helperEmphasis: 'default',
10
+ labelEmphasis: 'default',
11
11
  });
12
12
  });
13
13
 
14
14
  it('mutes both label and helper text for disabled and read-only fields', () => {
15
15
  expect(resolveFieldPresentation(resolveFieldState({ disabled: true }))).toEqual({
16
- helperTone: 'muted',
17
- labelTone: 'muted',
16
+ helperEmphasis: 'muted',
17
+ labelEmphasis: 'muted',
18
18
  });
19
19
  expect(resolveFieldPresentation(resolveFieldState({ readOnly: true }))).toEqual({
20
- helperTone: 'muted',
21
- labelTone: 'muted',
20
+ helperEmphasis: 'muted',
21
+ labelEmphasis: 'muted',
22
22
  });
23
23
  });
24
24
 
25
- it('elevates invalid fields to the danger tone', () => {
25
+ it('elevates invalid fields to the error color', () => {
26
26
  expect(resolveFieldPresentation(resolveFieldState({ invalid: true, readOnly: true }))).toEqual({
27
- helperTone: 'danger',
28
- labelTone: 'danger',
27
+ helperColor: 'error',
28
+ helperEmphasis: 'default',
29
+ labelColor: 'error',
30
+ labelEmphasis: 'default',
29
31
  });
30
32
  });
31
33
  });
@@ -1,30 +1,32 @@
1
+ import type { SurfaceColor, SurfaceEmphasis } from '../../surfaceColor';
1
2
  import type { FieldState } from './resolveFieldState';
2
- import type { TextTone } from './resolveTextColor';
3
-
4
- type FieldPresentationTone = Extract<TextTone, 'default' | 'muted' | 'danger'>;
5
3
 
6
4
  export interface ResolvedFieldPresentation {
7
- helperTone: FieldPresentationTone;
8
- labelTone: FieldPresentationTone;
5
+ helperColor?: Extract<SurfaceColor, 'error'>;
6
+ helperEmphasis: Extract<SurfaceEmphasis, 'default' | 'muted'>;
7
+ labelColor?: Extract<SurfaceColor, 'error'>;
8
+ labelEmphasis: Extract<SurfaceEmphasis, 'default' | 'muted'>;
9
9
  }
10
10
 
11
11
  export function resolveFieldPresentation(fieldState: FieldState): ResolvedFieldPresentation {
12
12
  if (fieldState.invalid) {
13
13
  return {
14
- helperTone: 'danger',
15
- labelTone: 'danger',
14
+ helperColor: 'error',
15
+ helperEmphasis: 'default',
16
+ labelColor: 'error',
17
+ labelEmphasis: 'default',
16
18
  };
17
19
  }
18
20
 
19
21
  if (fieldState.disabled || fieldState.readOnly) {
20
22
  return {
21
- helperTone: 'muted',
22
- labelTone: 'muted',
23
+ helperEmphasis: 'muted',
24
+ labelEmphasis: 'muted',
23
25
  };
24
26
  }
25
27
 
26
28
  return {
27
- helperTone: 'default',
28
- labelTone: 'default',
29
+ helperEmphasis: 'default',
30
+ labelEmphasis: 'default',
29
31
  };
30
32
  }
@@ -5,11 +5,11 @@ import { resolveFieldState } from './resolveFieldState';
5
5
  import { resolveButtonColors, resolveInputColors } from './resolveInteractiveColors';
6
6
 
7
7
  describe('resolveInteractiveColors', () => {
8
- it('maps button variant, tone, and state through semantic slots', () => {
8
+ it('maps button variant, color, and state through semantic slots', () => {
9
9
  const theme = createTheme();
10
10
  const solid = resolveButtonColors(theme, {
11
11
  variant: 'solid',
12
- tone: 'primary',
12
+ color: 'primary',
13
13
  state: {
14
14
  disabled: false,
15
15
  focused: false,
@@ -19,7 +19,7 @@ describe('resolveInteractiveColors', () => {
19
19
  });
20
20
  const outline = resolveButtonColors(theme, {
21
21
  variant: 'outline',
22
- tone: 'danger',
22
+ color: 'danger',
23
23
  state: {
24
24
  disabled: false,
25
25
  focused: false,
@@ -50,7 +50,7 @@ describe('resolveInteractiveColors', () => {
50
50
  }),
51
51
  );
52
52
 
53
- expect(focusedInvalid.borderColor).toBe(theme.semantics.danger.base);
53
+ expect(focusedInvalid.borderColor).toBe(theme.semantics.error.base);
54
54
  expect(readOnly.backgroundColor).toBe(theme.semantics.surface.subtle);
55
55
  expect(readOnly.placeholderColor).toBe(theme.semantics.content.muted);
56
56
  });
@@ -1,7 +1,8 @@
1
+ import type { SurfaceColor } from '../../surfaceColor';
1
2
  import type { SurfaceTheme } from '../../theme/types';
2
3
  import type { FieldState } from './resolveFieldState';
3
4
  import type { InteractionState } from './resolveInteractiveState';
4
- import { type ComponentTone, resolveTone } from './resolveTone';
5
+ import { resolveSurfaceColor } from './resolveSurfaceColor';
5
6
 
6
7
  export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft';
7
8
 
@@ -16,11 +17,11 @@ export function resolveButtonColors(
16
17
  theme: SurfaceTheme,
17
18
  {
18
19
  variant,
19
- tone,
20
+ color,
20
21
  state,
21
22
  }: {
22
23
  variant: ButtonVariant;
23
- tone: ComponentTone;
24
+ color: SurfaceColor;
24
25
  state: InteractionState;
25
26
  },
26
27
  ): ResolvedInteractiveColors {
@@ -33,7 +34,7 @@ export function resolveButtonColors(
33
34
  };
34
35
  }
35
36
 
36
- const semanticTone = resolveTone(theme, tone);
37
+ const semanticTone = resolveSurfaceColor(theme, color);
37
38
 
38
39
  switch (variant) {
39
40
  case 'outline':
@@ -99,9 +100,7 @@ export function resolveInputColors(
99
100
  backgroundColor: fieldState.readOnly
100
101
  ? theme.semantics.surface.subtle
101
102
  : theme.semantics.surface.default,
102
- borderColor: fieldState.focused
103
- ? theme.semantics.danger.base
104
- : theme.semantics.danger.outline,
103
+ borderColor: fieldState.focused ? theme.semantics.error.base : theme.semantics.error.outline,
105
104
  contentColor: theme.semantics.content.default,
106
105
  placeholderColor: theme.semantics.content.muted,
107
106
  };
@@ -5,12 +5,12 @@ import { resolveFieldState } from './resolveFieldState';
5
5
  import { resolveSelectionControlColors } from './resolveSelectionControlColors';
6
6
 
7
7
  describe('resolveSelectionControlColors', () => {
8
- it('maps checked controls through the selected tone semantics', () => {
8
+ it('maps checked controls through the selected color semantics', () => {
9
9
  const theme = createTheme();
10
10
  const colors = resolveSelectionControlColors(theme, {
11
11
  checked: true,
12
12
  fieldState: resolveFieldState({ focused: true }),
13
- tone: 'primary',
13
+ color: 'primary',
14
14
  });
15
15
 
16
16
  expect(colors.borderColor).toBe(theme.semantics.action.primary.base);
@@ -18,24 +18,24 @@ describe('resolveSelectionControlColors', () => {
18
18
  expect(colors.labelColor).toBe(theme.semantics.content.default);
19
19
  });
20
20
 
21
- it('maps invalid unchecked controls through danger/border semantics', () => {
21
+ it('maps invalid unchecked controls through error/border semantics', () => {
22
22
  const theme = createTheme();
23
23
  const colors = resolveSelectionControlColors(theme, {
24
24
  checked: false,
25
25
  fieldState: resolveFieldState({ invalid: true }),
26
- tone: 'primary',
26
+ color: 'primary',
27
27
  });
28
28
 
29
- expect(colors.borderColor).toBe(theme.semantics.danger.outline);
30
- expect(colors.trackColor).toBe(theme.semantics.danger.outline);
29
+ expect(colors.borderColor).toBe(theme.semantics.error.outline);
30
+ expect(colors.trackColor).toBe(theme.semantics.error.outline);
31
31
  });
32
32
 
33
- it('mutes disabled controls regardless of tone', () => {
33
+ it('mutes disabled controls regardless of color', () => {
34
34
  const theme = createTheme();
35
35
  const colors = resolveSelectionControlColors(theme, {
36
36
  checked: true,
37
37
  fieldState: resolveFieldState({ disabled: true }),
38
- tone: 'danger',
38
+ color: 'danger',
39
39
  });
40
40
 
41
41
  expect(colors.labelColor).toBe(theme.semantics.content.muted);
@@ -1,6 +1,7 @@
1
+ import type { SurfaceColor } from '../../surfaceColor';
1
2
  import type { SurfaceTheme } from '../../theme/types';
2
3
  import type { FieldState } from './resolveFieldState';
3
- import { type ComponentTone, resolveTone } from './resolveTone';
4
+ import { resolveSurfaceColor } from './resolveSurfaceColor';
4
5
 
5
6
  export interface ResolvedSelectionControlColors {
6
7
  backgroundColor: string;
@@ -15,20 +16,20 @@ export interface ResolvedSelectionControlColors {
15
16
  export function resolveSelectionControlColors(
16
17
  theme: SurfaceTheme,
17
18
  {
18
- tone = 'primary',
19
+ color = 'primary',
19
20
  fieldState,
20
21
  checked,
21
22
  hovered = false,
22
23
  pressed = false,
23
24
  }: {
24
- tone?: ComponentTone;
25
+ color?: SurfaceColor;
25
26
  fieldState: FieldState;
26
27
  checked: boolean;
27
28
  hovered?: boolean;
28
29
  pressed?: boolean;
29
30
  },
30
31
  ): ResolvedSelectionControlColors {
31
- const semanticTone = resolveTone(theme, fieldState.invalid ? 'danger' : tone);
32
+ const semanticTone = resolveSurfaceColor(theme, fieldState.invalid ? 'error' : color);
32
33
  const isMuted = fieldState.disabled;
33
34
  const isInteractiveReadOnly = fieldState.readOnly && !fieldState.disabled;
34
35
 
@@ -47,7 +48,7 @@ export function resolveSelectionControlColors(
47
48
  }
48
49
 
49
50
  const uncheckedBorderColor = fieldState.invalid
50
- ? theme.semantics.danger.outline
51
+ ? theme.semantics.error.outline
51
52
  : fieldState.focused
52
53
  ? theme.semantics.border.focus
53
54
  : theme.semantics.border.strong;
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { createTheme } from '../../theme/createTheme';
4
+ import { resolveSurfaceColor } from './resolveSurfaceColor';
5
+
6
+ describe('resolveSurfaceColor', () => {
7
+ it('maps every public surface color to a semantic role', () => {
8
+ const theme = createTheme();
9
+
10
+ expect(resolveSurfaceColor(theme, 'primary')).toBe(theme.semantics.action.primary);
11
+ expect(resolveSurfaceColor(theme, 'secondary')).toBe(theme.semantics.secondary);
12
+ expect(resolveSurfaceColor(theme, 'tertiary')).toBe(theme.semantics.accent);
13
+ expect(resolveSurfaceColor(theme, 'quaternary')).toBe(theme.semantics.highlight);
14
+ expect(resolveSurfaceColor(theme, 'neutral')).toBe(theme.semantics.action.neutral);
15
+ expect(resolveSurfaceColor(theme, 'success')).toBe(theme.semantics.success);
16
+ expect(resolveSurfaceColor(theme, 'warning')).toBe(theme.semantics.warning);
17
+ expect(resolveSurfaceColor(theme, 'error')).toBe(theme.semantics.error);
18
+ expect(resolveSurfaceColor(theme, 'info')).toBe(theme.semantics.info);
19
+ expect(resolveSurfaceColor(theme, 'danger')).toBe(theme.semantics.action.danger);
20
+ });
21
+ });
@@ -0,0 +1,31 @@
1
+ import type { SurfaceColor } from '../../surfaceColor';
2
+ import type { RoleSemantics, SurfaceTheme } from '../../theme/types';
3
+
4
+ export function resolveSurfaceColor(
5
+ theme: SurfaceTheme,
6
+ color: SurfaceColor = 'primary',
7
+ ): RoleSemantics {
8
+ switch (color) {
9
+ case 'secondary':
10
+ return theme.semantics.secondary;
11
+ case 'tertiary':
12
+ return theme.semantics.accent;
13
+ case 'quaternary':
14
+ return theme.semantics.highlight;
15
+ case 'neutral':
16
+ return theme.semantics.action.neutral;
17
+ case 'error':
18
+ return theme.semantics.error;
19
+ case 'info':
20
+ return theme.semantics.info;
21
+ case 'danger':
22
+ return theme.semantics.action.danger;
23
+ case 'success':
24
+ return theme.semantics.success;
25
+ case 'warning':
26
+ return theme.semantics.warning;
27
+ case 'primary':
28
+ default:
29
+ return theme.semantics.action.primary;
30
+ }
31
+ }
@@ -4,7 +4,7 @@ import { createTheme } from '../../theme/createTheme';
4
4
  import { resolveTextColor } from './resolveTextColor';
5
5
 
6
6
  describe('resolveTextColor', () => {
7
- it('maps semantic text tones through content aliases', () => {
7
+ it('maps semantic text emphases through content aliases', () => {
8
8
  const theme = createTheme();
9
9
 
10
10
  expect(resolveTextColor(theme, 'default')).toBe(theme.semantics.content.default);
@@ -13,11 +13,22 @@ describe('resolveTextColor', () => {
13
13
  expect(resolveTextColor(theme, 'inverse')).toBe(theme.semantics.content.inverse);
14
14
  });
15
15
 
16
- it('supports semantic status tones for shared field and messaging copy', () => {
16
+ it('supports semantic colors for shared field and messaging copy', () => {
17
17
  const theme = createTheme();
18
18
 
19
- expect(resolveTextColor(theme, 'danger')).toBe(theme.semantics.danger.base);
20
- expect(resolveTextColor(theme, 'success')).toBe(theme.semantics.success.base);
21
- expect(resolveTextColor(theme, 'warning')).toBe(theme.semantics.warning.base);
19
+ expect(resolveTextColor(theme, 'default', 'danger')).toBe(theme.semantics.action.danger.base);
20
+ expect(resolveTextColor(theme, 'default', 'success')).toBe(theme.semantics.success.base);
21
+ expect(resolveTextColor(theme, 'default', 'warning')).toBe(theme.semantics.warning.base);
22
+ expect(resolveTextColor(theme, 'default', 'error')).toBe(theme.semantics.error.base);
23
+ expect(resolveTextColor(theme, 'default', 'info')).toBe(theme.semantics.info.base);
24
+ });
25
+
26
+ it('maps inverse emphasis with a semantic color to role foreground text', () => {
27
+ const theme = createTheme();
28
+
29
+ expect(resolveTextColor(theme, 'inverse', 'primary')).toBe(
30
+ theme.semantics.action.primary.onSolidText,
31
+ );
32
+ expect(resolveTextColor(theme, 'inverse', 'error')).toBe(theme.semantics.error.onSolidText);
22
33
  });
23
34
  });
@@ -1,39 +1,27 @@
1
- import { resolveToken } from '../../theme/resolveToken';
1
+ import type { SurfaceColor, SurfaceEmphasis } from '../../surfaceColor';
2
2
  import type { SurfaceTheme } from '../../theme/types';
3
-
4
- export type TextTone =
5
- | 'default'
6
- | 'muted'
7
- | 'subtle'
8
- | 'inverse'
9
- | 'danger'
10
- | 'success'
11
- | 'warning';
12
- export type TextColorValue = keyof SurfaceTheme['colors'] | string;
3
+ import { resolveSurfaceColor } from './resolveSurfaceColor';
13
4
 
14
5
  export function resolveTextColor(
15
6
  theme: SurfaceTheme,
16
- tone: TextTone = 'default',
17
- color?: TextColorValue,
7
+ emphasis: SurfaceEmphasis = 'default',
8
+ color?: SurfaceColor,
18
9
  ): string {
19
10
  if (color) {
20
- return resolveToken(theme.colors, color);
11
+ if (emphasis === 'inverse') {
12
+ return resolveSurfaceColor(theme, color).onSolidText;
13
+ }
14
+
15
+ return resolveSurfaceColor(theme, color).base;
21
16
  }
22
17
 
23
- switch (tone) {
18
+ switch (emphasis) {
24
19
  case 'muted':
25
20
  return theme.semantics.content.muted;
26
21
  case 'subtle':
27
22
  return theme.semantics.content.subtle;
28
23
  case 'inverse':
29
24
  return theme.semantics.content.inverse;
30
- case 'danger':
31
- return theme.semantics.danger.base;
32
- case 'success':
33
- return theme.semantics.success.base;
34
- case 'warning':
35
- return theme.semantics.warning.base;
36
- case 'default':
37
25
  default:
38
26
  return theme.semantics.content.default;
39
27
  }
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+ import {
3
+ ScrollView,
4
+ type ScrollViewProps as ReactNativeScrollViewProps,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ } from 'react-native';
8
+
9
+ import { useResponsiveRuntime } from '../core/responsive';
10
+ import { useTheme } from '../theme/ThemeContext';
11
+ import { type BoxStyleProps, resolveBoxStyles } from './helpers';
12
+
13
+ export interface ScrollAreaProps
14
+ extends
15
+ BoxStyleProps,
16
+ Omit<ReactNativeScrollViewProps, 'children' | 'contentContainerStyle' | 'style'> {
17
+ children?: React.ReactNode;
18
+ contentContainerStyle?: StyleProp<ViewStyle>;
19
+ }
20
+
21
+ export function ScrollArea({
22
+ children,
23
+ contentContainerStyle,
24
+ p,
25
+ px,
26
+ py,
27
+ pt,
28
+ pb,
29
+ pl,
30
+ pr,
31
+ m,
32
+ mx,
33
+ my,
34
+ mt,
35
+ mb,
36
+ ml,
37
+ mr,
38
+ bg,
39
+ radius,
40
+ borderWidth,
41
+ borderColor,
42
+ width,
43
+ height,
44
+ minWidth,
45
+ maxWidth,
46
+ minHeight,
47
+ maxHeight,
48
+ flex,
49
+ flexGrow,
50
+ flexShrink,
51
+ flexBasis,
52
+ alignSelf,
53
+ position,
54
+ top,
55
+ bottom,
56
+ left,
57
+ right,
58
+ overflow,
59
+ zIndex,
60
+ opacity,
61
+ style,
62
+ ...scrollProps
63
+ }: ScrollAreaProps) {
64
+ const { theme } = useTheme();
65
+ const { breakpoint } = useResponsiveRuntime();
66
+ const resolved = resolveBoxStyles(theme, breakpoint, {
67
+ p,
68
+ px,
69
+ py,
70
+ pt,
71
+ pb,
72
+ pl,
73
+ pr,
74
+ m,
75
+ mx,
76
+ my,
77
+ mt,
78
+ mb,
79
+ ml,
80
+ mr,
81
+ bg,
82
+ radius,
83
+ borderWidth,
84
+ borderColor,
85
+ width,
86
+ height,
87
+ minWidth,
88
+ maxWidth,
89
+ minHeight,
90
+ maxHeight,
91
+ flex,
92
+ flexGrow,
93
+ flexShrink,
94
+ flexBasis,
95
+ alignSelf,
96
+ position,
97
+ top,
98
+ bottom,
99
+ left,
100
+ right,
101
+ overflow,
102
+ zIndex,
103
+ opacity,
104
+ });
105
+
106
+ return (
107
+ <ScrollView
108
+ {...scrollProps}
109
+ contentContainerStyle={contentContainerStyle}
110
+ style={[resolved, style]}
111
+ >
112
+ {children}
113
+ </ScrollView>
114
+ );
115
+ }
@@ -12,6 +12,8 @@ export type { GridProps } from './Grid';
12
12
  export { Grid } from './Grid';
13
13
  export type { InlineProps } from './Inline';
14
14
  export { Inline } from './Inline';
15
+ export type { ScrollAreaProps } from './ScrollArea';
16
+ export { ScrollArea } from './ScrollArea';
15
17
  export type { ShowProps } from './Show';
16
18
  export { Show } from './Show';
17
19
  export type { SpacerProps } from './Spacer';
@@ -39,6 +39,8 @@ export const Heading: React.FC<HeadingProps> = ({
39
39
  children,
40
40
  level = 2,
41
41
  align,
42
+ color,
43
+ emphasis = 'default',
42
44
  numberOfLines,
43
45
  i18nKey,
44
46
  testID,
@@ -52,7 +54,7 @@ export const Heading: React.FC<HeadingProps> = ({
52
54
  accessibilityRole="header"
53
55
  testID={testID}
54
56
  numberOfLines={numberOfLines}
55
- style={resolveHeadingTextStyle(theme, level, align)}
57
+ style={resolveHeadingTextStyle(theme, level, align, emphasis, color)}
56
58
  >
57
59
  {content}
58
60
  </Text>
@@ -22,7 +22,7 @@ describe('resolveHeadingTextStyle', () => {
22
22
  expect(style.fontFamily).toBe('SpaceGrotesk_700Regular');
23
23
  });
24
24
 
25
- it('shares the default content tone with the text primitive', () => {
25
+ it('shares the default content emphasis with the text primitive', () => {
26
26
  const theme = createTheme();
27
27
  const style = resolveHeadingTextStyle(theme, 2);
28
28