@newtonedev/components 0.1.14 → 0.1.15
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/dist/composites/actions/Button/Button.d.ts +17 -30
- package/dist/composites/actions/Button/Button.d.ts.map +1 -1
- package/dist/composites/actions/Button/Button.styles.d.ts +12 -24
- package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
- package/dist/composites/actions/Button/Button.types.d.ts +4 -13
- package/dist/composites/actions/Button/Button.types.d.ts.map +1 -1
- package/dist/composites/actions/Button/index.d.ts +1 -1
- package/dist/composites/actions/Button/index.d.ts.map +1 -1
- package/dist/index.cjs +120 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +117 -216
- package/dist/index.js.map +1 -1
- package/dist/primitives/Frame/Frame.d.ts.map +1 -1
- package/dist/registry/registry.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/composites/actions/Button/Button.styles.ts +90 -198
- package/src/composites/actions/Button/Button.tsx +32 -49
- package/src/composites/actions/Button/Button.types.ts +4 -15
- package/src/composites/actions/Button/index.ts +1 -1
- package/src/index.ts +6 -1
- package/src/primitives/Frame/Frame.tsx +1 -0
- package/src/registry/registry.ts +5 -21
- package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts +0 -70
- package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts.map +0 -1
- package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +0 -23
- package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts.map +0 -1
- package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts +0 -45
- package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts.map +0 -1
- package/dist/_COMPONENT_TEMPLATE/index.d.ts +0 -3
- 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,
|
|
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,
|
|
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,7 +1,18 @@
|
|
|
1
1
|
import type { UseTokensResult } from 'newtone-api';
|
|
2
|
-
import type {
|
|
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:
|
|
58
|
-
md:
|
|
59
|
-
lg:
|
|
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;
|
|
67
|
+
const textExtra = 8;
|
|
64
68
|
|
|
65
|
-
// Icon-only:
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
130
|
-
*
|
|
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
|
|
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
|
-
|
|
135
|
+
height: BUTTON_HEIGHTS.sm,
|
|
136
|
+
padding: 12,
|
|
178
137
|
gap: tokens.spacing['08'],
|
|
179
138
|
borderRadius: 8,
|
|
180
|
-
textSize: 'md',
|
|
181
|
-
iconSize:
|
|
139
|
+
textSize: 'md',
|
|
140
|
+
iconSize: 20,
|
|
182
141
|
},
|
|
183
142
|
md: {
|
|
184
|
-
|
|
143
|
+
height: BUTTON_HEIGHTS.md,
|
|
144
|
+
padding: 16,
|
|
185
145
|
gap: tokens.spacing['08'],
|
|
186
146
|
borderRadius: 12,
|
|
187
|
-
textSize: 'md',
|
|
147
|
+
textSize: 'md',
|
|
188
148
|
iconSize: 24,
|
|
189
149
|
},
|
|
190
150
|
lg: {
|
|
191
|
-
|
|
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: '
|
|
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
|
-
|
|
181
|
+
const t = tokens.colors[theme];
|
|
182
|
+
|
|
213
183
|
if (disabled) {
|
|
214
|
-
const baseColors = getVariantColorsForState(variant,
|
|
215
|
-
const disabledBg = tokens.colors.primary.main.fontSecondary;
|
|
184
|
+
const baseColors = getVariantColorsForState(variant, tokens, theme);
|
|
216
185
|
return {
|
|
217
186
|
...baseColors,
|
|
218
|
-
bg:
|
|
219
|
-
hoveredBg:
|
|
220
|
-
pressedBg:
|
|
221
|
-
textColor:
|
|
222
|
-
iconColor:
|
|
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,
|
|
197
|
+
return getVariantColorsForState(variant, tokens, theme);
|
|
227
198
|
}
|
|
228
199
|
|
|
229
200
|
/**
|
|
230
201
|
* Get variant colors for non-disabled state.
|
|
231
|
-
*
|
|
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
|
-
|
|
237
|
-
|
|
206
|
+
tokens: UseTokensResult,
|
|
207
|
+
theme: ThemeName,
|
|
238
208
|
) {
|
|
239
|
-
|
|
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
|
-
|
|
254
|
-
|
|
211
|
+
// PRIMARY: Filled background using emphasis appearance
|
|
212
|
+
if (variant === 'primary') {
|
|
255
213
|
return {
|
|
256
|
-
bg: t.emphasis.
|
|
257
|
-
hoveredBg: t.emphasis.
|
|
258
|
-
pressedBg: t.emphasis.
|
|
259
|
-
textColor: t.
|
|
260
|
-
iconColor: t.
|
|
261
|
-
borderWidth:
|
|
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
|
|
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:
|
|
337
|
-
hoveredBg:
|
|
338
|
-
pressedBg:
|
|
339
|
-
textColor: t.
|
|
340
|
-
iconColor: t.
|
|
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
|
-
//
|
|
237
|
+
// GHOST: No border, transparent background, subtle hover
|
|
347
238
|
return {
|
|
348
239
|
bg: 'transparent',
|
|
349
|
-
hoveredBg:
|
|
350
|
-
pressedBg:
|
|
351
|
-
textColor:
|
|
352
|
-
iconColor:
|
|
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
|
|
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
|
-
* **
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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"
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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,
|
|
76
|
-
[variant,
|
|
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
|
-
//
|
|
68
|
+
// Ghost variant with no icon uses standard padding
|
|
81
69
|
const padding = React.useMemo(
|
|
82
|
-
() =>
|
|
83
|
-
|
|
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
|
-
|
|
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:
|
|
92
|
+
borderRadius: sizeTokens.borderRadius,
|
|
106
93
|
borderWidth: variantColors.borderWidth,
|
|
107
94
|
borderColor: variantColors.borderColor || 'transparent',
|
|
108
|
-
opacity: isDisabled ? (loading ? 0.6 : 0.4) :
|
|
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
|
|
4
|
+
* Visual variant for the Button component (describes visual weight)
|
|
5
5
|
*/
|
|
6
|
-
export type ButtonVariant = 'primary' | 'secondary' | '
|
|
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
|
|
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';
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
SUCCESS_DEFAULTS,
|
|
22
22
|
WARNING_DEFAULTS,
|
|
23
23
|
ERROR_DEFAULTS,
|
|
24
|
+
NON_PRIMARY_RELATIVE_DEFAULTS,
|
|
24
25
|
DEFAULT_THEME_MAPPINGS,
|
|
25
26
|
buildGoogleFontsUrl,
|
|
26
27
|
measureAvgCharWidth,
|
|
@@ -64,6 +65,10 @@ export type {
|
|
|
64
65
|
PaletteDefaults,
|
|
65
66
|
ContrastLevelDefaults,
|
|
66
67
|
SwatchDefaults,
|
|
68
|
+
RelativeContrastDefaults,
|
|
69
|
+
RelativePaletteDefaults,
|
|
70
|
+
PaletteKeySteps,
|
|
71
|
+
RelativeSwatchDefaults,
|
|
67
72
|
ResolvedTokens,
|
|
68
73
|
ResolvedSwatches,
|
|
69
74
|
PaletteColors,
|
|
@@ -126,7 +131,7 @@ export type {
|
|
|
126
131
|
|
|
127
132
|
// Composite Components
|
|
128
133
|
export { Button } from './composites/actions/Button/Button';
|
|
129
|
-
export type { ButtonProps, ButtonVariant,
|
|
134
|
+
export type { ButtonProps, ButtonVariant, ButtonSize, ButtonIconPosition } from './composites/actions/Button/Button.types';
|
|
130
135
|
|
|
131
136
|
export { Card } from './composites/layout/Card/Card';
|
|
132
137
|
export type { CardProps } from './composites/layout/Card/Card.types';
|