@ornikar/bumper 3.7.0 → 3.7.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 +8 -0
- package/docs/migration/Button.md +502 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.7.1](https://github.com/ornikar/kitt/compare/@ornikar/bumper@3.7.0...@ornikar/bumper@3.7.1) (2026-03-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @ornikar/bumper
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [3.7.0](https://github.com/ornikar/kitt/compare/@ornikar/bumper@3.6.2...@ornikar/bumper@3.7.0) (2026-03-17)
|
|
7
15
|
|
|
8
16
|
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Button → Button Migration Guide
|
|
2
|
+
|
|
3
|
+
> Source: `@ornikar/kitt-universal/Button`
|
|
4
|
+
> Target: `@ornikar/bumper/Button`
|
|
5
|
+
> Generated: 2026-03-20
|
|
6
|
+
|
|
7
|
+
## Import Change
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Before
|
|
11
|
+
import { Button } from '@ornikar/kitt-universal';
|
|
12
|
+
import type { ButtonProps } from '@ornikar/kitt-universal';
|
|
13
|
+
|
|
14
|
+
// After
|
|
15
|
+
import { Button } from '@ornikar/bumper';
|
|
16
|
+
import type { ButtonProps } from '@ornikar/bumper';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Also remove any `LoaderIcon` import used solely for Button loading patterns — bumper has a native `isLoading` prop.
|
|
20
|
+
|
|
21
|
+
## Props Mapping
|
|
22
|
+
|
|
23
|
+
| Source Prop | Target Prop | Transform | Notes |
|
|
24
|
+
| ----------------------------------------------------------------------- | --------------------------------------- | -------------------------------------- | ----------------------------------------------------------------- |
|
|
25
|
+
| `children` (direct) | `<Button.Text>{children}</Button.Text>` | Wrap in compound component | See [Children & Composition](#children--composition) |
|
|
26
|
+
| `type="primary"` | `type="primary"` | Keep as-is | Same value |
|
|
27
|
+
| `type="secondary"` | `type="secondary"` | Keep as-is | Same value (also the default) |
|
|
28
|
+
| `type="tertiary"` | `type="tertiary"` | Keep as-is | Same value |
|
|
29
|
+
| `type="tertiary-danger"` | `type="danger"` | Rename value | Not used in learner-apps |
|
|
30
|
+
| `variant="default"` | — | Remove prop | Default behavior, no equivalent needed |
|
|
31
|
+
| `variant="revert"` | `isOnContrasted` | Replace prop + value with boolean prop | See [Variant Mapping](#variant-mapping) |
|
|
32
|
+
| `size="default"` | — | Remove prop | Maps to `large` which is the default |
|
|
33
|
+
| `size="medium"` | — | Remove prop | Maps to `large` which is the default |
|
|
34
|
+
| `disabled` | `disabled` | Keep as-is | Same API |
|
|
35
|
+
| `stretch` (boolean) | `stretch` | Keep as-is | Same API when static boolean |
|
|
36
|
+
| `stretch` (responsive object) | `stretch` + `$medium`/`$small`/etc. | Convert responsive syntax | See [Responsive Stretch](#responsive-stretch) |
|
|
37
|
+
| `icon={<Icon />}` | `<Button.Icon icon={<Icon />} />` | Convert prop to compound child | See [Icon Migration](#icon-migration) |
|
|
38
|
+
| `iconPosition="left"` | — | Remove prop | Icon position determined by child order (Icon before Text = left) |
|
|
39
|
+
| `iconPosition="right"` | — | Remove prop | Place `Button.Icon` after `Button.Text` |
|
|
40
|
+
| `icon={isLoading ? <LoaderIcon /> : <Icon />}` + `disabled={isLoading}` | `isLoading` | Replace pattern with single prop | See [Loading Pattern](#loading-pattern) |
|
|
41
|
+
| `withBadge` | `<Button.Badge />` | Convert prop to compound child | Not used in learner-apps |
|
|
42
|
+
| `badgeCount={n}` | `<Button.Badge count={n} />` | Convert prop to compound child | Not used in learner-apps |
|
|
43
|
+
| `timerAttrs` | — | **Not migratable** | No bumper equivalent. Requires manual review |
|
|
44
|
+
| `testID` | `testID` | Keep as-is | Same API |
|
|
45
|
+
| `onPress` | `onPress` | Keep as-is | Same API |
|
|
46
|
+
| `onFocus` | — | Remove prop | Not exposed in bumper ButtonProps |
|
|
47
|
+
| `onBlur` | — | Remove prop | Not exposed in bumper ButtonProps |
|
|
48
|
+
| `onHoverIn` | — | Remove prop | Not exposed in bumper ButtonProps |
|
|
49
|
+
| `onHoverOut` | — | Remove prop | Not exposed in bumper ButtonProps |
|
|
50
|
+
| `href` | — | Remove prop | Not used in learner-apps. Navigation uses InternalLink wrappers |
|
|
51
|
+
| `hrefAttrs` | — | Remove prop | Not used in learner-apps |
|
|
52
|
+
| `accessibilityRole` | — | Remove prop | Bumper sets `role="button"` automatically |
|
|
53
|
+
| `innerSpacing` | — | Remove prop | Not used in learner-apps |
|
|
54
|
+
| `style` | — | Remove prop | Not supported. Use Tamagui style props if needed |
|
|
55
|
+
| `isHoveredInternal` | — | Remove prop | Internal/storybook only |
|
|
56
|
+
| `isPressedInternal` | — | Remove prop | Internal/storybook only |
|
|
57
|
+
| `isFocusedInternal` | — | Remove prop | Internal/storybook only |
|
|
58
|
+
|
|
59
|
+
## Value Mappings
|
|
60
|
+
|
|
61
|
+
### Type Values
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
primary → primary
|
|
65
|
+
secondary → secondary (default in both)
|
|
66
|
+
tertiary → tertiary
|
|
67
|
+
tertiary-danger → danger (not used in learner-apps)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Size Values
|
|
71
|
+
|
|
72
|
+
All sizes map to `large` (the default), so remove the `size` prop entirely:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
default → (remove prop) — large is the default
|
|
76
|
+
medium → (remove prop) — large is the default
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Variant Mapping
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
variant="default" → (remove prop entirely)
|
|
83
|
+
variant="revert" → isOnContrasted
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
When `variant="revert"` also remove the `variant` prop itself:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// Before
|
|
90
|
+
<Button type="primary" variant="revert" onPress={onClose}>
|
|
91
|
+
|
|
92
|
+
// After
|
|
93
|
+
<Button type="primary" isOnContrasted onPress={onClose}>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Children & Composition
|
|
97
|
+
|
|
98
|
+
The biggest change: bumper Button uses a **compound component pattern**. All text children must be wrapped in `Button.Text`.
|
|
99
|
+
|
|
100
|
+
### Basic text children
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// Before
|
|
104
|
+
<Button type="primary" onPress={onPress}>
|
|
105
|
+
<FormattedMessage defaultMessage="Continue" />
|
|
106
|
+
</Button>
|
|
107
|
+
|
|
108
|
+
// After
|
|
109
|
+
<Button type="primary" onPress={onPress}>
|
|
110
|
+
<Button.Text>
|
|
111
|
+
<FormattedMessage defaultMessage="Continue" />
|
|
112
|
+
</Button.Text>
|
|
113
|
+
</Button>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Multiple children / mixed content
|
|
117
|
+
|
|
118
|
+
If the source Button has multiple children (e.g., text nodes), wrap them all in a single `Button.Text`:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// Before
|
|
122
|
+
<Button type="primary" onPress={onPress}>
|
|
123
|
+
{label}
|
|
124
|
+
</Button>
|
|
125
|
+
|
|
126
|
+
// After
|
|
127
|
+
<Button type="primary" onPress={onPress}>
|
|
128
|
+
<Button.Text>{label}</Button.Text>
|
|
129
|
+
</Button>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Icon-only button (no text children)
|
|
133
|
+
|
|
134
|
+
If the source Button has only an `icon` prop and no `children`, use only `Button.Icon`:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// Before
|
|
138
|
+
<Button icon={<ReplayIcon />} type="primary" onPress={onReplay} />
|
|
139
|
+
|
|
140
|
+
// After
|
|
141
|
+
<Button type="primary" onPress={onReplay}>
|
|
142
|
+
<Button.Icon icon={<ReplayIcon />} />
|
|
143
|
+
</Button>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Note**: Consider migrating icon-only buttons to `IconButton` from bumper instead, depending on design intent.
|
|
147
|
+
|
|
148
|
+
## Icon Migration
|
|
149
|
+
|
|
150
|
+
### Icon with text (left position — default)
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// Before
|
|
154
|
+
<Button type="primary" icon={<ArrowRightIcon />} onPress={onNext}>
|
|
155
|
+
<FormattedMessage defaultMessage="Next" />
|
|
156
|
+
</Button>
|
|
157
|
+
|
|
158
|
+
// After
|
|
159
|
+
<Button type="primary" onPress={onNext}>
|
|
160
|
+
<Button.Icon icon={<ArrowRightIcon />} />
|
|
161
|
+
<Button.Text>
|
|
162
|
+
<FormattedMessage defaultMessage="Next" />
|
|
163
|
+
</Button.Text>
|
|
164
|
+
</Button>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Icon with text (right position)
|
|
168
|
+
|
|
169
|
+
Place `Button.Icon` **after** `Button.Text`:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// Before
|
|
173
|
+
<Button icon={<ArrowRightIcon />} iconPosition="right" onPress={onNext}>
|
|
174
|
+
<FormattedMessage defaultMessage="Next" />
|
|
175
|
+
</Button>
|
|
176
|
+
|
|
177
|
+
// After
|
|
178
|
+
<Button onPress={onNext}>
|
|
179
|
+
<Button.Text>
|
|
180
|
+
<FormattedMessage defaultMessage="Next" />
|
|
181
|
+
</Button.Text>
|
|
182
|
+
<Button.Icon icon={<ArrowRightIcon />} />
|
|
183
|
+
</Button>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Loading Pattern
|
|
187
|
+
|
|
188
|
+
The pervasive `LoaderIcon` + `disabled` pattern in kitt-universal maps to bumper's native `isLoading` prop:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// Before
|
|
192
|
+
<Button
|
|
193
|
+
type="primary"
|
|
194
|
+
icon={isLoading ? <LoaderIcon /> : <ArrowRightIcon />}
|
|
195
|
+
disabled={isLoading}
|
|
196
|
+
onPress={handleSubmit}
|
|
197
|
+
>
|
|
198
|
+
<FormattedMessage defaultMessage="Submit" />
|
|
199
|
+
</Button>
|
|
200
|
+
|
|
201
|
+
// After
|
|
202
|
+
<Button type="primary" isLoading={isLoading} onPress={handleSubmit}>
|
|
203
|
+
<Button.Icon icon={<ArrowRightIcon />} />
|
|
204
|
+
<Button.Text>
|
|
205
|
+
<FormattedMessage defaultMessage="Submit" />
|
|
206
|
+
</Button.Text>
|
|
207
|
+
</Button>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Important**: When `isLoading` is true, bumper hides all children (with `opacity: 0`) and shows a centered spinner. The button preserves its original dimensions. There is no need to conditionally swap icon elements.
|
|
211
|
+
|
|
212
|
+
If the source only uses `LoaderIcon` without a fallback icon (icon-only loading):
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// Before
|
|
216
|
+
<Button icon={isLoading ? <LoaderIcon /> : undefined} disabled={isLoading} onPress={onPress}>
|
|
217
|
+
<FormattedMessage defaultMessage="Action" />
|
|
218
|
+
</Button>
|
|
219
|
+
|
|
220
|
+
// After
|
|
221
|
+
<Button isLoading={isLoading} onPress={onPress}>
|
|
222
|
+
<Button.Text>
|
|
223
|
+
<FormattedMessage defaultMessage="Action" />
|
|
224
|
+
</Button.Text>
|
|
225
|
+
</Button>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
If `disabled` is used for reasons **beyond** loading (e.g., form validation), keep it alongside `isLoading`:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
// Before
|
|
232
|
+
<Button
|
|
233
|
+
disabled={isLoading || !isFormValid}
|
|
234
|
+
icon={isLoading ? <LoaderIcon /> : <CheckIcon />}
|
|
235
|
+
onPress={onSubmit}
|
|
236
|
+
>
|
|
237
|
+
Submit
|
|
238
|
+
</Button>
|
|
239
|
+
|
|
240
|
+
// After
|
|
241
|
+
<Button
|
|
242
|
+
isLoading={isLoading}
|
|
243
|
+
disabled={!isFormValid}
|
|
244
|
+
onPress={onSubmit}
|
|
245
|
+
>
|
|
246
|
+
<Button.Icon icon={<CheckIcon />} />
|
|
247
|
+
<Button.Text>Submit</Button.Text>
|
|
248
|
+
</Button>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Responsive Stretch
|
|
252
|
+
|
|
253
|
+
kitt-universal uses `ResponsiveValue<boolean>` objects. Bumper uses Tamagui media query props.
|
|
254
|
+
|
|
255
|
+
### Breakpoint name mapping
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
base → (base prop, no prefix)
|
|
259
|
+
small → $small
|
|
260
|
+
medium → $medium
|
|
261
|
+
large → $large
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Conversion rules
|
|
265
|
+
|
|
266
|
+
The base value becomes the direct prop. Override values become media query props.
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
// Before: stretch={{ base: true, small: false }}
|
|
270
|
+
// After:
|
|
271
|
+
<Button stretch $small={{ stretch: false }}>
|
|
272
|
+
|
|
273
|
+
// Before: stretch={{ base: true, large: false }}
|
|
274
|
+
// After:
|
|
275
|
+
<Button stretch $large={{ stretch: false }}>
|
|
276
|
+
|
|
277
|
+
// Before: stretch={{ base: true, medium: false }}
|
|
278
|
+
// After:
|
|
279
|
+
<Button stretch $medium={{ stretch: false }}>
|
|
280
|
+
|
|
281
|
+
// Before: stretch={true}
|
|
282
|
+
// After:
|
|
283
|
+
<Button stretch>
|
|
284
|
+
|
|
285
|
+
// Before: stretch={false} or stretch={undefined}
|
|
286
|
+
// After:
|
|
287
|
+
// (remove the prop entirely)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Dynamic responsive values
|
|
291
|
+
|
|
292
|
+
If `stretch` receives a variable (not an object literal), it cannot be mechanically converted. Flag for manual review:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
// Before
|
|
296
|
+
<Button stretch={responsiveStretch} onPress={onPress}>
|
|
297
|
+
|
|
298
|
+
// After — MANUAL REVIEW NEEDED
|
|
299
|
+
// Must convert ResponsiveValue<boolean> to Tamagui media query props
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Behavioral Differences
|
|
303
|
+
|
|
304
|
+
| Aspect | kitt-universal | bumper |
|
|
305
|
+
| ----------------------------- | --------------------------------------------- | --------------------------------------------- | ----------------------------------------------- |
|
|
306
|
+
| **Default type** | `secondary` | `secondary` | Same |
|
|
307
|
+
| **Default size** | `default` (40px min-height) | `large` (48px) | Slightly larger |
|
|
308
|
+
| **Border radius** | 4px | `$radius.m` token | May differ visually |
|
|
309
|
+
| **Focus ring** | 3px purple border via `FocusBorder` component | 3px outline via `outlineColor: $border.focus` | Similar but implemented differently |
|
|
310
|
+
| **Loading** | No built-in loading | `isLoading` prop with spinner | New capability |
|
|
311
|
+
| **Animation** | `react-native-reanimated` spring animations | Tamagui built-in transitions | Different animation library |
|
|
312
|
+
| **Gap between icon and text** | Theme-dependent spacing | Fixed `$space.8` (8px) gap | May differ slightly |
|
|
313
|
+
| **Icon sizing** | Size depends on button size (20px/24px) | Fixed `$icon.m` | Consistent across sizes |
|
|
314
|
+
| **Children required** | Either `children` or `icon` required | `children` is required | Must always have children (compound components) |
|
|
315
|
+
|
|
316
|
+
## Edge Cases
|
|
317
|
+
|
|
318
|
+
### Conditional type values
|
|
319
|
+
|
|
320
|
+
Map each possible value:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// Before
|
|
324
|
+
<Button type={isPrimary ? "primary" : "tertiary"} onPress={onPress}>
|
|
325
|
+
|
|
326
|
+
// After
|
|
327
|
+
<Button type={isPrimary ? "primary" : "tertiary"} onPress={onPress}>
|
|
328
|
+
// Same — these values map 1:1
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Conditional variant
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// Before
|
|
335
|
+
<Button variant={isContrasted ? "revert" : "default"} onPress={onPress}>
|
|
336
|
+
|
|
337
|
+
// After
|
|
338
|
+
<Button isOnContrasted={isContrasted} onPress={onPress}>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Spread props from ButtonProps
|
|
342
|
+
|
|
343
|
+
If components spread ButtonProps, filter out removed props:
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
// Before
|
|
347
|
+
const { timerAttrs, icon, iconPosition, variant, size, ...rest } = buttonProps;
|
|
348
|
+
|
|
349
|
+
// After — remap relevant props
|
|
350
|
+
const {
|
|
351
|
+
timerAttrs,
|
|
352
|
+
icon,
|
|
353
|
+
iconPosition,
|
|
354
|
+
variant,
|
|
355
|
+
size,
|
|
356
|
+
onFocus,
|
|
357
|
+
onBlur,
|
|
358
|
+
onHoverIn,
|
|
359
|
+
onHoverOut,
|
|
360
|
+
style,
|
|
361
|
+
href,
|
|
362
|
+
hrefAttrs,
|
|
363
|
+
accessibilityRole,
|
|
364
|
+
innerSpacing,
|
|
365
|
+
withBadge,
|
|
366
|
+
badgeCount,
|
|
367
|
+
...rest
|
|
368
|
+
} = buttonProps;
|
|
369
|
+
<Button {...rest} isOnContrasted={variant === 'revert'}>
|
|
370
|
+
{icon && <Button.Icon icon={icon} />}
|
|
371
|
+
{children && <Button.Text>{children}</Button.Text>}
|
|
372
|
+
</Button>;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Wrapper / re-export migration
|
|
376
|
+
|
|
377
|
+
Components that extend or pick from `ButtonProps` need type updates:
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
// Before
|
|
381
|
+
import type { ButtonProps } from '@ornikar/kitt-universal';
|
|
382
|
+
interface MyButtonProps extends Pick<ButtonProps, 'stretch' | 'type'> { ... }
|
|
383
|
+
|
|
384
|
+
// After
|
|
385
|
+
import type { ButtonProps } from '@ornikar/bumper';
|
|
386
|
+
interface MyButtonProps extends Pick<ButtonProps, 'stretch' | 'type'> { ... }
|
|
387
|
+
// stretch and type exist in both — no further changes needed
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
For wrappers that pick `icon`, `variant`, or `size`:
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// Before
|
|
394
|
+
interface MyProps extends Pick<ButtonProps, 'icon' | 'variant' | 'size'> { ... }
|
|
395
|
+
|
|
396
|
+
// After — these props no longer exist on ButtonProps
|
|
397
|
+
// icon → render Button.Icon in the component body
|
|
398
|
+
// variant → replace with isOnContrasted
|
|
399
|
+
// size → remove (always large)
|
|
400
|
+
interface MyProps {
|
|
401
|
+
icon?: ReactNode;
|
|
402
|
+
isOnContrasted?: boolean;
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### ActionButton wrapper (native)
|
|
407
|
+
|
|
408
|
+
The `ActionButton` wrapper that auto-shows `LoaderIcon` during async `onPress` can be simplified:
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
// Before (ActionButton pattern)
|
|
412
|
+
<ActionButton type="primary" icon={<ArrowRightIcon />} onPress={asyncHandler}>
|
|
413
|
+
<FormattedMessage defaultMessage="Submit" />
|
|
414
|
+
</ActionButton>;
|
|
415
|
+
|
|
416
|
+
// After — use isLoading directly
|
|
417
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
418
|
+
<Button type="primary" isLoading={isLoading} onPress={wrappedHandler}>
|
|
419
|
+
<Button.Icon icon={<ArrowRightIcon />} />
|
|
420
|
+
<Button.Text>
|
|
421
|
+
<FormattedMessage defaultMessage="Submit" />
|
|
422
|
+
</Button.Text>
|
|
423
|
+
</Button>;
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### InternalLink / InternalLinkButton wrappers
|
|
427
|
+
|
|
428
|
+
These wrap Button for navigation. Update the Button props inside:
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
// Before
|
|
432
|
+
<InternalLink as={Button} to={route} type="tertiary" icon={<ArrowLeftIcon />}>
|
|
433
|
+
<FormattedMessage defaultMessage="Back" />
|
|
434
|
+
</InternalLink>
|
|
435
|
+
|
|
436
|
+
// After — Button props change but InternalLink pattern stays
|
|
437
|
+
// NOTE: InternalLink's `as` prop may need updating if it expects specific ButtonProps shape.
|
|
438
|
+
// Flag for manual review if the wrapper doesn't support compound children.
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Actions.Button usage
|
|
442
|
+
|
|
443
|
+
`Actions.Button` from kitt-universal shares the same `ButtonProps` interface but lives inside the `Actions` compound component. These should be migrated **separately** as part of an `Actions` migration. Do not convert `Actions.Button` as part of this Button migration.
|
|
444
|
+
|
|
445
|
+
## Migration Rules (machine-readable)
|
|
446
|
+
|
|
447
|
+
Apply these rules in order for each `<Button>` usage:
|
|
448
|
+
|
|
449
|
+
1. **Update import**: Replace `'@ornikar/kitt-universal'` with `'@ornikar/bumper'` for `Button` and `ButtonProps` imports. Remove `LoaderIcon` import if only used for Button loading patterns.
|
|
450
|
+
|
|
451
|
+
2. **Remove `size` prop**: Delete any `size="default"` or `size="medium"` — bumper defaults to `large`.
|
|
452
|
+
|
|
453
|
+
3. **Convert `variant`**:
|
|
454
|
+
|
|
455
|
+
- If `variant="default"` → remove the prop
|
|
456
|
+
- If `variant="revert"` → replace with `isOnContrasted`
|
|
457
|
+
- If `variant={expr}` → replace with `isOnContrasted={expr === 'revert'}`
|
|
458
|
+
|
|
459
|
+
4. **Convert `type` values**:
|
|
460
|
+
|
|
461
|
+
- `"tertiary-danger"` → `"danger"`
|
|
462
|
+
- All others unchanged
|
|
463
|
+
|
|
464
|
+
5. **Convert `stretch` responsive syntax**:
|
|
465
|
+
|
|
466
|
+
- If `stretch={true}` or `stretch` → keep as `stretch`
|
|
467
|
+
- If `stretch={{ base: X, breakpoint: Y }}` → convert base to prop, breakpoints to `$breakpoint={{ stretch: Y }}`
|
|
468
|
+
- If `stretch={variable}` → flag for manual review
|
|
469
|
+
|
|
470
|
+
6. **Detect loading pattern**: If the JSX contains `icon={condition ? <LoaderIcon /> : <OtherIcon />}` paired with `disabled={sameCondition}`:
|
|
471
|
+
|
|
472
|
+
- Add `isLoading={condition}` prop
|
|
473
|
+
- Remove the `LoaderIcon` conditional from icon
|
|
474
|
+
- Remove `disabled={condition}` (keep `disabled` if it has additional conditions)
|
|
475
|
+
- Move the non-LoaderIcon icon to `<Button.Icon icon={...} />`
|
|
476
|
+
|
|
477
|
+
7. **Convert `icon` prop to compound child**:
|
|
478
|
+
|
|
479
|
+
- Remove `icon` prop from `<Button>`
|
|
480
|
+
- If `iconPosition="right"` or `iconPosition` is absent/`"left"` → determines child order
|
|
481
|
+
- Add `<Button.Icon icon={iconValue} />` as child (before `Button.Text` for left, after for right)
|
|
482
|
+
- Remove `iconPosition` prop
|
|
483
|
+
|
|
484
|
+
8. **Wrap text children in `Button.Text`**:
|
|
485
|
+
|
|
486
|
+
- Take all remaining children of `<Button>` (text, `<FormattedMessage>`, expressions)
|
|
487
|
+
- Wrap them in `<Button.Text>...</Button.Text>`
|
|
488
|
+
- If Button had no `children` (icon-only), skip this step
|
|
489
|
+
|
|
490
|
+
9. **Remove unsupported props**: Delete `onFocus`, `onBlur`, `onHoverIn`, `onHoverOut`, `href`, `hrefAttrs`, `accessibilityRole`, `innerSpacing`, `style`, `isHoveredInternal`, `isPressedInternal`, `isFocusedInternal`, `withBadge`, `badgeCount`.
|
|
491
|
+
|
|
492
|
+
10. **Self-closing to explicit children**: If the source was a self-closing `<Button ... />`, convert to `<Button ...>...</Button>` with compound children inside.
|
|
493
|
+
|
|
494
|
+
## Not Migratable (requires human review)
|
|
495
|
+
|
|
496
|
+
- **`timerAttrs` prop** (1 usage in `AnswerSection`): No bumper equivalent. The timer/gauge overlay feature does not exist in bumper Button. This usage needs a custom solution or feature request.
|
|
497
|
+
- **Dynamic `stretch` variables**: When `stretch` receives a runtime variable (not an object literal), ask the user on how to proceed.
|
|
498
|
+
- **`InternalLink as={Button}` wrappers**: The `as` polymorphic pattern may not be compatible with bumper's compound children model. Each InternalLink/InternalLinkButton wrapper needs manual verification.
|
|
499
|
+
- **`Actions.Button`**: Shares ButtonProps but lives in a different component tree. Migrate separately as part of Actions component migration.
|
|
500
|
+
- **Icon-only buttons**: Don't do anything. Migrate separatly as part of the IconButton component migration.
|
|
501
|
+
- **`style` prop usage**: Any custom styles passed via `style` prop need manual conversion to Tamagui style props.
|
|
502
|
+
- **Components extending `ButtonProps`**: Proceed with the changes, only ask for user input if the linting is failing.
|