@newtonedev/components 0.1.14 → 0.1.16

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 (32) hide show
  1. package/dist/composites/actions/Button/Button.d.ts +17 -30
  2. package/dist/composites/actions/Button/Button.d.ts.map +1 -1
  3. package/dist/composites/actions/Button/Button.styles.d.ts +12 -24
  4. package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
  5. package/dist/composites/actions/Button/Button.types.d.ts +4 -13
  6. package/dist/composites/actions/Button/Button.types.d.ts.map +1 -1
  7. package/dist/composites/actions/Button/index.d.ts +1 -1
  8. package/dist/composites/actions/Button/index.d.ts.map +1 -1
  9. package/dist/index.cjs +120 -215
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +117 -216
  14. package/dist/index.js.map +1 -1
  15. package/dist/primitives/Frame/Frame.d.ts.map +1 -1
  16. package/dist/registry/registry.d.ts.map +1 -1
  17. package/package.json +1 -1
  18. package/src/composites/actions/Button/Button.styles.ts +90 -198
  19. package/src/composites/actions/Button/Button.tsx +32 -49
  20. package/src/composites/actions/Button/Button.types.ts +4 -15
  21. package/src/composites/actions/Button/index.ts +1 -1
  22. package/src/index.ts +7 -1
  23. package/src/primitives/Frame/Frame.tsx +1 -0
  24. package/src/registry/registry.ts +5 -21
  25. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts +0 -70
  26. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts.map +0 -1
  27. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +0 -23
  28. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts.map +0 -1
  29. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts +0 -45
  30. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts.map +0 -1
  31. package/dist/_COMPONENT_TEMPLATE/index.d.ts +0 -3
  32. package/dist/_COMPONENT_TEMPLATE/index.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Frame.d.ts","sourceRoot":"","sources":["../../../src/primitives/Frame/Frame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAuChD;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EAER,SAAS,EAET,MAAM,EAEN,KAAK,EACL,UAAU,EAEV,MAAM,EACN,SAAS,EACT,IAAI,EACJ,OAAO,EACP,OAAO,EACP,IAAI,EAEJ,KAAK,EACL,OAAO,EAEP,OAAO,EACP,GAAG,EAEH,KAAK,EACL,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EAET,QAAQ,EACR,GAAG,EACH,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,aAAa,EACb,OAAO,EAEP,MAAM,EACN,QAAQ,EAER,OAAO,EACP,IAAI,EACJ,QAAgB,EAEhB,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,EACN,QAAQ,EACR,GAAG,EAEH,KAAK,GACN,EAAE,UAAU,qBAuMZ"}
