@boostdev/design-system-react-native 0.5.1 → 0.6.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.
- package/CHANGELOG.md +32 -0
- package/README.md +35 -1
- package/dist/index.cjs +37 -24
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +37 -24
- package/package.json +1 -1
- package/src/components/interaction/Button/Button.mdx +10 -2
- package/src/components/interaction/Button/Button.spec.tsx +62 -2
- package/src/components/interaction/Button/Button.tsx +4 -4
- package/src/components/interaction/form/Combobox/Combobox.spec.tsx +14 -1
- package/src/components/interaction/form/Combobox/Combobox.tsx +4 -3
- package/src/components/interaction/form/FileInput/FileInput.spec.tsx +13 -1
- package/src/components/interaction/form/FileInput/FileInput.tsx +3 -3
- package/src/components/interaction/form/FormInput/FormInput.spec.tsx +14 -1
- package/src/components/interaction/form/FormInput/FormInput.tsx +5 -4
- package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +14 -1
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +7 -6
- package/src/components/interaction/form/Select/Select.mdx +1 -1
- package/src/components/interaction/form/Select/Select.spec.tsx +14 -1
- package/src/components/interaction/form/Select/Select.tsx +2 -2
- package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -1
- package/src/components/interaction/form/Textarea/Textarea.tsx +3 -3
- package/src/index.ts +1 -1
- package/src/native/ThemeContext.spec.tsx +81 -1
- package/src/native/ThemeContext.tsx +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to `@boostdev/design-system-react-native` are documented here.
|
|
4
|
+
## [0.6.0] - 2026-09-29
|
|
5
|
+
### Added
|
|
6
|
+
- Feat(theme): radius overrides for buttons and fields
|
|
7
|
+
|
|
8
|
+
ThemeProvider takes `tokens.radius` ({ button, iconButton, field }, in
|
|
9
|
+
points), the native equivalent of the web `--button_radius`. The
|
|
10
|
+
defaults are 0.5.1's values (m, full, m), so nothing changes for an app
|
|
11
|
+
that does not set it. `useTheme()` returns the resolved `radius` next
|
|
12
|
+
to `colors` and `isDark`, and `ThemeRadius` is exported.
|
|
13
|
+
|
|
14
|
+
A nested ThemeProvider merges its radius over its parent's, so a
|
|
15
|
+
provider that only recolors a subtree keeps the app's corners. Colors
|
|
16
|
+
still merge over the foundation, not over the parent.
|
|
17
|
+
|
|
18
|
+
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> ([`d8200d3`](https://gitlab.com/b00stdev/boostdev-design-system-react-native/-/commit/d8200d39ed24b30658a6c45692883dd1128898c1))
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Refactor(button): corner radius comes from the theme
|
|
22
|
+
|
|
23
|
+
A text button uses `radius.button` and an icon-only button
|
|
24
|
+
`radius.iconButton` instead of the hardcoded border.radius.m and
|
|
25
|
+
border.radius.full. A borderRadius in `style` still wins.
|
|
26
|
+
|
|
27
|
+
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> ([`d672541`](https://gitlab.com/b00stdev/boostdev-design-system-react-native/-/commit/d6725412c7a6406203b6887e34512a09994c5265))
|
|
28
|
+
- Refactor(form): fields take their corner radius from the theme
|
|
29
|
+
|
|
30
|
+
FormInput, Select, Combobox, Textarea and the FileInput drop zone use
|
|
31
|
+
`radius.field` instead of the hardcoded border.radius.m. NumberInput
|
|
32
|
+
uses it for the outer corners on its steppers.
|
|
33
|
+
|
|
34
|
+
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> ([`17b93bf`](https://gitlab.com/b00stdev/boostdev-design-system-react-native/-/commit/17b93bfa554c060b1ae83ca488b738d9a307b4b9))
|
|
35
|
+
|
|
4
36
|
## [0.5.1] - 2026-09-28
|
|
5
37
|
### Fixed
|
|
6
38
|
- Fix(button): text buttons get medium corners
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ export default function App() {
|
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`ThemeProvider` auto-detects the system color scheme (light/dark) via `useColorScheme`, or you can pass `colorScheme="light" | "dark"` to force one. Inside the tree, `useTheme()` exposes `{ colors, isDark }` where `colors` is the full foundation token palette (`colorBg`, `colorOnBg`, `colorInteractive`, …).
|
|
37
|
+
`ThemeProvider` auto-detects the system color scheme (light/dark) via `useColorScheme`, or you can pass `colorScheme="light" | "dark"` to force one. Inside the tree, `useTheme()` exposes `{ colors, isDark, radius }` where `colors` is the full foundation token palette (`colorBg`, `colorOnBg`, `colorInteractive`, …) and `radius` holds the corner radii of the controls (see [Control radius](#control-radius)).
|
|
38
38
|
|
|
39
39
|
## Brand theming (token overrides)
|
|
40
40
|
|
|
@@ -62,6 +62,40 @@ Unspecified tokens keep their foundation values. Available token names are the k
|
|
|
62
62
|
Pass a stable `tokens` reference (a module-level constant, not an inline object
|
|
63
63
|
literal) — the provider memoizes the merged colors on that reference.
|
|
64
64
|
|
|
65
|
+
### Control radius
|
|
66
|
+
|
|
67
|
+
The corner radius of buttons and fields comes from the theme, not from the
|
|
68
|
+
components — the React Native equivalent of the web `--button_radius` custom
|
|
69
|
+
property. Override it with `tokens.radius`, in points; it is the same in light
|
|
70
|
+
and dark:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { ThemeProvider, border } from '@boostdev/design-system-react-native';
|
|
74
|
+
import type { ThemeTokenOverrides } from '@boostdev/design-system-react-native';
|
|
75
|
+
|
|
76
|
+
const tokens: ThemeTokenOverrides = {
|
|
77
|
+
light: { colorInteractive: '#00c853' },
|
|
78
|
+
radius: { button: border.radius.s, iconButton: border.radius.s, field: border.radius.s },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
<ThemeProvider tokens={tokens}>
|
|
82
|
+
<App />
|
|
83
|
+
</ThemeProvider>;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Key | Applies to | Default |
|
|
87
|
+
| ------------ | ------------------------------------------------------------------------- | --------------------------- |
|
|
88
|
+
| `button` | `Button` with a text label | `border.radius.m` (16) |
|
|
89
|
+
| `iconButton` | `Button` with `isIconOnly` | `border.radius.full` (9999) |
|
|
90
|
+
| `field` | `FormInput`, `Select`, `Combobox`, `Textarea`, `FileInput`, `NumberInput` | `border.radius.m` (16) |
|
|
91
|
+
|
|
92
|
+
Unspecified keys keep their default. A nested `ThemeProvider` inherits its
|
|
93
|
+
parent's radii and merges its own `radius` over them, so a provider that only
|
|
94
|
+
recolors a subtree keeps the app's corners. (Colors differ: a nested provider
|
|
95
|
+
merges its colors over the foundation, not over its parent.) A `borderRadius`
|
|
96
|
+
in a `Button`'s `style` still wins over the theme. Your own controls can read
|
|
97
|
+
the same values through `useTheme().radius` (type `ThemeRadius`).
|
|
98
|
+
|
|
65
99
|
## React Navigation compatibility
|
|
66
100
|
|
|
67
101
|
The library also ships a thin, **opt-in** bridge that produces a [React Navigation](https://reactnavigation.org/) `Theme` from the same foundation tokens. The bridge is purely additive — nothing about the standalone API changes when you use it, and `@react-navigation/native` is only required if you actually consume the bridge.
|
package/dist/index.cjs
CHANGED
|
@@ -114,15 +114,25 @@ var lineHeight = {
|
|
|
114
114
|
|
|
115
115
|
// src/native/ThemeContext.tsx
|
|
116
116
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
117
|
+
var defaultRadius = {
|
|
118
|
+
button: import_native.border.radius.m,
|
|
119
|
+
iconButton: import_native.border.radius.full,
|
|
120
|
+
field: import_native.border.radius.m
|
|
121
|
+
};
|
|
117
122
|
var ThemeContext = (0, import_react.createContext)(null);
|
|
118
123
|
function ThemeProvider({ children, colorScheme, tokens }) {
|
|
119
124
|
const systemScheme = (0, import_react_native.useColorScheme)();
|
|
120
125
|
const isDark = (colorScheme ?? systemScheme) === "dark";
|
|
126
|
+
const parentRadius = (0, import_react.useContext)(ThemeContext)?.radius ?? defaultRadius;
|
|
121
127
|
const value = (0, import_react.useMemo)(() => {
|
|
122
128
|
const base = isDark ? import_native.colors.dark : import_native.colors.light;
|
|
123
129
|
const overrides = isDark ? tokens?.dark : tokens?.light;
|
|
124
|
-
return {
|
|
125
|
-
|
|
130
|
+
return {
|
|
131
|
+
colors: { ...base, ...overrides },
|
|
132
|
+
isDark,
|
|
133
|
+
radius: { ...parentRadius, ...tokens?.radius }
|
|
134
|
+
};
|
|
135
|
+
}, [isDark, tokens, parentRadius]);
|
|
126
136
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value, children });
|
|
127
137
|
}
|
|
128
138
|
function useTheme() {
|
|
@@ -2269,7 +2279,7 @@ function Button({
|
|
|
2269
2279
|
accessibilityLabel,
|
|
2270
2280
|
style
|
|
2271
2281
|
}) {
|
|
2272
|
-
const { colors: colors2 } = useTheme();
|
|
2282
|
+
const { colors: colors2, radius } = useTheme();
|
|
2273
2283
|
const { height, px, fontSize } = sizeTokens[size];
|
|
2274
2284
|
const primaryColor = colors2.colorInteractive;
|
|
2275
2285
|
const onPrimaryColor = colors2.colorOnInteractive;
|
|
@@ -2278,8 +2288,8 @@ function Button({
|
|
|
2278
2288
|
const containerStyle = {
|
|
2279
2289
|
minHeight: height,
|
|
2280
2290
|
paddingHorizontal: isIconOnly ? import_native.spacing.xs : px,
|
|
2281
|
-
//
|
|
2282
|
-
borderRadius: isIconOnly ?
|
|
2291
|
+
// Corners come from the theme (`tokens.radius`); a borderRadius in `style` still wins.
|
|
2292
|
+
borderRadius: isIconOnly ? radius.iconButton : radius.button,
|
|
2283
2293
|
opacity: disabled ? 0.4 : 1,
|
|
2284
2294
|
// Icon-only: square with equal xs padding on both axes.
|
|
2285
2295
|
...isIconOnly && { aspectRatio: 1, paddingVertical: import_native.spacing.xs },
|
|
@@ -2683,7 +2693,6 @@ var styles33 = import_react_native37.StyleSheet.create({
|
|
|
2683
2693
|
paddingHorizontal: import_native.spacing.m,
|
|
2684
2694
|
borderWidth: 2,
|
|
2685
2695
|
borderStyle: "dashed",
|
|
2686
|
-
borderRadius: import_native.border.radius.m,
|
|
2687
2696
|
minHeight: 120
|
|
2688
2697
|
},
|
|
2689
2698
|
icon: {
|
|
@@ -2724,7 +2733,7 @@ function FileInput({
|
|
|
2724
2733
|
fileNames = [],
|
|
2725
2734
|
style
|
|
2726
2735
|
}) {
|
|
2727
|
-
const { colors: colors2 } = useTheme();
|
|
2736
|
+
const { colors: colors2, radius } = useTheme();
|
|
2728
2737
|
const borderColor = error ? colors2.colorError : colors2.colorBgSubtle;
|
|
2729
2738
|
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_react_native37.View, { style: [styles33.container, style], testID: name, children: [
|
|
2730
2739
|
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
@@ -2745,6 +2754,7 @@ function FileInput({
|
|
|
2745
2754
|
styles33.dropZone,
|
|
2746
2755
|
{
|
|
2747
2756
|
borderColor,
|
|
2757
|
+
borderRadius: radius.field,
|
|
2748
2758
|
backgroundColor: "transparent"
|
|
2749
2759
|
},
|
|
2750
2760
|
pressed && { opacity: 0.7 },
|
|
@@ -2779,7 +2789,8 @@ var styles34 = import_react_native38.StyleSheet.create({
|
|
|
2779
2789
|
gap: import_native.spacing.xxs
|
|
2780
2790
|
},
|
|
2781
2791
|
/**
|
|
2782
|
-
* Draws the rounded border and provides the fixed
|
|
2792
|
+
* Draws the rounded border (`radius.field` from the theme) and provides the fixed
|
|
2793
|
+
* 44pt touch-target height.
|
|
2783
2794
|
* justifyContent: 'center' vertically centers the inner TextInput.
|
|
2784
2795
|
* position: 'relative' is required for the dropdown's absolute positioning.
|
|
2785
2796
|
*/
|
|
@@ -2787,8 +2798,7 @@ var styles34 = import_react_native38.StyleSheet.create({
|
|
|
2787
2798
|
position: "relative",
|
|
2788
2799
|
height: INPUT_HEIGHT,
|
|
2789
2800
|
justifyContent: "center",
|
|
2790
|
-
borderWidth: 1
|
|
2791
|
-
borderRadius: import_native.border.radius.m
|
|
2801
|
+
borderWidth: 1
|
|
2792
2802
|
},
|
|
2793
2803
|
/**
|
|
2794
2804
|
* No vertical padding: iOS centers text automatically inside the wrapper height.
|
|
@@ -2841,7 +2851,7 @@ function Combobox({
|
|
|
2841
2851
|
hint,
|
|
2842
2852
|
style
|
|
2843
2853
|
}) {
|
|
2844
|
-
const { colors: colors2 } = useTheme();
|
|
2854
|
+
const { colors: colors2, radius } = useTheme();
|
|
2845
2855
|
const selectedOption = (0, import_react10.useMemo)(
|
|
2846
2856
|
() => options.find((o) => o.value === value),
|
|
2847
2857
|
[options, value]
|
|
@@ -2879,6 +2889,7 @@ function Combobox({
|
|
|
2879
2889
|
styles34.inputWrapper,
|
|
2880
2890
|
{
|
|
2881
2891
|
borderColor,
|
|
2892
|
+
borderRadius: radius.field,
|
|
2882
2893
|
backgroundColor: colors2.colorBg
|
|
2883
2894
|
},
|
|
2884
2895
|
disabled && { opacity: 0.4 }
|
|
@@ -3079,14 +3090,14 @@ var styles36 = import_react_native40.StyleSheet.create({
|
|
|
3079
3090
|
gap: import_native.spacing.xxs
|
|
3080
3091
|
},
|
|
3081
3092
|
/**
|
|
3082
|
-
* The wrapper draws the rounded border
|
|
3093
|
+
* The wrapper draws the rounded border (`radius.field` from the theme) and provides
|
|
3094
|
+
* at least 44pt touch-target height.
|
|
3083
3095
|
* justifyContent: 'center' vertically centers the inner TextInput on both iOS and Android.
|
|
3084
3096
|
*/
|
|
3085
3097
|
inputWrapper: {
|
|
3086
3098
|
minHeight: INPUT_HEIGHT2,
|
|
3087
3099
|
justifyContent: "center",
|
|
3088
|
-
borderWidth: 1
|
|
3089
|
-
borderRadius: import_native.border.radius.m
|
|
3100
|
+
borderWidth: 1
|
|
3090
3101
|
},
|
|
3091
3102
|
/**
|
|
3092
3103
|
* No vertical padding: iOS centers text automatically inside the wrapper height.
|
|
@@ -3119,7 +3130,7 @@ function FormInput({
|
|
|
3119
3130
|
onFocus,
|
|
3120
3131
|
style
|
|
3121
3132
|
}) {
|
|
3122
|
-
const { colors: colors2 } = useTheme();
|
|
3133
|
+
const { colors: colors2, radius } = useTheme();
|
|
3123
3134
|
const [focused, setFocused] = (0, import_react12.useState)(false);
|
|
3124
3135
|
const borderColor = error ? colors2.colorError : focused ? colors2.colorInteractive : colors2.colorOnBg;
|
|
3125
3136
|
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react_native40.View, { style: [styles36.container, style], children: [
|
|
@@ -3131,6 +3142,7 @@ function FormInput({
|
|
|
3131
3142
|
styles36.inputWrapper,
|
|
3132
3143
|
{
|
|
3133
3144
|
borderColor,
|
|
3145
|
+
borderRadius: radius.field,
|
|
3134
3146
|
backgroundColor: colors2.colorBg
|
|
3135
3147
|
},
|
|
3136
3148
|
disabled && { opacity: 0.4 }
|
|
@@ -3187,7 +3199,6 @@ var styles37 = import_react_native41.StyleSheet.create({
|
|
|
3187
3199
|
paddingHorizontal: import_native.spacing.s,
|
|
3188
3200
|
paddingVertical: import_native.spacing.xs,
|
|
3189
3201
|
borderWidth: 1,
|
|
3190
|
-
borderRadius: import_native.border.radius.m,
|
|
3191
3202
|
minHeight: 100,
|
|
3192
3203
|
textAlignVertical: "top"
|
|
3193
3204
|
}
|
|
@@ -3207,7 +3218,7 @@ function Textarea({
|
|
|
3207
3218
|
onFocus,
|
|
3208
3219
|
style
|
|
3209
3220
|
}) {
|
|
3210
|
-
const { colors: colors2 } = useTheme();
|
|
3221
|
+
const { colors: colors2, radius } = useTheme();
|
|
3211
3222
|
const [focused, setFocused] = (0, import_react13.useState)(false);
|
|
3212
3223
|
const borderColor = error ? colors2.colorError : focused ? colors2.colorInteractive : colors2.colorOnBg;
|
|
3213
3224
|
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_react_native41.View, { style: [styles37.container, style], children: [
|
|
@@ -3238,6 +3249,7 @@ function Textarea({
|
|
|
3238
3249
|
styles37.textarea,
|
|
3239
3250
|
{
|
|
3240
3251
|
borderColor,
|
|
3252
|
+
borderRadius: radius.field,
|
|
3241
3253
|
color: colors2.colorOnBg,
|
|
3242
3254
|
backgroundColor: colors2.colorBg
|
|
3243
3255
|
},
|
|
@@ -3265,7 +3277,6 @@ var styles38 = import_react_native42.StyleSheet.create({
|
|
|
3265
3277
|
paddingHorizontal: import_native.spacing.s,
|
|
3266
3278
|
paddingVertical: import_native.spacing.xs,
|
|
3267
3279
|
borderWidth: 1,
|
|
3268
|
-
borderRadius: import_native.border.radius.m,
|
|
3269
3280
|
minHeight: 44
|
|
3270
3281
|
},
|
|
3271
3282
|
triggerText: {
|
|
@@ -3330,7 +3341,7 @@ function Select({
|
|
|
3330
3341
|
onChange,
|
|
3331
3342
|
style
|
|
3332
3343
|
}) {
|
|
3333
|
-
const { colors: colors2 } = useTheme();
|
|
3344
|
+
const { colors: colors2, radius } = useTheme();
|
|
3334
3345
|
const [open, setOpen] = (0, import_react14.useState)(false);
|
|
3335
3346
|
const selectedOption = options.find((o) => o.value === value);
|
|
3336
3347
|
const displayText = selectedOption?.label ?? placeholder;
|
|
@@ -3355,6 +3366,7 @@ function Select({
|
|
|
3355
3366
|
styles38.trigger,
|
|
3356
3367
|
{
|
|
3357
3368
|
borderColor,
|
|
3369
|
+
borderRadius: radius.field,
|
|
3358
3370
|
backgroundColor: colors2.colorBg
|
|
3359
3371
|
},
|
|
3360
3372
|
disabled && { opacity: 0.4 }
|
|
@@ -3650,14 +3662,11 @@ var styles40 = import_react_native44.StyleSheet.create({
|
|
|
3650
3662
|
justifyContent: "center",
|
|
3651
3663
|
borderWidth: 1
|
|
3652
3664
|
},
|
|
3665
|
+
// The steppers round only the field's outer corners, with `radius.field` from the theme.
|
|
3653
3666
|
stepperStart: {
|
|
3654
|
-
borderTopStartRadius: import_native.border.radius.m,
|
|
3655
|
-
borderBottomStartRadius: import_native.border.radius.m,
|
|
3656
3667
|
borderEndWidth: 0
|
|
3657
3668
|
},
|
|
3658
3669
|
stepperEnd: {
|
|
3659
|
-
borderTopEndRadius: import_native.border.radius.m,
|
|
3660
|
-
borderBottomEndRadius: import_native.border.radius.m,
|
|
3661
3670
|
borderStartWidth: 0
|
|
3662
3671
|
},
|
|
3663
3672
|
stepperText: {
|
|
@@ -3708,7 +3717,7 @@ function NumberInput({
|
|
|
3708
3717
|
onChange,
|
|
3709
3718
|
style
|
|
3710
3719
|
}) {
|
|
3711
|
-
const { colors: colors2 } = useTheme();
|
|
3720
|
+
const { colors: colors2, radius } = useTheme();
|
|
3712
3721
|
const isControlled = value !== void 0;
|
|
3713
3722
|
const [internalValue, setInternalValue] = (0, import_react16.useState)(defaultValue ?? 0);
|
|
3714
3723
|
const currentValue = isControlled ? value : internalValue;
|
|
@@ -3740,6 +3749,8 @@ function NumberInput({
|
|
|
3740
3749
|
styles40.stepperStart,
|
|
3741
3750
|
{
|
|
3742
3751
|
borderColor,
|
|
3752
|
+
borderTopStartRadius: radius.field,
|
|
3753
|
+
borderBottomStartRadius: radius.field,
|
|
3743
3754
|
backgroundColor: colors2.colorBgSubtle
|
|
3744
3755
|
},
|
|
3745
3756
|
pressed && { opacity: 0.7 },
|
|
@@ -3805,6 +3816,8 @@ function NumberInput({
|
|
|
3805
3816
|
styles40.stepperEnd,
|
|
3806
3817
|
{
|
|
3807
3818
|
borderColor,
|
|
3819
|
+
borderTopEndRadius: radius.field,
|
|
3820
|
+
borderBottomEndRadius: radius.field,
|
|
3808
3821
|
backgroundColor: colors2.colorBgSubtle
|
|
3809
3822
|
},
|
|
3810
3823
|
pressed && { opacity: 0.7 },
|
package/dist/index.d.cts
CHANGED
|
@@ -18,6 +18,15 @@ declare const lineHeight: {
|
|
|
18
18
|
type ThemeColorName = keyof typeof colors.light;
|
|
19
19
|
/** Semantic theme colors, widened to `string` so brand overrides typecheck. */
|
|
20
20
|
type ThemeColors = Readonly<Record<ThemeColorName, string>>;
|
|
21
|
+
/** Corner radii of the controls, in points. */
|
|
22
|
+
interface ThemeRadius {
|
|
23
|
+
/** Buttons with a text label. */
|
|
24
|
+
button: number;
|
|
25
|
+
/** Icon-only buttons (`isIconOnly`). */
|
|
26
|
+
iconButton: number;
|
|
27
|
+
/** Input fields: FormInput, Select, Combobox, Textarea, FileInput, NumberInput. */
|
|
28
|
+
field: number;
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* Per-scheme token overrides, merged over the foundation colors.
|
|
23
32
|
* The React Native equivalent of overriding tokens in the web
|
|
@@ -26,10 +35,17 @@ type ThemeColors = Readonly<Record<ThemeColorName, string>>;
|
|
|
26
35
|
interface ThemeTokenOverrides {
|
|
27
36
|
light?: Partial<Record<ThemeColorName, string>>;
|
|
28
37
|
dark?: Partial<Record<ThemeColorName, string>>;
|
|
38
|
+
/**
|
|
39
|
+
* Radius overrides, the same in light and dark. The native equivalent of the
|
|
40
|
+
* web `--button_radius` custom property. Merged over the parent provider's
|
|
41
|
+
* radii, so a nested provider that only overrides colors keeps the app's radii.
|
|
42
|
+
*/
|
|
43
|
+
radius?: Partial<ThemeRadius>;
|
|
29
44
|
}
|
|
30
45
|
interface ThemeContextValue {
|
|
31
46
|
colors: ThemeColors;
|
|
32
47
|
isDark: boolean;
|
|
48
|
+
radius: ThemeRadius;
|
|
33
49
|
}
|
|
34
50
|
interface ThemeProviderProps {
|
|
35
51
|
children: ReactNode;
|
|
@@ -667,4 +683,4 @@ interface MessageProps {
|
|
|
667
683
|
}
|
|
668
684
|
declare function Message({ message, type, style }: MessageProps): react_jsx_runtime.JSX.Element | null;
|
|
669
685
|
|
|
670
|
-
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type CreateReactNavigationThemeOptions, type DescriptionItem, DescriptionList, Dialog, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, type ReactNavigationFontStyle, type ReactNavigationTheme, type ReactNavigationThemeColors, type ReactNavigationThemeFonts, ScaledText, ScaledTextInput, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, SideSheet, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, type ThemeColorName, type ThemeColors, ThemeProvider, type ThemeTokenOverrides, ToastProvider, Tooltip, Typography, createReactNavigationTheme, lineHeight, maxFontScaleFor, maxFontScaleForStyle, useReactNavigationTheme, useTheme, useToast };
|
|
686
|
+
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type CreateReactNavigationThemeOptions, type DescriptionItem, DescriptionList, Dialog, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, type ReactNavigationFontStyle, type ReactNavigationTheme, type ReactNavigationThemeColors, type ReactNavigationThemeFonts, ScaledText, ScaledTextInput, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, SideSheet, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, type ThemeColorName, type ThemeColors, ThemeProvider, type ThemeRadius, type ThemeTokenOverrides, ToastProvider, Tooltip, Typography, createReactNavigationTheme, lineHeight, maxFontScaleFor, maxFontScaleForStyle, useReactNavigationTheme, useTheme, useToast };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ declare const lineHeight: {
|
|
|
18
18
|
type ThemeColorName = keyof typeof colors.light;
|
|
19
19
|
/** Semantic theme colors, widened to `string` so brand overrides typecheck. */
|
|
20
20
|
type ThemeColors = Readonly<Record<ThemeColorName, string>>;
|
|
21
|
+
/** Corner radii of the controls, in points. */
|
|
22
|
+
interface ThemeRadius {
|
|
23
|
+
/** Buttons with a text label. */
|
|
24
|
+
button: number;
|
|
25
|
+
/** Icon-only buttons (`isIconOnly`). */
|
|
26
|
+
iconButton: number;
|
|
27
|
+
/** Input fields: FormInput, Select, Combobox, Textarea, FileInput, NumberInput. */
|
|
28
|
+
field: number;
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* Per-scheme token overrides, merged over the foundation colors.
|
|
23
32
|
* The React Native equivalent of overriding tokens in the web
|
|
@@ -26,10 +35,17 @@ type ThemeColors = Readonly<Record<ThemeColorName, string>>;
|
|
|
26
35
|
interface ThemeTokenOverrides {
|
|
27
36
|
light?: Partial<Record<ThemeColorName, string>>;
|
|
28
37
|
dark?: Partial<Record<ThemeColorName, string>>;
|
|
38
|
+
/**
|
|
39
|
+
* Radius overrides, the same in light and dark. The native equivalent of the
|
|
40
|
+
* web `--button_radius` custom property. Merged over the parent provider's
|
|
41
|
+
* radii, so a nested provider that only overrides colors keeps the app's radii.
|
|
42
|
+
*/
|
|
43
|
+
radius?: Partial<ThemeRadius>;
|
|
29
44
|
}
|
|
30
45
|
interface ThemeContextValue {
|
|
31
46
|
colors: ThemeColors;
|
|
32
47
|
isDark: boolean;
|
|
48
|
+
radius: ThemeRadius;
|
|
33
49
|
}
|
|
34
50
|
interface ThemeProviderProps {
|
|
35
51
|
children: ReactNode;
|
|
@@ -667,4 +683,4 @@ interface MessageProps {
|
|
|
667
683
|
}
|
|
668
684
|
declare function Message({ message, type, style }: MessageProps): react_jsx_runtime.JSX.Element | null;
|
|
669
685
|
|
|
670
|
-
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type CreateReactNavigationThemeOptions, type DescriptionItem, DescriptionList, Dialog, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, type ReactNavigationFontStyle, type ReactNavigationTheme, type ReactNavigationThemeColors, type ReactNavigationThemeFonts, ScaledText, ScaledTextInput, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, SideSheet, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, type ThemeColorName, type ThemeColors, ThemeProvider, type ThemeTokenOverrides, ToastProvider, Tooltip, Typography, createReactNavigationTheme, lineHeight, maxFontScaleFor, maxFontScaleForStyle, useReactNavigationTheme, useTheme, useToast };
|
|
686
|
+
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type CreateReactNavigationThemeOptions, type DescriptionItem, DescriptionList, Dialog, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, type ReactNavigationFontStyle, type ReactNavigationTheme, type ReactNavigationThemeColors, type ReactNavigationThemeFonts, ScaledText, ScaledTextInput, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, SideSheet, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, type ThemeColorName, type ThemeColors, ThemeProvider, type ThemeRadius, type ThemeTokenOverrides, ToastProvider, Tooltip, Typography, createReactNavigationTheme, lineHeight, maxFontScaleFor, maxFontScaleForStyle, useReactNavigationTheme, useTheme, useToast };
|
package/dist/index.js
CHANGED
|
@@ -15,15 +15,25 @@ var lineHeight = {
|
|
|
15
15
|
|
|
16
16
|
// src/native/ThemeContext.tsx
|
|
17
17
|
import { jsx } from "react/jsx-runtime";
|
|
18
|
+
var defaultRadius = {
|
|
19
|
+
button: border.radius.m,
|
|
20
|
+
iconButton: border.radius.full,
|
|
21
|
+
field: border.radius.m
|
|
22
|
+
};
|
|
18
23
|
var ThemeContext = createContext(null);
|
|
19
24
|
function ThemeProvider({ children, colorScheme, tokens }) {
|
|
20
25
|
const systemScheme = useColorScheme();
|
|
21
26
|
const isDark = (colorScheme ?? systemScheme) === "dark";
|
|
27
|
+
const parentRadius = useContext(ThemeContext)?.radius ?? defaultRadius;
|
|
22
28
|
const value = useMemo(() => {
|
|
23
29
|
const base = isDark ? colors.dark : colors.light;
|
|
24
30
|
const overrides = isDark ? tokens?.dark : tokens?.light;
|
|
25
|
-
return {
|
|
26
|
-
|
|
31
|
+
return {
|
|
32
|
+
colors: { ...base, ...overrides },
|
|
33
|
+
isDark,
|
|
34
|
+
radius: { ...parentRadius, ...tokens?.radius }
|
|
35
|
+
};
|
|
36
|
+
}, [isDark, tokens, parentRadius]);
|
|
27
37
|
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
28
38
|
}
|
|
29
39
|
function useTheme() {
|
|
@@ -2170,7 +2180,7 @@ function Button({
|
|
|
2170
2180
|
accessibilityLabel,
|
|
2171
2181
|
style
|
|
2172
2182
|
}) {
|
|
2173
|
-
const { colors: colors2 } = useTheme();
|
|
2183
|
+
const { colors: colors2, radius } = useTheme();
|
|
2174
2184
|
const { height, px, fontSize } = sizeTokens[size];
|
|
2175
2185
|
const primaryColor = colors2.colorInteractive;
|
|
2176
2186
|
const onPrimaryColor = colors2.colorOnInteractive;
|
|
@@ -2179,8 +2189,8 @@ function Button({
|
|
|
2179
2189
|
const containerStyle = {
|
|
2180
2190
|
minHeight: height,
|
|
2181
2191
|
paddingHorizontal: isIconOnly ? spacing.xs : px,
|
|
2182
|
-
//
|
|
2183
|
-
borderRadius: isIconOnly ?
|
|
2192
|
+
// Corners come from the theme (`tokens.radius`); a borderRadius in `style` still wins.
|
|
2193
|
+
borderRadius: isIconOnly ? radius.iconButton : radius.button,
|
|
2184
2194
|
opacity: disabled ? 0.4 : 1,
|
|
2185
2195
|
// Icon-only: square with equal xs padding on both axes.
|
|
2186
2196
|
...isIconOnly && { aspectRatio: 1, paddingVertical: spacing.xs },
|
|
@@ -2584,7 +2594,6 @@ var styles33 = StyleSheet34.create({
|
|
|
2584
2594
|
paddingHorizontal: spacing.m,
|
|
2585
2595
|
borderWidth: 2,
|
|
2586
2596
|
borderStyle: "dashed",
|
|
2587
|
-
borderRadius: border.radius.m,
|
|
2588
2597
|
minHeight: 120
|
|
2589
2598
|
},
|
|
2590
2599
|
icon: {
|
|
@@ -2625,7 +2634,7 @@ function FileInput({
|
|
|
2625
2634
|
fileNames = [],
|
|
2626
2635
|
style
|
|
2627
2636
|
}) {
|
|
2628
|
-
const { colors: colors2 } = useTheme();
|
|
2637
|
+
const { colors: colors2, radius } = useTheme();
|
|
2629
2638
|
const borderColor = error ? colors2.colorError : colors2.colorBgSubtle;
|
|
2630
2639
|
return /* @__PURE__ */ jsxs22(View29, { style: [styles33.container, style], testID: name, children: [
|
|
2631
2640
|
/* @__PURE__ */ jsx38(
|
|
@@ -2646,6 +2655,7 @@ function FileInput({
|
|
|
2646
2655
|
styles33.dropZone,
|
|
2647
2656
|
{
|
|
2648
2657
|
borderColor,
|
|
2658
|
+
borderRadius: radius.field,
|
|
2649
2659
|
backgroundColor: "transparent"
|
|
2650
2660
|
},
|
|
2651
2661
|
pressed && { opacity: 0.7 },
|
|
@@ -2680,7 +2690,8 @@ var styles34 = StyleSheet35.create({
|
|
|
2680
2690
|
gap: spacing.xxs
|
|
2681
2691
|
},
|
|
2682
2692
|
/**
|
|
2683
|
-
* Draws the rounded border and provides the fixed
|
|
2693
|
+
* Draws the rounded border (`radius.field` from the theme) and provides the fixed
|
|
2694
|
+
* 44pt touch-target height.
|
|
2684
2695
|
* justifyContent: 'center' vertically centers the inner TextInput.
|
|
2685
2696
|
* position: 'relative' is required for the dropdown's absolute positioning.
|
|
2686
2697
|
*/
|
|
@@ -2688,8 +2699,7 @@ var styles34 = StyleSheet35.create({
|
|
|
2688
2699
|
position: "relative",
|
|
2689
2700
|
height: INPUT_HEIGHT,
|
|
2690
2701
|
justifyContent: "center",
|
|
2691
|
-
borderWidth: 1
|
|
2692
|
-
borderRadius: border.radius.m
|
|
2702
|
+
borderWidth: 1
|
|
2693
2703
|
},
|
|
2694
2704
|
/**
|
|
2695
2705
|
* No vertical padding: iOS centers text automatically inside the wrapper height.
|
|
@@ -2742,7 +2752,7 @@ function Combobox({
|
|
|
2742
2752
|
hint,
|
|
2743
2753
|
style
|
|
2744
2754
|
}) {
|
|
2745
|
-
const { colors: colors2 } = useTheme();
|
|
2755
|
+
const { colors: colors2, radius } = useTheme();
|
|
2746
2756
|
const selectedOption = useMemo2(
|
|
2747
2757
|
() => options.find((o) => o.value === value),
|
|
2748
2758
|
[options, value]
|
|
@@ -2780,6 +2790,7 @@ function Combobox({
|
|
|
2780
2790
|
styles34.inputWrapper,
|
|
2781
2791
|
{
|
|
2782
2792
|
borderColor,
|
|
2793
|
+
borderRadius: radius.field,
|
|
2783
2794
|
backgroundColor: colors2.colorBg
|
|
2784
2795
|
},
|
|
2785
2796
|
disabled && { opacity: 0.4 }
|
|
@@ -2980,14 +2991,14 @@ var styles36 = StyleSheet37.create({
|
|
|
2980
2991
|
gap: spacing.xxs
|
|
2981
2992
|
},
|
|
2982
2993
|
/**
|
|
2983
|
-
* The wrapper draws the rounded border
|
|
2994
|
+
* The wrapper draws the rounded border (`radius.field` from the theme) and provides
|
|
2995
|
+
* at least 44pt touch-target height.
|
|
2984
2996
|
* justifyContent: 'center' vertically centers the inner TextInput on both iOS and Android.
|
|
2985
2997
|
*/
|
|
2986
2998
|
inputWrapper: {
|
|
2987
2999
|
minHeight: INPUT_HEIGHT2,
|
|
2988
3000
|
justifyContent: "center",
|
|
2989
|
-
borderWidth: 1
|
|
2990
|
-
borderRadius: border.radius.m
|
|
3001
|
+
borderWidth: 1
|
|
2991
3002
|
},
|
|
2992
3003
|
/**
|
|
2993
3004
|
* No vertical padding: iOS centers text automatically inside the wrapper height.
|
|
@@ -3020,7 +3031,7 @@ function FormInput({
|
|
|
3020
3031
|
onFocus,
|
|
3021
3032
|
style
|
|
3022
3033
|
}) {
|
|
3023
|
-
const { colors: colors2 } = useTheme();
|
|
3034
|
+
const { colors: colors2, radius } = useTheme();
|
|
3024
3035
|
const [focused, setFocused] = useState8(false);
|
|
3025
3036
|
const borderColor = error ? colors2.colorError : focused ? colors2.colorInteractive : colors2.colorOnBg;
|
|
3026
3037
|
return /* @__PURE__ */ jsxs24(View32, { style: [styles36.container, style], children: [
|
|
@@ -3032,6 +3043,7 @@ function FormInput({
|
|
|
3032
3043
|
styles36.inputWrapper,
|
|
3033
3044
|
{
|
|
3034
3045
|
borderColor,
|
|
3046
|
+
borderRadius: radius.field,
|
|
3035
3047
|
backgroundColor: colors2.colorBg
|
|
3036
3048
|
},
|
|
3037
3049
|
disabled && { opacity: 0.4 }
|
|
@@ -3088,7 +3100,6 @@ var styles37 = StyleSheet38.create({
|
|
|
3088
3100
|
paddingHorizontal: spacing.s,
|
|
3089
3101
|
paddingVertical: spacing.xs,
|
|
3090
3102
|
borderWidth: 1,
|
|
3091
|
-
borderRadius: border.radius.m,
|
|
3092
3103
|
minHeight: 100,
|
|
3093
3104
|
textAlignVertical: "top"
|
|
3094
3105
|
}
|
|
@@ -3108,7 +3119,7 @@ function Textarea({
|
|
|
3108
3119
|
onFocus,
|
|
3109
3120
|
style
|
|
3110
3121
|
}) {
|
|
3111
|
-
const { colors: colors2 } = useTheme();
|
|
3122
|
+
const { colors: colors2, radius } = useTheme();
|
|
3112
3123
|
const [focused, setFocused] = useState9(false);
|
|
3113
3124
|
const borderColor = error ? colors2.colorError : focused ? colors2.colorInteractive : colors2.colorOnBg;
|
|
3114
3125
|
return /* @__PURE__ */ jsxs25(View33, { style: [styles37.container, style], children: [
|
|
@@ -3139,6 +3150,7 @@ function Textarea({
|
|
|
3139
3150
|
styles37.textarea,
|
|
3140
3151
|
{
|
|
3141
3152
|
borderColor,
|
|
3153
|
+
borderRadius: radius.field,
|
|
3142
3154
|
color: colors2.colorOnBg,
|
|
3143
3155
|
backgroundColor: colors2.colorBg
|
|
3144
3156
|
},
|
|
@@ -3166,7 +3178,6 @@ var styles38 = StyleSheet39.create({
|
|
|
3166
3178
|
paddingHorizontal: spacing.s,
|
|
3167
3179
|
paddingVertical: spacing.xs,
|
|
3168
3180
|
borderWidth: 1,
|
|
3169
|
-
borderRadius: border.radius.m,
|
|
3170
3181
|
minHeight: 44
|
|
3171
3182
|
},
|
|
3172
3183
|
triggerText: {
|
|
@@ -3231,7 +3242,7 @@ function Select({
|
|
|
3231
3242
|
onChange,
|
|
3232
3243
|
style
|
|
3233
3244
|
}) {
|
|
3234
|
-
const { colors: colors2 } = useTheme();
|
|
3245
|
+
const { colors: colors2, radius } = useTheme();
|
|
3235
3246
|
const [open, setOpen] = useState10(false);
|
|
3236
3247
|
const selectedOption = options.find((o) => o.value === value);
|
|
3237
3248
|
const displayText = selectedOption?.label ?? placeholder;
|
|
@@ -3256,6 +3267,7 @@ function Select({
|
|
|
3256
3267
|
styles38.trigger,
|
|
3257
3268
|
{
|
|
3258
3269
|
borderColor,
|
|
3270
|
+
borderRadius: radius.field,
|
|
3259
3271
|
backgroundColor: colors2.colorBg
|
|
3260
3272
|
},
|
|
3261
3273
|
disabled && { opacity: 0.4 }
|
|
@@ -3551,14 +3563,11 @@ var styles40 = StyleSheet41.create({
|
|
|
3551
3563
|
justifyContent: "center",
|
|
3552
3564
|
borderWidth: 1
|
|
3553
3565
|
},
|
|
3566
|
+
// The steppers round only the field's outer corners, with `radius.field` from the theme.
|
|
3554
3567
|
stepperStart: {
|
|
3555
|
-
borderTopStartRadius: border.radius.m,
|
|
3556
|
-
borderBottomStartRadius: border.radius.m,
|
|
3557
3568
|
borderEndWidth: 0
|
|
3558
3569
|
},
|
|
3559
3570
|
stepperEnd: {
|
|
3560
|
-
borderTopEndRadius: border.radius.m,
|
|
3561
|
-
borderBottomEndRadius: border.radius.m,
|
|
3562
3571
|
borderStartWidth: 0
|
|
3563
3572
|
},
|
|
3564
3573
|
stepperText: {
|
|
@@ -3609,7 +3618,7 @@ function NumberInput({
|
|
|
3609
3618
|
onChange,
|
|
3610
3619
|
style
|
|
3611
3620
|
}) {
|
|
3612
|
-
const { colors: colors2 } = useTheme();
|
|
3621
|
+
const { colors: colors2, radius } = useTheme();
|
|
3613
3622
|
const isControlled = value !== void 0;
|
|
3614
3623
|
const [internalValue, setInternalValue] = useState12(defaultValue ?? 0);
|
|
3615
3624
|
const currentValue = isControlled ? value : internalValue;
|
|
@@ -3641,6 +3650,8 @@ function NumberInput({
|
|
|
3641
3650
|
styles40.stepperStart,
|
|
3642
3651
|
{
|
|
3643
3652
|
borderColor,
|
|
3653
|
+
borderTopStartRadius: radius.field,
|
|
3654
|
+
borderBottomStartRadius: radius.field,
|
|
3644
3655
|
backgroundColor: colors2.colorBgSubtle
|
|
3645
3656
|
},
|
|
3646
3657
|
pressed && { opacity: 0.7 },
|
|
@@ -3706,6 +3717,8 @@ function NumberInput({
|
|
|
3706
3717
|
styles40.stepperEnd,
|
|
3707
3718
|
{
|
|
3708
3719
|
borderColor,
|
|
3720
|
+
borderTopEndRadius: radius.field,
|
|
3721
|
+
borderBottomEndRadius: radius.field,
|
|
3709
3722
|
backgroundColor: colors2.colorBgSubtle
|
|
3710
3723
|
},
|
|
3711
3724
|
pressed && { opacity: 0.7 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boostdev/design-system-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "BoostDev React Native component library: accessible, token-driven components built on @boostdev/design-system-foundation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -47,7 +47,15 @@ export default function App() {
|
|
|
47
47
|
|
|
48
48
|
## Shape
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
The corner radius comes from the theme, not from the button: a button with a text label uses `radius.button` on every size, an icon-only button (`isIconOnly`) uses `radius.iconButton`. By default these are `border.radius.m` (the same as the form fields) and `border.radius.full` (round). Set them per app through `ThemeProvider`, the native equivalent of the web `--button_radius`:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<ThemeProvider tokens={{ radius: { button: border.radius.s, iconButton: border.radius.s } }}>
|
|
54
|
+
<App />
|
|
55
|
+
</ThemeProvider>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A nested `ThemeProvider` keeps its parent's radii unless it sets its own. A `borderRadius` in `style` overrides the theme.
|
|
51
59
|
|
|
52
60
|
## Icons
|
|
53
61
|
|
|
@@ -56,7 +64,7 @@ A button with a text label has `border.radius.m` corners on every size, the same
|
|
|
56
64
|
|
|
57
65
|
### Icon-only (1:1 aspect ratio)
|
|
58
66
|
|
|
59
|
-
Set `isIconOnly` to render a
|
|
67
|
+
Set `isIconOnly` to render a 1:1 button (round by default, see [Shape](#shape)) with equal `spacing.xs` padding on both axes. Composable with any `variant`. Always pair with `accessibilityLabel` for assistive tech.
|
|
60
68
|
|
|
61
69
|
<Canvas of={Stories.IconOnly} />
|
|
62
70
|
<Canvas of={Stories.IconOnlyOutline} />
|