1
+ {"version":3,"file":"Frame.d.ts","sourceRoot":"","sources":["../../../src/primitives/Frame/Frame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAuChD;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EAER,SAAS,EAET,MAAM,EAEN,KAAK,EACL,UAAU,EAEV,MAAM,EACN,SAAS,EACT,IAAI,EACJ,OAAO,EACP,OAAO,EACP,IAAI,EAEJ,KAAK,EACL,OAAO,EAEP,OAAO,EACP,GAAG,EAEH,KAAK,EACL,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EAET,QAAQ,EACR,GAAG,EACH,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,aAAa,EACb,OAAO,EAEP,MAAM,EACN,QAAQ,EAER,OAAO,EACP,IAAI,EACJ,QAAgB,EAEhB,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,EACN,QAAQ,EACR,GAAG,EAEH,KAAK,GACN,EAAE,UAAU,qBAwMZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE3D,eAAO,MAAM,UAAU,EAAE,SAAS,YAAY,EAM7C,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,SAAS,aAAa,EAimB9C,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAElE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEhE;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAEpF"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE3D,eAAO,MAAM,UAAU,EAAE,SAAS,YAAY,EAM7C,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,SAAS,aAAa,EAilB9C,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAElE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEhE;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAEpF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newtonedev/components",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "React + React Native Web component library for Newtone",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,18 @@
1
1
  import type { UseTokensResult } from 'newtone-api';
2
- import type { ButtonVariant, ButtonSemantic, ButtonSize } from './Button.types';
2
+ import type { ThemeName } from 'newtone-api';
3
+ import type { ButtonVariant, ButtonSize } from './Button.types';
3
4
  import type { TextSize } from '../../../primitives/Text/Text.types';
4
5
 
6
+ /**
7
+ * Fixed heights per size (px)
8
+ */
9
+ export const BUTTON_HEIGHTS: Record<ButtonSize, number> = {
10
+ sm: 40,
11
+ md: 48,
12
+ lg: 56,
13
+ xl: 64,
14
+ };
15
+
5
16
  /**
6
17
  * Configuration returned by getButtonConfig
7
18
  */
@@ -16,6 +27,7 @@ export interface ButtonConfig {
16
27
  borderColor?: string;
17
28
  };
18
29
  sizeTokens: {
30
+ height: number;
19
31
  padding: number;
20
32
  gap: number;
21
33
  borderRadius: number;
@@ -30,8 +42,6 @@ export interface ButtonConfig {
30
42
  export interface ButtonPadding {
31
43
  paddingLeft: number;
32
44
  paddingRight: number;
33
- paddingTop: number;
34
- paddingBottom: number;
35
45
  }
36
46
 
37
47
  /**
@@ -39,12 +49,6 @@ export interface ButtonPadding {
39
49
  *
40
50
  * Text appears visually closer to borders than icons, so we add 8px extra
41
51
  * horizontal padding on the text side for optical balance.
42
- *
43
- * @param size - Button size (determines base padding)
44
- * @param hasIcon - Whether button has an icon
45
- * @param hasText - Whether button has text
46
- * @param iconPosition - Icon position relative to text
47
- * @returns Padding object with all four sides
48
52
  */
49
53
  export function computeButtonPadding(
50
54
  size: ButtonSize,
@@ -52,106 +56,59 @@ export function computeButtonPadding(
52
56
  hasText: boolean,
53
57
  iconPosition: 'left' | 'right'
54
58
  ): ButtonPadding {
55
- // Size-specific base padding
56
59
  const basePadding: Record<ButtonSize, number> = {
57
- sm: 8,
58
- md: 12,
59
- lg: 16,
60
+ sm: 12,
61
+ md: 16,
62
+ lg: 20,
63
+ xl: 24,
60
64
  };
61
65
 
62
66
  const base = basePadding[size];
63
- const textExtra = 8; // Optical correction for text vs icon
67
+ const textExtra = 8;
64
68
 
65
- // Icon-only: square button
69
+ // Icon-only: symmetric
66
70
  if (!hasText && hasIcon) {
67
- return {
68
- paddingLeft: base,
69
- paddingRight: base,
70
- paddingTop: base,
71
- paddingBottom: base,
72
- };
71
+ return { paddingLeft: base, paddingRight: base };
73
72
  }
74
73
 
75
74
  // Text-only: extra padding on both sides
76
75
  if (hasText && !hasIcon) {
77
- return {
78
- paddingLeft: base + textExtra,
79
- paddingRight: base + textExtra,
80
- paddingTop: base,
81
- paddingBottom: base,
82
- };
76
+ return { paddingLeft: base + textExtra, paddingRight: base + textExtra };
83
77
  }
84
78
 
85
79
  // Icon + text: extra padding on text side only
86
80
  if (hasText && hasIcon) {
87
81
  if (iconPosition === 'left') {
88
- return {
89
- paddingLeft: base,
90
- paddingRight: base + textExtra,
91
- paddingTop: base,
92
- paddingBottom: base,
93
- };
82
+ return { paddingLeft: base, paddingRight: base + textExtra };
94
83
  } else {
95
- return {
96
- paddingLeft: base + textExtra,
97
- paddingRight: base,
98
- paddingTop: base,
99
- paddingBottom: base,
100
- };
84
+ return { paddingLeft: base + textExtra, paddingRight: base };
101
85
  }
102
86
  }
103
87
 
104
- // Fallback: symmetric base padding
105
- return {
106
- paddingLeft: base,
107
- paddingRight: base,
108
- paddingTop: base,
109
- paddingBottom: base,
110
- };
88
+ return { paddingLeft: base, paddingRight: base };
111
89
  }
112
90
 
113
91
  /**
114
- * Map button semantic to theme name in the new token architecture.
115
- * 'accent' maps to 'primary'; others map directly.
116
- */
117
- function semanticToTheme(semantic: ButtonSemantic): 'primary' | 'success' | 'error' | 'warning' {
118
- if (semantic === 'accent') return 'primary';
119
- return semantic as 'success' | 'error' | 'warning';
120
- }
121
-
122
- /**
123
- * Compute button configuration based on variant, semantic, size, and state.
124
- *
125
- * This function ONLY computes variant colors + size tokens.
126
- * Layout concerns (flexbox, spacing, alignment) are handled by Wrapper primitive.
127
- * Typography concerns (font, size, weight) are handled by Text primitive.
92
+ * Compute button configuration based on variant, size, and state.
128
93
  *
129
- * Elevation-aware: neutral primary uses backgroundInteractive for consistent contrast.
130
- * Semantic variants use proper PaletteTokens no opacity hacks.
131
- *
132
- * @param variant - Button type (primary, secondary, tertiary)
133
- * @param semantic - Button semantic meaning (neutral, accent, success, error, warning)
134
- * @param size - Button size (sm, md, lg)
135
- * @param disabled - Whether button is disabled
136
- * @param tokens - Resolved tokens for current elevation
137
- * @returns ButtonConfig with variantColors and sizeTokens
94
+ * Colors are derived from the inherited theme (via FrameContext) rather than
95
+ * hardcoded to the primary palette. The `theme` parameter comes from the
96
+ * nearest Frame's `theme` prop, defaulting to 'primary'.
138
97
  */
139
98
  export function getButtonConfig(
140
99
  variant: ButtonVariant,
141
- semantic: ButtonSemantic,
142
100
  size: ButtonSize,
143
101
  disabled: boolean,
144
- tokens: UseTokensResult
102
+ tokens: UseTokensResult,
103
+ theme: ThemeName,
145
104
  ): ButtonConfig {
146
- // Get size configuration
147
105
  const sizeConfig = getSizeConfig(size, tokens);
148
-
149
- // Get variant-specific colors
150
- const variantColors = getVariantColors(variant, semantic, disabled, tokens);
106
+ const variantColors = getVariantColors(variant, disabled, tokens, theme);
151
107
 
152
108
  return {
153
109
  variantColors,
154
110
  sizeTokens: {
111
+ height: sizeConfig.height,
155
112
  padding: sizeConfig.padding,
156
113
  gap: sizeConfig.gap,
157
114
  borderRadius: sizeConfig.borderRadius,
@@ -162,11 +119,12 @@ export function getButtonConfig(
162
119
  }
163
120
 
164
121
  /**
165
- * Get size configuration with unified icon/text sizes across all sizes.
122
+ * Get size configuration. Heights are fixed per size.
166
123
  * Only padding and radius scale with size.
167
124
  */
168
125
  function getSizeConfig(size: ButtonSize, tokens: UseTokensResult) {
169
126
  const configs: Record<ButtonSize, {
127
+ height: number;
170
128
  padding: number;
171
129
  gap: number;
172
130
  borderRadius: number;
@@ -174,24 +132,35 @@ function getSizeConfig(size: ButtonSize, tokens: UseTokensResult) {
174
132
  iconSize: number;
175
133
  }> = {
176
134
  sm: {
177
- padding: 8,
135
+ height: BUTTON_HEIGHTS.sm,
136
+ padding: 12,
178
137
  gap: tokens.spacing['08'],
179
138
  borderRadius: 8,
180
- textSize: 'md', // 16px
181
- iconSize: 24,
139
+ textSize: 'md',
140
+ iconSize: 20,
182
141
  },
183
142
  md: {
184
- padding: 12,
143
+ height: BUTTON_HEIGHTS.md,
144
+ padding: 16,
185
145
  gap: tokens.spacing['08'],
186
146
  borderRadius: 12,
187
- textSize: 'md', // 16px
147
+ textSize: 'md',
188
148
  iconSize: 24,
189
149
  },
190
150
  lg: {
191
- padding: 16,
151
+ height: BUTTON_HEIGHTS.lg,
152
+ padding: 20,
153
+ gap: tokens.spacing['08'],
154
+ borderRadius: 14,
155
+ textSize: 'md',
156
+ iconSize: 24,
157
+ },
158
+ xl: {
159
+ height: BUTTON_HEIGHTS.xl,
160
+ padding: 24,
192
161
  gap: tokens.spacing['08'],
193
162
  borderRadius: 16,
194
- textSize: 'md', // 16px
163
+ textSize: 'lg',
195
164
  iconSize: 24,
196
165
  },
197
166
  };
@@ -200,156 +169,79 @@ function getSizeConfig(size: ButtonSize, tokens: UseTokensResult) {
200
169
  }
201
170
 
202
171
  /**
203
- * Get variant-specific colors from theme tokens.
172
+ * Get variant-specific colors from the inherited theme's tokens.
204
173
  * Handles disabled state override for all variants.
205
174
  */
206
175
  function getVariantColors(
207
176
  variant: ButtonVariant,
208
- semantic: ButtonSemantic,
209
177
  disabled: boolean,
210
- tokens: UseTokensResult
178
+ tokens: UseTokensResult,
179
+ theme: ThemeName,
211
180
  ) {
212
- // Disabled state overrides for all variants
181
+ const t = tokens.colors[theme];
182
+
213
183
  if (disabled) {
214
- const baseColors = getVariantColorsForState(variant, semantic, tokens);
215
- const disabledBg = tokens.colors.primary.main.fontSecondary;
184
+ const baseColors = getVariantColorsForState(variant, tokens, theme);
216
185
  return {
217
186
  ...baseColors,
218
- bg: disabledBg,
219
- hoveredBg: disabledBg,
220
- pressedBg: disabledBg,
221
- textColor: tokens.colors.primary.main.fontTertiary,
222
- iconColor: tokens.colors.primary.main.fontTertiary,
187
+ bg: 'transparent',
188
+ hoveredBg: 'transparent',
189
+ pressedBg: 'transparent',
190
+ textColor: t.main.fontDisabled,
191
+ iconColor: t.main.fontDisabled,
192
+ borderWidth: 1,
193
+ borderColor: t.main.divider,
223
194
  };
224
195
  }
225
196
 
226
- return getVariantColorsForState(variant, semantic, tokens);
197
+ return getVariantColorsForState(variant, tokens, theme);
227
198
  }
228
199
 
229
200
  /**
230
201
  * Get variant colors for non-disabled state.
231
- * Implements 4 types × 5 semantics = 20 combinations.
232
- * Uses proper PaletteTokens for all semantic variants — no opacity hacks.
202
+ * Uses the inherited theme's tokens no semantic prop needed.
233
203
  */
234
204
  function getVariantColorsForState(
235
205
  variant: ButtonVariant,
236
- semantic: ButtonSemantic,
237
- tokens: UseTokensResult
206
+ tokens: UseTokensResult,
207
+ theme: ThemeName,
238
208
  ) {
239
- // PRIMARY VARIANT: Filled background
240
- if (variant === 'primary') {
241
- if (semantic === 'neutral') {
242
- return {
243
- bg: tokens.colors.primary.main.fontPrimary,
244
- hoveredBg: tokens.colors.primary.main.fontSecondary,
245
- pressedBg: tokens.colors.primary.main.fontPrimary,
246
- textColor: tokens.colors.primary.main.divider,
247
- iconColor: tokens.colors.primary.main.divider,
248
- borderWidth: 1,
249
- borderColor: 'transparent',
250
- };
251
- }
209
+ const t = tokens.colors[theme];
252
210
 
253
- // Semantic primary (accent, success, error, warning)
254
- const t = tokens.colors[semanticToTheme(semantic)];
211
+ // PRIMARY: Filled background using emphasis appearance
212
+ if (variant === 'primary') {
255
213
  return {
256
- bg: t.emphasis.background,
257
- hoveredBg: t.emphasis.fontPrimary,
258
- pressedBg: t.emphasis.fontSecondary,
259
- textColor: t.main.background,
260
- iconColor: t.main.background,
261
- borderWidth: 1,
214
+ bg: t.emphasis.actionEnabled,
215
+ hoveredBg: t.emphasis.actionHovered,
216
+ pressedBg: t.emphasis.actionPressed,
217
+ textColor: t.emphasis.accentSmall,
218
+ iconColor: t.emphasis.accentSmall,
219
+ borderWidth: 0,
262
220
  borderColor: 'transparent',
263
221
  };
264
222
  }
265
223
 
266
- // SECONDARY VARIANT: Outlined (border + subtle background for non-neutral)
224
+ // SECONDARY: Outlined with subtle hover
267
225
  if (variant === 'secondary') {
268
- if (semantic === 'neutral') {
269
- return {
270
- bg: 'transparent',
271
- hoveredBg: tokens.colors.primary.main.fontPrimary,
272
- pressedBg: tokens.colors.primary.main.fontSecondary,
273
- textColor: tokens.colors.primary.main.divider,
274
- iconColor: tokens.colors.primary.main.divider,
275
- borderWidth: 1,
276
- borderColor: tokens.colors.primary.main.fontSecondary,
277
- };
278
- }
279
-
280
- // Semantic secondary
281
- const t = tokens.colors[semanticToTheme(semantic)];
282
- return {
283
- bg: t.tinted.background,
284
- hoveredBg: t.tinted.fontPrimary,
285
- pressedBg: t.tinted.fontSecondary,
286
- textColor: t.emphasis.divider,
287
- iconColor: t.emphasis.divider,
288
- borderWidth: 1,
289
- borderColor: 'transparent',
290
- };
291
- }
292
-
293
- // TERTIARY VARIANT: Ghost (text-only, no visible border)
294
- if (variant === 'tertiary') {
295
- if (semantic === 'neutral') {
296
- return {
297
- bg: 'transparent',
298
- hoveredBg: tokens.colors.primary.main.fontPrimary,
299
- pressedBg: tokens.colors.primary.main.fontSecondary,
300
- textColor: tokens.colors.primary.main.divider,
301
- iconColor: tokens.colors.primary.main.divider,
302
- borderWidth: 1,
303
- borderColor: 'transparent',
304
- };
305
- }
306
-
307
- // Semantic tertiary
308
- const t = tokens.colors[semanticToTheme(semantic)];
309
- return {
310
- bg: 'transparent',
311
- hoveredBg: t.tinted.background,
312
- pressedBg: t.tinted.fontPrimary,
313
- textColor: t.emphasis.divider,
314
- iconColor: t.emphasis.divider,
315
- borderWidth: 1,
316
- borderColor: 'transparent',
317
- };
318
- }
319
-
320
- // LINK VARIANT: Text-only with underline on hover, no background
321
- if (variant === 'link') {
322
- if (semantic === 'neutral') {
323
- return {
324
- bg: 'transparent',
325
- hoveredBg: 'transparent',
326
- pressedBg: 'transparent',
327
- textColor: tokens.colors.primary.main.fontSecondary,
328
- iconColor: tokens.colors.primary.main.fontSecondary,
329
- borderWidth: 0,
330
- borderColor: 'transparent',
331
- };
332
- }
333
-
334
- const t = tokens.colors[semanticToTheme(semantic)];
335
226
  return {
336
- bg: 'transparent',
337
- hoveredBg: 'transparent',
338
- pressedBg: 'transparent',
339
- textColor: t.emphasis.divider,
340
- iconColor: t.emphasis.divider,
227
+ bg: t.main.actionEnabled,
228
+ hoveredBg: t.main.actionHovered,
229
+ pressedBg: t.main.actionPressed,
230
+ textColor: t.main.fontPrimary,
231
+ iconColor: t.main.fontPrimary,
341
232
  borderWidth: 0,
342
233
  borderColor: 'transparent',
343
234
  };
344
235
  }
345
236
 
346
- // Fallback (should never reach here with proper types)
237
+ // GHOST: No border, transparent background, subtle hover
347
238
  return {
348
239
  bg: 'transparent',
349
- hoveredBg: 'transparent',
350
- pressedBg: 'transparent',
351
- textColor: tokens.colors.primary.main.divider,
352
- iconColor: tokens.colors.primary.main.divider,
240
+ hoveredBg: t.main.actionEnabled,
241
+ pressedBg: t.main.actionHovered,
242
+ textColor: t.main.fontSecondary,
243
+ iconColor: t.main.fontSecondary,
353
244
  borderWidth: 0,
245
+ borderColor: 'transparent',
354
246
  };
355
247
  }
@@ -3,51 +3,39 @@ import { Pressable } from 'react-native';
3
3
  import type { ButtonProps } from './Button.types';
4
4
  import { getButtonConfig, computeButtonPadding } from './Button.styles';
5
5
  import { useTokens } from 'newtone-api';
6
+ import { useFrameContext } from 'newtone-api';
6
7
  import { Icon } from '../../../primitives/Icon/Icon';
7
8
  import { Text } from '../../../primitives/Text';
8
9
  import { Wrapper } from '../../../primitives/Wrapper/Wrapper';
9
10
 
10
11
  /**
11
- * Button — A composite component demonstrating the primitive composition pattern.
12
+ * Button — A composite component that inherits its color theme from the
13
+ * nearest parent Frame's `theme` prop (defaults to 'primary').
12
14
  *
13
- * **Composition Architecture:**
14
- * - Pressable (RN primitive) handles interaction
15
- * - Wrapper (primitive) handles layout (direction, gap, padding, alignment)
16
- * - Icon (primitive) handles icon rendering with theme tokens
17
- * - Text (primitive) — handles typography with theme tokens
15
+ * **Variants:**
16
+ * - `primary` Filled background (highest visual weight)
17
+ * - `secondary`Outlined with subtle hover
18
+ * - `ghost`No border, transparent, subtle hover
18
19
  *
19
- * This component does NOT manually compute flexbox, spacing, or typography styles.
20
- * Instead, it delegates to primitives which already handle these concerns.
21
- *
22
- * Automatically adapts to the current theme and mode from NewtoneProvider.
20
+ * **Sizes (fixed heights):**
21
+ * - `sm` 40px
22
+ * - `md` — 48px
23
+ * - `lg` 56px
24
+ * - `xl` — 64px
23
25
  *
24
26
  * @example
25
27
  * ```tsx
26
- * <Button variant="primary" semantic="accent" size="md" onPress={() => console.log('Pressed')}>
28
+ * <Button variant="primary" size="md" onPress={() => console.log('Pressed')}>
27
29
  * Click me
28
30
  * </Button>
29
31
  * ```
30
32
  *
31
33
  * @example
32
34
  * ```tsx
33
- * <Button icon="add" variant="primary" semantic="accent" onPress={handleAdd}>
34
- * New Item
35
- * </Button>
36
- * ```
37
- *
38
- * @example
39
- * ```tsx
40
- * <Button icon="delete" variant="tertiary" semantic="neutral" size="sm" onPress={handleDelete} />
41
- * ```
42
- *
43
- * @example
44
- * ```tsx
45
- * <Button variant="link" semantic="accent" onPress={handleNav}>Learn more</Button>
46
- * ```
47
- *
48
- * @example
49
- * ```tsx
50
- * <Button loading onPress={handleSubmit}>Saving...</Button>
35
+ * // Inherits theme from parent Frame
36
+ * <Frame theme="error">
37
+ * <Button variant="primary">Delete</Button>
38
+ * </Frame>
51
39
  * ```
52
40
  */
53
41
  export function Button({
@@ -55,7 +43,6 @@ export function Button({
55
43
  icon,
56
44
  iconPosition = 'left',
57
45
  variant = 'primary',
58
- semantic = 'neutral',
59
46
  size = 'md',
60
47
  disabled = false,
61
48
  loading = false,
@@ -64,31 +51,29 @@ export function Button({
64
51
  textStyle,
65
52
  ...pressableProps
66
53
  }: ButtonProps) {
67
- // Read tokens from current elevation context
68
54
  const tokens = useTokens();
55
+ const frameCtx = useFrameContext();
56
+
57
+ // Inherit theme from nearest Frame, default to 'primary'
58
+ const theme = frameCtx?.theme ?? 'primary';
69
59
 
70
- // Loading implies disabled
71
60
  const isDisabled = disabled || loading;
72
61
 
73
- // Get color tokens + size config
74
62
  const { variantColors, sizeTokens } = React.useMemo(
75
- () => getButtonConfig(variant, semantic, size, isDisabled, tokens),
76
- [variant, semantic, size, isDisabled, tokens]
63
+ () => getButtonConfig(variant, size, isDisabled, tokens, theme),
64
+ [variant, size, isDisabled, tokens, theme]
77
65
  );
78
66
 
79
67
  // Compute asymmetric padding (+8px on text side for optical balance)
80
- // Link variant uses minimal padding
68
+ // Ghost variant with no icon uses standard padding
81
69
  const padding = React.useMemo(
82
- () => variant === 'link'
83
- ? { paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }
84
- : computeButtonPadding(size, !!icon, !!children, iconPosition),
85
- [size, icon, children, iconPosition, variant]
70
+ () => computeButtonPadding(size, !!icon, !!children, iconPosition),
71
+ [size, icon, children, iconPosition]
86
72
  );
87
73
 
88
74
  return (
89
75
  <Pressable disabled={isDisabled} {...pressableProps}>
90
76
  {({ pressed, hovered }: { pressed: boolean; hovered?: boolean }) => (
91
- // Wrapper handles layout: direction, gap, alignment (padding via style)
92
77
  <Wrapper
93
78
  direction="horizontal"
94
79
  align="center"
@@ -96,16 +81,18 @@ export function Button({
96
81
  gap={sizeTokens.gap}
97
82
  style={[
98
83
  {
99
- ...padding, // Asymmetric horizontal padding for text optical balance
84
+ height: sizeTokens.height,
85
+ paddingLeft: padding.paddingLeft,
86
+ paddingRight: padding.paddingRight,
100
87
  backgroundColor: pressed && !isDisabled
101
88
  ? variantColors.pressedBg
102
89
  : hovered && !isDisabled
103
90
  ? variantColors.hoveredBg
104
91
  : variantColors.bg,
105
- borderRadius: variant === 'link' ? 0 : sizeTokens.borderRadius,
92
+ borderRadius: sizeTokens.borderRadius,
106
93
  borderWidth: variantColors.borderWidth,
107
94
  borderColor: variantColors.borderColor || 'transparent',
108
- opacity: isDisabled ? (loading ? 0.6 : 0.4) : (variant === 'link' && pressed ? 0.7 : 1),
95
+ opacity: isDisabled ? (loading ? 0.6 : 0.4) : 1,
109
96
  ...(fullWidth && { width: '100%' as any, alignSelf: 'stretch' as any }),
110
97
  },
111
98
  ...(Array.isArray(style) ? style : style ? [style] : []),
@@ -115,16 +102,12 @@ export function Button({
115
102
  <Icon name={icon} size={sizeTokens.iconSize} color={variantColors.iconColor} />
116
103
  )}
117
104
  {children && (
118
- // Text primitive with semantic props + color style override
119
105
  <Text
120
106
  role="label"
121
107
  size={sizeTokens.textSize}
122
108
  centerVertically
123
109
  style={[
124
- {
125
- color: variantColors.textColor,
126
- ...(variant === 'link' && hovered && { textDecorationLine: 'underline' as any }),
127
- },
110
+ { color: variantColors.textColor },
128
111
  ...(Array.isArray(textStyle) ? textStyle : textStyle ? [textStyle] : []),
129
112
  ]}
130
113
  >
@@ -1,19 +1,14 @@
1
1
  import type { PressableProps, ViewStyle, TextStyle } from 'react-native';
2
2
 
3
3
  /**
4
- * Visual type for the Button component (describes visual weight)
4
+ * Visual variant for the Button component (describes visual weight)
5
5
  */
6
- export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'link';
7
-
8
- /**
9
- * Semantic meaning for the Button component (describes color purpose)
10
- */
11
- export type ButtonSemantic = 'neutral' | 'accent' | 'success' | 'error' | 'warning';
6
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost';
12
7
 
13
8
  /**
14
9
  * Size presets for the Button component
15
10
  */
16
- export type ButtonSize = 'sm' | 'md' | 'lg';
11
+ export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
17
12
 
18
13
  /**
19
14
  * Icon position relative to button text
@@ -43,17 +38,11 @@ export interface ButtonProps extends Omit<PressableProps, 'children' | 'style'>
43
38
  readonly iconPosition?: ButtonIconPosition;
44
39
 
45
40
  /**
46
- * Visual type (describes visual weight: filled, outlined, ghost)
41
+ * Visual variant (describes visual weight: filled, outlined, ghost)
47
42
  * @default 'primary'
48
43
  */
49
44
  readonly variant?: ButtonVariant;
50
45
 
51
- /**
52
- * Semantic meaning (describes color purpose: neutral, accent, success, error, warning)
53
- * @default 'neutral'
54
- */
55
- readonly semantic?: ButtonSemantic;
56
-
57
46
  /**
58
47
  * Size preset
59
48
  * @default 'md'
@@ -1,2 +1,2 @@
1
1
  export { Button } from './Button';
2
- export type { ButtonProps, ButtonVariant, ButtonSize } from './Button.types';
2
+ export type { ButtonProps, ButtonVariant, ButtonSize, ButtonIconPosition } from './Button.types';