react-native-tinyui 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +383 -53
- package/ios/ConcentricView/TinyuiConcentricView.h +12 -0
- package/ios/ConcentricView/TinyuiConcentricView.mm +60 -0
- package/ios/Core/Tinyui.mm +54 -0
- package/ios/Core/TinyuiComponentClasses.mm +44 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextProvider.swift +64 -79
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextShadowNode.h +46 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextShadowNode.mm +159 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextView.mm +6 -27
- package/ios/LiquidGlassText/TinyuiTextOutlineShape.swift +5 -2
- package/ios/Menu/TinyuiMenuBuilder.swift +238 -0
- package/ios/Menu/TinyuiMenuProvider.swift +51 -85
- package/ios/Menu/TinyuiMenuView.mm +13 -35
- package/ios/Popover/TinyuiPopoverProvider.swift +198 -44
- package/ios/Popover/TinyuiPopoverView.mm +26 -1
- package/ios/SFSymbol/TinyuiSFSymbolEffects.h +9 -0
- package/ios/SFSymbol/TinyuiSFSymbolEffects.mm +180 -0
- package/ios/SFSymbol/TinyuiSFSymbolImage.h +8 -0
- package/ios/SFSymbol/TinyuiSFSymbolImage.mm +141 -0
- package/ios/SFSymbol/TinyuiSFSymbolShadowNode.h +31 -0
- package/ios/SFSymbol/TinyuiSFSymbolShadowNode.mm +30 -0
- package/ios/SFSymbol/TinyuiSFSymbolView.h +4 -0
- package/ios/SFSymbol/TinyuiSFSymbolView.mm +173 -0
- package/ios/Stepper/TinyuiStepperShadowNode.h +32 -0
- package/ios/Stepper/TinyuiStepperShadowNode.mm +28 -0
- package/ios/Stepper/TinyuiStepperView.h +12 -0
- package/ios/Stepper/TinyuiStepperView.mm +128 -0
- package/ios/TipKit/TinyuiTip.swift +31 -0
- package/ios/TipKit/TinyuiTipPopoverProvider.swift +276 -0
- package/ios/TipKit/TinyuiTipPopoverView.h +8 -0
- package/ios/TipKit/TinyuiTipPopoverView.mm +114 -0
- package/ios/TipKit/TinyuiTipsRuntime.swift +148 -0
- package/lib/module/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
- package/lib/module/ConcentricView/index.js +29 -0
- package/lib/module/ConcentricView/index.js.map +1 -0
- package/lib/module/ConcentricView/types.js +4 -0
- package/lib/module/ConcentricView/types.js.map +1 -0
- package/lib/module/LiquidGlassText/LiquidGlassTextNativeComponent.ts +2 -12
- package/lib/module/LiquidGlassText/index.js +60 -35
- package/lib/module/LiquidGlassText/index.js.map +1 -1
- package/lib/module/Menu/MenuNativeComponent.ts +7 -5
- package/lib/module/Menu/index.js +4 -9
- package/lib/module/Menu/index.js.map +1 -1
- package/lib/module/Menu/options.js +4 -12
- package/lib/module/Menu/options.js.map +1 -1
- package/lib/module/NativeTinyui.js.map +1 -1
- package/lib/module/Popover/index.js +4 -2
- package/lib/module/Popover/index.js.map +1 -1
- package/lib/module/SFSymbol/SFSymbolNativeComponent.ts +20 -0
- package/lib/module/SFSymbol/index.js +73 -0
- package/lib/module/SFSymbol/index.js.map +1 -0
- package/lib/module/SFSymbol/options.js +79 -0
- package/lib/module/SFSymbol/options.js.map +1 -0
- package/lib/module/SFSymbol/types.js +4 -0
- package/lib/module/SFSymbol/types.js.map +1 -0
- package/lib/module/Stepper/StepperNativeComponent.ts +29 -0
- package/lib/module/Stepper/index.js +75 -0
- package/lib/module/Stepper/index.js.map +1 -0
- package/lib/module/Stepper/types.js +4 -0
- package/lib/module/Stepper/types.js.map +1 -0
- package/lib/module/TipKit/Popover.js +89 -0
- package/lib/module/TipKit/Popover.js.map +1 -0
- package/lib/module/TipKit/TipPopoverNativeComponent.ts +37 -0
- package/lib/module/TipKit/index.js +28 -0
- package/lib/module/TipKit/index.js.map +1 -0
- package/lib/module/TipKit/types.js +4 -0
- package/lib/module/TipKit/types.js.map +1 -0
- package/lib/module/TipKit/validation.js +24 -0
- package/lib/module/TipKit/validation.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/getNativeTinyui.js.map +1 -1
- package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts +7 -0
- package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/ConcentricView/index.d.ts +4 -0
- package/lib/typescript/src/ConcentricView/index.d.ts.map +1 -0
- package/lib/typescript/src/ConcentricView/types.d.ts +6 -0
- package/lib/typescript/src/ConcentricView/types.d.ts.map +1 -0
- package/lib/typescript/src/LiquidGlassText/LiquidGlassTextNativeComponent.d.ts +2 -8
- package/lib/typescript/src/LiquidGlassText/LiquidGlassTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassText/index.d.ts +1 -1
- package/lib/typescript/src/LiquidGlassText/index.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassText/types.d.ts +13 -22
- package/lib/typescript/src/LiquidGlassText/types.d.ts.map +1 -1
- package/lib/typescript/src/Menu/MenuNativeComponent.d.ts +4 -5
- package/lib/typescript/src/Menu/MenuNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/Menu/index.d.ts +1 -1
- package/lib/typescript/src/Menu/index.d.ts.map +1 -1
- package/lib/typescript/src/Menu/options.d.ts +40 -4
- package/lib/typescript/src/Menu/options.d.ts.map +1 -1
- package/lib/typescript/src/Menu/types.d.ts +16 -4
- package/lib/typescript/src/Menu/types.d.ts.map +1 -1
- package/lib/typescript/src/NativeTinyui.d.ts +2 -0
- package/lib/typescript/src/NativeTinyui.d.ts.map +1 -1
- package/lib/typescript/src/Popover/index.d.ts.map +1 -1
- package/lib/typescript/src/Popover/types.d.ts +2 -0
- package/lib/typescript/src/Popover/types.d.ts.map +1 -1
- package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts +11 -0
- package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/index.d.ts +4 -0
- package/lib/typescript/src/SFSymbol/index.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/options.d.ts +86 -0
- package/lib/typescript/src/SFSymbol/options.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/types.d.ts +113 -0
- package/lib/typescript/src/SFSymbol/types.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts +21 -0
- package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/index.d.ts +4 -0
- package/lib/typescript/src/Stepper/index.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/types.d.ts +24 -0
- package/lib/typescript/src/Stepper/types.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/Popover.d.ts +3 -0
- package/lib/typescript/src/TipKit/Popover.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts +38 -0
- package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/index.d.ts +10 -0
- package/lib/typescript/src/TipKit/index.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/types.d.ts +46 -0
- package/lib/typescript/src/TipKit/types.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/validation.d.ts +4 -0
- package/lib/typescript/src/TipKit/validation.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/getNativeTinyui.d.ts +1 -1
- package/lib/typescript/src/utils/getNativeTinyui.d.ts.map +1 -1
- package/package.json +37 -2
- package/{Tinyui.podspec → react-native-tinyui.podspec} +23 -2
- package/src/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
- package/src/ConcentricView/index.tsx +34 -0
- package/src/ConcentricView/types.ts +6 -0
- package/src/LiquidGlassText/LiquidGlassTextNativeComponent.ts +2 -12
- package/src/LiquidGlassText/index.tsx +73 -39
- package/src/LiquidGlassText/types.ts +24 -50
- package/src/Menu/MenuNativeComponent.ts +7 -5
- package/src/Menu/index.tsx +6 -6
- package/src/Menu/options.ts +4 -18
- package/src/Menu/types.ts +18 -4
- package/src/NativeTinyui.ts +2 -0
- package/src/Popover/index.tsx +8 -3
- package/src/Popover/types.ts +2 -0
- package/src/SFSymbol/SFSymbolNativeComponent.ts +20 -0
- package/src/SFSymbol/index.tsx +89 -0
- package/src/SFSymbol/options.ts +116 -0
- package/src/SFSymbol/types.ts +158 -0
- package/src/Stepper/StepperNativeComponent.ts +29 -0
- package/src/Stepper/index.tsx +91 -0
- package/src/Stepper/types.ts +24 -0
- package/src/TipKit/Popover.tsx +87 -0
- package/src/TipKit/TipPopoverNativeComponent.ts +37 -0
- package/src/TipKit/index.ts +47 -0
- package/src/TipKit/types.ts +57 -0
- package/src/TipKit/validation.ts +39 -0
- package/src/index.tsx +32 -4
- package/src/utils/getNativeTinyui.ts +8 -1
- package/ios/Core/TinyuiHostingHelpers.swift +0 -74
- package/ios/Menu/TinyuiMenuViewSwift.swift +0 -408
- package/ios/Popover/TinyuiPopoverViewSwift.swift +0 -88
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# react-native-tinyui
|
|
2
2
|
|
|
3
|
-
Dependency-free, native iOS `Menu`, `Popover` and `LiquidGlassText` components for React Native's
|
|
3
|
+
Dependency-free, native iOS `Menu`, `Popover`, `TipKit`, `Stepper`, `ConcentricView`, `SFSymbol` and `LiquidGlassText` components for React Native's
|
|
4
4
|
New Architecture. The public API follows normal React composition patterns;
|
|
5
|
-
the native UI is
|
|
5
|
+
the native UI is exposed through Fabric components. `Menu`, `Popover`, `Stepper`, `ConcentricView` and `SFSymbol` are
|
|
6
|
+
implemented directly with UIKit; `LiquidGlassText` uses SwiftUI where needed.
|
|
6
7
|
|
|
7
8
|
The initial component behavior is inspired by
|
|
8
9
|
[`@expo/ui`](https://github.com/expo/expo/tree/main/packages/expo-ui), but this
|
|
9
10
|
package does not require Expo Modules or any other runtime package. The
|
|
10
|
-
React Native ↔
|
|
11
|
+
React Native ↔ native view integration follows the approach used by
|
|
11
12
|
[`react-native-pager-view`](https://github.com/callstack/react-native-pager-view):
|
|
12
|
-
a Fabric component
|
|
13
|
+
a Fabric component owns the platform view that renders its content.
|
|
13
14
|
|
|
14
15
|
## Requirements
|
|
15
16
|
|
|
@@ -45,6 +46,10 @@ Available components:
|
|
|
45
46
|
- `Menu`
|
|
46
47
|
- `Popover`
|
|
47
48
|
- `LiquidGlassText`
|
|
49
|
+
- `Stepper`
|
|
50
|
+
- `ConcentricView`
|
|
51
|
+
- `SFSymbol`
|
|
52
|
+
- `TipKit` (enables `TipKit.Popover`, `TipKit.configure` and `TipKit.invalidate`)
|
|
48
53
|
|
|
49
54
|
All components are enabled by default. Use an empty `components` array to
|
|
50
55
|
compile only the TurboModule core. Run `pod install` again whenever this list
|
|
@@ -57,6 +62,10 @@ Component-specific JavaScript entry points are also available:
|
|
|
57
62
|
import { Menu } from 'react-native-tinyui/menu';
|
|
58
63
|
import { Popover } from 'react-native-tinyui/popover';
|
|
59
64
|
import { LiquidGlassText } from 'react-native-tinyui/liquid-glass-text';
|
|
65
|
+
import { Stepper } from 'react-native-tinyui/stepper';
|
|
66
|
+
import { ConcentricView } from 'react-native-tinyui/concentric-view';
|
|
67
|
+
import { SFSymbol } from 'react-native-tinyui/sf-symbol';
|
|
68
|
+
import { TipKit } from 'react-native-tinyui/tip-kit';
|
|
60
69
|
```
|
|
61
70
|
|
|
62
71
|
The root `react-native-tinyui` imports remain supported for backwards
|
|
@@ -66,9 +75,8 @@ compatibility. JavaScript entry points provide cleaner dependency boundaries;
|
|
|
66
75
|
## Menu
|
|
67
76
|
|
|
68
77
|
`Menu` uses `children` as its trigger and receives its native menu entries
|
|
69
|
-
through the `options` prop.
|
|
70
|
-
|
|
71
|
-
menu.
|
|
78
|
+
through the `options` prop. A tap opens the native menu; `onActionPress`
|
|
79
|
+
receives every selected action's stable id and displayed title.
|
|
72
80
|
|
|
73
81
|
```tsx
|
|
74
82
|
import { Menu } from 'react-native-tinyui';
|
|
@@ -76,34 +84,40 @@ import { Menu } from 'react-native-tinyui';
|
|
|
76
84
|
<Menu
|
|
77
85
|
accessibilityLabel="Document actions"
|
|
78
86
|
title="Document actions"
|
|
79
|
-
|
|
87
|
+
onActionPress={({ nativeEvent }) => {
|
|
88
|
+
if (nativeEvent.id === 'rename') rename();
|
|
89
|
+
if (nativeEvent.id === 'toggle-pinned') togglePinned();
|
|
90
|
+
if (nativeEvent.id === 'copy-link') copyLink();
|
|
91
|
+
if (nativeEvent.id === 'invite-people') invitePeople();
|
|
92
|
+
if (nativeEvent.id === 'remove') remove();
|
|
93
|
+
}}
|
|
80
94
|
options={[
|
|
81
95
|
{
|
|
96
|
+
id: 'rename',
|
|
82
97
|
title: 'Rename',
|
|
83
98
|
systemImage: 'square.and.pencil',
|
|
84
|
-
onSelect: rename,
|
|
85
99
|
},
|
|
86
100
|
{
|
|
101
|
+
id: 'toggle-pinned',
|
|
87
102
|
title: 'Pinned',
|
|
88
103
|
state: 'on',
|
|
89
104
|
systemImage: 'pin',
|
|
90
|
-
onSelect: togglePinned,
|
|
91
105
|
},
|
|
92
106
|
{
|
|
93
107
|
type: 'submenu',
|
|
94
108
|
title: 'Share',
|
|
95
109
|
systemImage: 'square.and.arrow.up',
|
|
96
110
|
options: [
|
|
97
|
-
{
|
|
98
|
-
{
|
|
111
|
+
{ id: 'copy-link', title: 'Copy link' },
|
|
112
|
+
{ id: 'invite-people', title: 'Invite people' },
|
|
99
113
|
],
|
|
100
114
|
},
|
|
101
115
|
{ type: 'divider' },
|
|
102
116
|
{
|
|
117
|
+
id: 'remove',
|
|
103
118
|
title: 'Delete',
|
|
104
119
|
destructive: true,
|
|
105
120
|
systemImage: 'trash',
|
|
106
|
-
onSelect: remove,
|
|
107
121
|
},
|
|
108
122
|
]}
|
|
109
123
|
>
|
|
@@ -119,11 +133,14 @@ or `type: 'divider'` for structural entries; nested entries use their own
|
|
|
119
133
|
simply render no image.
|
|
120
134
|
|
|
121
135
|
Action options support `subtitle`, `state`, `destructive`, `disabled`, `hidden`,
|
|
122
|
-
and `keepOpen`.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
and `keepOpen`. Set explicit `id` values when handling `onActionPress`; actions
|
|
137
|
+
without one receive a generated menu-path id. Use `icon` to load an image from
|
|
138
|
+
the containing app's asset catalog; `iconColor` tints either an asset image or
|
|
139
|
+
an SF Symbol and accepts React Native `ColorValue`s, including dynamic and
|
|
140
|
+
semantic iOS colors. UIKit doesn't expose custom menu title colors, so the
|
|
141
|
+
deprecated `titleColor` field is retained only for source compatibility; use
|
|
142
|
+
`destructive` for system red styling. Submenus support the same label fields
|
|
143
|
+
plus `destructive`, `disabled`, `hidden`, and `displayInline`.
|
|
127
144
|
|
|
128
145
|
## Popover
|
|
129
146
|
|
|
@@ -138,10 +155,9 @@ import { Popover, PopoverClose } from 'react-native-tinyui';
|
|
|
138
155
|
defaultOpen={false}
|
|
139
156
|
attachmentAnchor="bottom"
|
|
140
157
|
arrowEdge="top"
|
|
141
|
-
contentSize={{ width: 320, height: 220 }}
|
|
142
158
|
onOpenChange={(open) => console.log({ open })}
|
|
143
159
|
content={({ close }) => (
|
|
144
|
-
<View style={
|
|
160
|
+
<View style={{ width: 280, padding: 20 }}>
|
|
145
161
|
<Text>Any React Native content can be rendered here.</Text>
|
|
146
162
|
<PopoverClose close={close} style={styles.doneButton}>
|
|
147
163
|
<Text>Done</Text>
|
|
@@ -161,14 +177,120 @@ the `content` prop, which can be a node or a render function receiving
|
|
|
161
177
|
as `Popover.Close`) renders a `Pressable` that calls `close` when pressed.
|
|
162
178
|
|
|
163
179
|
Use `open` with `onOpenChange` for a controlled popover, or `defaultOpen` for an
|
|
164
|
-
uncontrolled one.
|
|
165
|
-
|
|
180
|
+
uncontrolled one. The layout of `content` determines the popover's preferred
|
|
181
|
+
size: set `width` and optionally `height` on its root view, or let its children
|
|
182
|
+
determine the height. Size changes are applied while the popover is visible.
|
|
183
|
+
UIKit may limit the displayed size to the available space on screen.
|
|
184
|
+
|
|
185
|
+
Keep the content background transparent to show the native popover material:
|
|
186
|
+
Liquid Glass on iOS 26+ when built with Xcode 26+, and the system popover
|
|
187
|
+
appearance on earlier iOS versions. `style` on `Popover` styles the trigger's
|
|
188
|
+
outer container, not the presented content.
|
|
189
|
+
|
|
190
|
+
## TipKit
|
|
191
|
+
|
|
192
|
+
`TipKit.Popover` anchors a native TipKit `TipUIPopoverViewController` to ordinary
|
|
193
|
+
React Native children on iOS 17+. The children retain their own touch handlers;
|
|
194
|
+
the tip appears automatically when both `enabled` and TipKit's eligibility allow
|
|
195
|
+
it. Its text, SF Symbol, action buttons, display history and invalidation use
|
|
196
|
+
Apple's TipKit framework. No additional package is required.
|
|
197
|
+
|
|
198
|
+
Configure TipKit once, **before mounting tips** (for example, during app startup):
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { TipKit, SFSymbol } from 'react-native-tinyui';
|
|
202
|
+
import { Pressable } from 'react-native';
|
|
203
|
+
|
|
204
|
+
await TipKit.configure({ displayFrequency: 'daily' });
|
|
205
|
+
|
|
206
|
+
// Inside your screen:
|
|
207
|
+
<TipKit.Popover
|
|
208
|
+
tipId="favorite-feature-v1"
|
|
209
|
+
title="Keep your favorites close"
|
|
210
|
+
message="Save an item to find it quickly next time."
|
|
211
|
+
systemImage="star"
|
|
212
|
+
enabled={isScreenFocused && !hasFavorites}
|
|
213
|
+
maxDisplayCount={3}
|
|
214
|
+
actions={[{ id: 'learn-more', title: 'Learn more' }]}
|
|
215
|
+
onActionPress={({ id }) => {
|
|
216
|
+
if (id === 'learn-more') openHelp();
|
|
217
|
+
}}
|
|
218
|
+
onStatusChange={(status) => console.log(status)}
|
|
219
|
+
onVisibleChange={(visible) => console.log({ visible })}
|
|
220
|
+
onError={(error) => console.warn(error.message)}
|
|
221
|
+
>
|
|
222
|
+
<Pressable onPress={async () => {
|
|
223
|
+
await saveFavorite();
|
|
224
|
+
await TipKit.invalidate('favorite-feature-v1', 'actionPerformed');
|
|
225
|
+
}}>
|
|
226
|
+
<SFSymbol name="star" size={24} />
|
|
227
|
+
</Pressable>
|
|
228
|
+
</TipKit.Popover>;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| Prop | Description | Default |
|
|
232
|
+
| --- | --- | --- |
|
|
233
|
+
| `tipId` | Required stable identity for persistent history | — |
|
|
234
|
+
| `title` | Required nonempty title, already localized by the app | — |
|
|
235
|
+
| `message` | Supporting plain text | — |
|
|
236
|
+
| `systemImage` | SF Symbol name | — |
|
|
237
|
+
| `actions` | Buttons with unique nonempty `{ id, title }` values | `[]` |
|
|
238
|
+
| `enabled` | Allows presentation; does not override TipKit eligibility | `true` |
|
|
239
|
+
| `maxDisplayCount` | Positive integer; automatically invalidates after this many displays | Unlimited |
|
|
240
|
+
| `ignoresDisplayFrequency` | Exempts this tip from the app-wide display interval | `false` |
|
|
241
|
+
| `arrowEdge` | Preferred bubble edge: `top`, `bottom`, `leading`, `trailing`, `auto` | `auto` |
|
|
242
|
+
| `onStatusChange` | `{ status: 'pending' \| 'available' }` or `{ status: 'invalidated', reason }` | — |
|
|
243
|
+
| `onVisibleChange` | Whether the native bubble is actually visible | — |
|
|
244
|
+
| `onActionPress` | Receives the selected `{ id, title }`; does not automatically invalidate | — |
|
|
245
|
+
| `onError` | Receives `{ code, message }`; otherwise a warning is logged | — |
|
|
246
|
+
|
|
247
|
+
The component accepts standard `ViewProps` and a native view ref. `style` lays
|
|
248
|
+
out the anchor, not the bubble. UIKit chooses the bubble's size and may adapt
|
|
249
|
+
the arrow to available space; leading/trailing follow layout direction. Bubble
|
|
250
|
+
content is native text/images/actions; `children` supplies only the anchor.
|
|
251
|
+
Content edits apply to the next presentation; the currently visible bubble
|
|
252
|
+
retains the content and action labels it was presented with.
|
|
253
|
+
|
|
254
|
+
### Configuration and lifetime
|
|
255
|
+
|
|
256
|
+
`TipKit.configure({ displayFrequency })` returns a promise. Supported frequencies
|
|
257
|
+
are `immediate`, `hourly`, `daily` (default), `weekly` and `monthly`. This is an
|
|
258
|
+
app-wide TipKit setting. Repeating the same configuration is safe, including
|
|
259
|
+
after Fast Refresh; changing it later rejects with `E_TIPS_ALREADY_CONFIGURED`.
|
|
260
|
+
Coordinate this startup configuration with any other native TipKit integration.
|
|
261
|
+
Initialization errors reject the promise; an enabled view mounted too early
|
|
262
|
+
reports `E_TIPS_NOT_CONFIGURED` and can recover after configuration succeeds.
|
|
263
|
+
|
|
264
|
+
`TipKit.invalidate(tipId, reason = 'actionPerformed')` also returns a promise and
|
|
265
|
+
requires configuration first. It persistently invalidates the ID even when no
|
|
266
|
+
view for that tip is mounted. `reason` may be `actionPerformed` or `tipClosed`.
|
|
267
|
+
System invalidations can additionally report `displayCountExceeded`,
|
|
268
|
+
`displayDurationExceeded`, or `unknown` through `onStatusChange`.
|
|
269
|
+
|
|
270
|
+
Keep IDs stable across renders, app launches and translations. All instances
|
|
271
|
+
with the same ID share their TipKit history. Keep `maxDisplayCount` and
|
|
272
|
+
`ignoresDisplayFrequency` consistent for each ID; conflicting values report
|
|
273
|
+
`E_CONFLICTING_TIP_OPTIONS`. Use a deliberate new versioned ID for a new feature
|
|
274
|
+
tip. Hiding a tip, unmounting it or reconfiguring TinyUI never resets history.
|
|
275
|
+
|
|
276
|
+
Use `enabled={isScreenFocused && businessCondition}` with your navigation
|
|
277
|
+
library. TinyUI also waits for a visible anchor and a free presenter, and
|
|
278
|
+
dismisses on detachment or when the anchor leaves the visible area. It presents
|
|
279
|
+
at most one TinyUI tip at a time and waits while another modal occupies the
|
|
280
|
+
presenter. An outside dismissal suppresses immediate reopening for that mount;
|
|
281
|
+
toggle `enabled` off and on to allow another attempt, subject to TipKit's state.
|
|
282
|
+
The system close button can permanently invalidate a tip.
|
|
283
|
+
|
|
284
|
+
This first version exposes popover tips and JS eligibility conditions. Inline
|
|
285
|
+
`TipUIView`, native rule/event builders, TipGroup and datastore reset APIs are
|
|
286
|
+
not exposed. The example's **New demo tip** button deliberately creates another
|
|
287
|
+
ID for trying the behavior again without clearing the app's TipKit datastore.
|
|
166
288
|
|
|
167
289
|
## LiquidGlassText
|
|
168
290
|
|
|
169
291
|
`LiquidGlassText` renders native glass inside the glyph outlines, based on
|
|
170
|
-
[GlassText](https://github.com/ailtonvivaz/GlassText). It
|
|
171
|
-
|
|
292
|
+
[GlassText](https://github.com/ailtonvivaz/GlassText). It exposes the effect and
|
|
293
|
+
typography through React props.
|
|
172
294
|
Background image loading is not part of this component.
|
|
173
295
|
|
|
174
296
|
Building this component requires **Xcode 26+**. Glass is rendered on **iOS 26+**;
|
|
@@ -180,48 +302,49 @@ import { LiquidGlassText } from 'react-native-tinyui';
|
|
|
180
302
|
|
|
181
303
|
<LiquidGlassText
|
|
182
304
|
text="Liquid Glass"
|
|
183
|
-
|
|
184
|
-
|
|
305
|
+
effect="regular"
|
|
306
|
+
tint="#00BBDD"
|
|
307
|
+
interactive
|
|
308
|
+
fontDesign="rounded"
|
|
309
|
+
textStyle={{ fontSize: 48, fontWeight: '700' }}
|
|
185
310
|
/>;
|
|
186
311
|
|
|
187
312
|
<LiquidGlassText
|
|
188
313
|
text={'Multi-line\nGlass Text\nEffect'}
|
|
189
|
-
glass="clear"
|
|
190
|
-
font="largeTitle"
|
|
191
|
-
fontWeight="heavy"
|
|
192
314
|
fontDesign="serif"
|
|
315
|
+
textStyle={{ fontSize: 34, fontWeight: '800' }}
|
|
193
316
|
multilineTextAlignment="center"
|
|
194
317
|
/>;
|
|
195
318
|
```
|
|
196
319
|
|
|
197
|
-
| Prop | Values
|
|
198
|
-
| ------------------------ |
|
|
199
|
-
| `text` | String (including text translated in JavaScript)
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `fontDesign` | `default`, `serif`, `monospaced`, `rounded`
|
|
204
|
-
| `
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
`
|
|
215
|
-
effect, with no tint or interactivity. `identity` applies no glass effect.
|
|
320
|
+
| Prop | Values | Default |
|
|
321
|
+
| ------------------------ | ------------------------------------------------ | --------- |
|
|
322
|
+
| `text` | String (including text translated in JavaScript) | Required |
|
|
323
|
+
| `effect` | `clear`, `regular`, `identity` | `clear` |
|
|
324
|
+
| `tint` | React Native `ColorValue` | — |
|
|
325
|
+
| `interactive` | Whether the glass responds to interaction | `false` |
|
|
326
|
+
| `fontDesign` | `default`, `serif`, `monospaced`, `rounded` | `default` |
|
|
327
|
+
| `textStyle` | Supported React Native text properties | — |
|
|
328
|
+
| `multilineTextAlignment` | `leading`, `center`, `trailing` | `leading` |
|
|
329
|
+
|
|
330
|
+
`textStyle` accepts arrays and `StyleSheet.create` references. Explicit
|
|
331
|
+
`multilineTextAlignment` overrides `textStyle.textAlign`. `textStyle.color`
|
|
332
|
+
supplies the glass tint and fallback text color; an explicit `tint` takes
|
|
333
|
+
precedence. `fontFamily` accepts the PostScript name of a font installed in the
|
|
334
|
+
app, and `fontDesign` only applies to system fonts.
|
|
335
|
+
|
|
336
|
+
`tint` accepts a React Native `ColorValue`, including `PlatformColor` and
|
|
337
|
+
`DynamicColorIOS`. `identity` applies no glass effect.
|
|
216
338
|
The component also accepts standard `ViewProps`, including `style`, `testID`,
|
|
217
339
|
accessibility props and a native view ref. The supplied text is used for
|
|
218
340
|
the default accessibility label; pass `accessibilityLabel` to override it.
|
|
219
341
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
produce a
|
|
342
|
+
Fabric measures the native text during layout and supplies its default width and
|
|
343
|
+
height without a JavaScript measurement pass. Layout styles can override these
|
|
344
|
+
dimensions, but do not resize the font or wrap text. As in GlassText, use
|
|
345
|
+
explicit `\n` characters for multiple lines. Keep padding on a surrounding
|
|
346
|
+
`View`. Color emoji and other glyphs without vector outlines cannot produce a
|
|
347
|
+
glass shape.
|
|
225
348
|
|
|
226
349
|
`text` accepts a string. Applications using JavaScript localization can pass
|
|
227
350
|
their translated string directly.
|
|
@@ -229,6 +352,213 @@ their translated string directly.
|
|
|
229
352
|
The adapted Core Text outline implementation retains the upstream MIT notice in
|
|
230
353
|
[`ios/LiquidGlassText/GlassText-LICENSE`](ios/LiquidGlassText/GlassText-LICENSE).
|
|
231
354
|
|
|
355
|
+
## Stepper
|
|
356
|
+
|
|
357
|
+
`Stepper` bridges UIKit's
|
|
358
|
+
[`UIStepper`](https://developer.apple.com/documentation/uikit/uistepper) through
|
|
359
|
+
Fabric. It supports fractional steps, press-and-hold repeat, and wrapping at
|
|
360
|
+
the bounds on iOS 17+.
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import { useState } from 'react';
|
|
364
|
+
import { Stepper } from 'react-native-tinyui';
|
|
365
|
+
|
|
366
|
+
function Quantity() {
|
|
367
|
+
const [quantity, setQuantity] = useState(1);
|
|
368
|
+
return (
|
|
369
|
+
<Stepper
|
|
370
|
+
accessibilityLabel="Quantity"
|
|
371
|
+
value={quantity}
|
|
372
|
+
minimumValue={1}
|
|
373
|
+
maximumValue={10}
|
|
374
|
+
onValueChange={setQuantity}
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Uncontrolled: UIKit owns the value after initialization.
|
|
380
|
+
<Stepper defaultValue={2.5} stepValue={0.5} maximumValue={5} wraps />;
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
| Prop | Description | Default |
|
|
384
|
+
| --- | --- | --- |
|
|
385
|
+
| `value` | Controlled numeric value | — |
|
|
386
|
+
| `defaultValue` | Initial value when uncontrolled; later changes are ignored | `0` |
|
|
387
|
+
| `minimumValue` | Lower bound | `0` |
|
|
388
|
+
| `maximumValue` | Upper bound, at least `minimumValue` | `100` |
|
|
389
|
+
| `stepValue` | Positive increment/decrement amount | `1` |
|
|
390
|
+
| `isContinuous` | Report changes during interaction; otherwise report on release | `true` |
|
|
391
|
+
| `autorepeat` | Repeatedly step while holding a button | `true` |
|
|
392
|
+
| `wraps` | Continue from the opposite bound when stepping past an end | `false` |
|
|
393
|
+
| `disabled` | Prevent user interaction | `false` |
|
|
394
|
+
| `onValueChange` | Callback receiving the new number after user interaction | — |
|
|
395
|
+
|
|
396
|
+
When `value` is supplied, update it in `onValueChange` to accept changes. Keeping
|
|
397
|
+
the same `value` restores the native control to that value. Omit `value` to let
|
|
398
|
+
UIKit manage the state. Programmatic value or range changes do not call
|
|
399
|
+
`onValueChange`.
|
|
400
|
+
|
|
401
|
+
All numeric props must be finite, `stepValue` must be positive, and
|
|
402
|
+
`maximumValue` must be at least `minimumValue`; invalid inputs throw a descriptive
|
|
403
|
+
error. Values are clamped to the current range. Equal bounds produce a fixed
|
|
404
|
+
value. Changing an uncontrolled range also clamps its current native value.
|
|
405
|
+
|
|
406
|
+
The component accepts standard `ViewProps` (except `children`) and a native view
|
|
407
|
+
ref. Native accessibility behavior is preserved; use `accessibilityLabel` to
|
|
408
|
+
name the value being adjusted. Fabric measures the native control's intrinsic
|
|
409
|
+
size. A larger `style` frame centers the native control without stretching
|
|
410
|
+
its buttons. Put padding on a surrounding `View`. Like UIKit's control, the
|
|
411
|
+
stepper shows the minus and plus buttons; render a separate `Text` for the value.
|
|
412
|
+
|
|
413
|
+
## ConcentricView
|
|
414
|
+
|
|
415
|
+
`ConcentricView` is a Fabric container that lets UIKit resolve its corner radii
|
|
416
|
+
relative to its containing view. On iOS 26+, it applies
|
|
417
|
+
[`UICornerRadius.containerConcentricRadius`](https://developer.apple.com/documentation/uikit/uicornerradius-c.class/containerconcentricradius)
|
|
418
|
+
through `UIView.cornerConfiguration`. UIKit handles geometry and layout changes.
|
|
419
|
+
Building this component requires **Xcode 26+**; on iOS 17–25 it uses
|
|
420
|
+
`minimumRadius` as a fixed corner radius.
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import { Text } from 'react-native';
|
|
424
|
+
import { ConcentricView } from 'react-native-tinyui';
|
|
425
|
+
|
|
426
|
+
<ConcentricView
|
|
427
|
+
minimumRadius={32}
|
|
428
|
+
style={{ padding: 12, backgroundColor: '#DCEBFF' }}
|
|
429
|
+
>
|
|
430
|
+
<ConcentricView
|
|
431
|
+
minimumRadius={12}
|
|
432
|
+
style={{ padding: 20, backgroundColor: '#3875D5' }}
|
|
433
|
+
>
|
|
434
|
+
<Text>UIKit resolves each corner.</Text>
|
|
435
|
+
</ConcentricView>
|
|
436
|
+
</ConcentricView>;
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`minimumRadius` defaults to `0` and must be finite and nonnegative. It only
|
|
440
|
+
controls the fixed fallback radius before iOS 26; iOS 26+ resolves corners
|
|
441
|
+
entirely through UIKit, without a minimum radius. The component accepts
|
|
442
|
+
`children`, standard `ViewProps`, and a native view ref. It clips content
|
|
443
|
+
by default; use `style={{ overflow: 'visible' }}` to allow overflow. Avoid
|
|
444
|
+
`style.borderRadius` and individual corner radii on this component.
|
|
445
|
+
React Native's custom border, outline, and shadow drawing does not calculate
|
|
446
|
+
concentric radii; keep those decorations on a surrounding view when needed.
|
|
447
|
+
|
|
448
|
+
## SFSymbol
|
|
449
|
+
|
|
450
|
+
`SFSymbol` uses **UIKit `UIImageView` + Apple's Symbols framework**, exposed
|
|
451
|
+
through Fabric. It does not embed a SwiftUI hosting view. Xcode 26+ is required
|
|
452
|
+
to build; the deployment target remains iOS 17. Yoga measures the configured
|
|
453
|
+
symbol's natural size, so `style.width` and `style.height` are optional.
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
import { useState } from 'react';
|
|
457
|
+
import { Button, PlatformColor, View } from 'react-native';
|
|
458
|
+
import { SFSymbol } from 'react-native-tinyui';
|
|
459
|
+
|
|
460
|
+
function Favorite() {
|
|
461
|
+
const [selected, setSelected] = useState(false);
|
|
462
|
+
const [trigger, setTrigger] = useState(0);
|
|
463
|
+
return (
|
|
464
|
+
<View>
|
|
465
|
+
<SFSymbol
|
|
466
|
+
name={selected ? 'heart.fill' : 'heart'}
|
|
467
|
+
size={32}
|
|
468
|
+
weight="semibold"
|
|
469
|
+
renderingMode="hierarchical"
|
|
470
|
+
color={PlatformColor('systemPinkColor')}
|
|
471
|
+
effect={{ type: 'bounce', trigger, options: { repeat: false } }}
|
|
472
|
+
contentTransition={{ type: 'magicReplace', direction: 'downUp' }}
|
|
473
|
+
/>
|
|
474
|
+
<Button title="Favorite" onPress={() => {
|
|
475
|
+
setSelected(!selected);
|
|
476
|
+
setTrigger(trigger + 1);
|
|
477
|
+
}} />
|
|
478
|
+
</View>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Image configuration
|
|
484
|
+
|
|
485
|
+
| Prop | Values / behavior |
|
|
486
|
+
| --- | --- |
|
|
487
|
+
| `name` | Required complete symbol name, including variants such as `square.fill`. Missing symbols render empty. |
|
|
488
|
+
| `source` | `system` (default), or `asset` for a custom SF Symbol in the app's asset catalog. Ordinary bitmap assets are not accepted. |
|
|
489
|
+
| `size` | Positive point size; default `17`. |
|
|
490
|
+
| `weight` | `unspecified` (default), `ultraLight`, `thin`, `light`, `regular`, `medium`, `semibold`, `bold`, `heavy`, `black`. |
|
|
491
|
+
| `scale` | `default`, `unspecified`, `small`, `medium`, `large`. |
|
|
492
|
+
| `textStyle` | `extraLargeTitle`, `extraLargeTitle2`, `largeTitle`, `title1`–`title3`, `headline`, `subheadline`, `body`, `callout`, `footnote`, `caption1`, `caption2`. Overrides `size` and supplies the style's weight unless explicitly set. |
|
|
493
|
+
| `fontFamily` | Font/PostScript name used to derive symbol metrics; unknown fonts use system metrics. |
|
|
494
|
+
| `allowFontScaling` | Default `true`. Scales the base point size by React Native's `fontScale`, including when using `textStyle`. |
|
|
495
|
+
| `maxFontSizeMultiplier` | Cap on that scale, >= 1. Omitted or `0` means unlimited. |
|
|
496
|
+
| `renderingMode` | `automatic` (default), `monochrome`, `hierarchical`, `palette`, `multicolor`. |
|
|
497
|
+
| `color` | React Native `ColorValue`, including `PlatformColor` and `DynamicColorIOS`. Defaults to semantic label color. |
|
|
498
|
+
| `paletteColors` | One to three `ColorValue`s in primary, secondary, tertiary order, used in palette mode. If omitted, the palette uses `color`. |
|
|
499
|
+
| `variableValue` | Progress from `0` to `1`; omitted uses the symbol default. Requires a symbol with variable annotations. |
|
|
500
|
+
| `variableValueMode` | `automatic` (default), `color`, `draw` (iOS 26+). |
|
|
501
|
+
| `colorRenderingMode` | `automatic` (default), `flat`, `gradient` (iOS 26+). |
|
|
502
|
+
| `resizeMode` | `center` (default, natural point size), `contain`, `cover`, `stretch`, for explicitly sized frames. Use `style.overflow: 'hidden'` to clip. |
|
|
503
|
+
|
|
504
|
+
Standard `ViewProps`, styles and refs are supported. Symbols are decorative
|
|
505
|
+
(`accessible={false}`) by default; supply `accessible` and a meaningful
|
|
506
|
+
`accessibilityLabel` for standalone images. Use a `Pressable` for interactive icons.
|
|
507
|
+
|
|
508
|
+
### Preset effects
|
|
509
|
+
|
|
510
|
+
Pass one object to `effect`, or an array of **distinct** effect types to combine
|
|
511
|
+
compatible effects. UIKit determines how combined effects interact.
|
|
512
|
+
|
|
513
|
+
| `type` | Configuration | Minimum iOS |
|
|
514
|
+
| --- | --- | --- |
|
|
515
|
+
| `bounce`, `scale`, `appear`, `disappear` | `direction: 'up' \| 'down'`, `scope` | 17 |
|
|
516
|
+
| `pulse` | `scope` | 17 |
|
|
517
|
+
| `variableColor` | `iteration: 'iterative' \| 'cumulative'`, `reversing: boolean`, `inactiveLayers: 'hide' \| 'dim'` | 17 |
|
|
518
|
+
| `wiggle` | `direction: 'up' \| 'down' \| 'left' \| 'right' \| 'forward' \| 'backward' \| 'clockwise' \| 'counterClockwise'`, or `angle` in degrees clockwise from +x; `scope` | 18 |
|
|
519
|
+
| `rotate` | `direction: 'clockwise' \| 'counterClockwise'`, `scope` | 18 |
|
|
520
|
+
| `breathe` | `style: 'plain' \| 'pulse'`, `scope` | 18 |
|
|
521
|
+
| `drawOn` | `scope`, also accepting `individually` | 26 |
|
|
522
|
+
| `drawOff` | `scope`, also accepting `individually`; `reversed: boolean` | 26 |
|
|
523
|
+
|
|
524
|
+
`scope` is `byLayer` or `wholeSymbol`; omit any modifier to keep Apple's default.
|
|
525
|
+
Each preset also accepts:
|
|
526
|
+
|
|
527
|
+
- `active` (default `true`): apply/remove the effect. Scale and visibility
|
|
528
|
+
effects hold their state until removed. Disappear/Draw Off can hide the symbol.
|
|
529
|
+
- `trigger`: a number or string. Change it to replay an effect. Mounting or
|
|
530
|
+
changing effect configuration also applies it; unrelated rerenders do not.
|
|
531
|
+
- `animated` (default `true`): animate application/removal of the effect.
|
|
532
|
+
- `options.speed`: positive speed multiplier, default system speed.
|
|
533
|
+
- `options.repeat`: a positive integer play count, `'forever'`, or `false` for
|
|
534
|
+
one play. Omit to use the preset's native repetition behavior.
|
|
535
|
+
- `options.repeatBehavior`: `periodic` or `continuous` (iOS 18+).
|
|
536
|
+
Continuous repeats forever and cannot be combined with a count or delay.
|
|
537
|
+
- `options.repeatDelay`: nonnegative delay in seconds between periodic plays
|
|
538
|
+
(iOS 18+). Without a count, periodic/delayed repetition continues indefinitely.
|
|
539
|
+
|
|
540
|
+
Repetition options only affect presets that support repeating. For example,
|
|
541
|
+
`scale` is a held state, not a looping animation. Removing `effect` clears all
|
|
542
|
+
effects. Detached/recycled views stop animations; reattaching applies them again.
|
|
543
|
+
|
|
544
|
+
### Replacement transitions and compatibility
|
|
545
|
+
|
|
546
|
+
`contentTransition` animates changes to the name or image configuration. It
|
|
547
|
+
accepts `type: 'automatic' | 'replace' | 'magicReplace'`, optional
|
|
548
|
+
`direction: 'downUp' | 'upUp' | 'offUp'`, `scope: 'byLayer' | 'wholeSymbol'`, and
|
|
549
|
+
a positive `speed`. Direction and scope configure Replace and the Magic Replace
|
|
550
|
+
fallback; Automatic uses the system transition. The initial image is not transitioned.
|
|
551
|
+
|
|
552
|
+
Unsupported presets are ignored on earlier iOS versions. Magic Replace falls
|
|
553
|
+
back to the configured Replace before iOS 18. Continuous repetition falls back
|
|
554
|
+
to periodic repetition on iOS 17; custom repeat delay is ignored. Before iOS 26,
|
|
555
|
+
variable mode and gradient configuration are ignored. Actual animation, palette,
|
|
556
|
+
multicolor and variable/draw support depend on each symbol's native annotations.
|
|
557
|
+
|
|
558
|
+
`respectReduceMotion` defaults to `true`: repeating/discrete effects and content
|
|
559
|
+
transitions are suppressed while Reduce Motion is enabled, while scale and
|
|
560
|
+
visibility state changes apply instantly. Set it to `false` to opt out.
|
|
561
|
+
|
|
232
562
|
## Contributing
|
|
233
563
|
|
|
234
564
|
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_CONCENTRIC_VIEW
|
|
2
|
+
|
|
3
|
+
#import "TinyuiConcentricView.h"
|
|
4
|
+
|
|
5
|
+
#import <react/renderer/components/TinyuiSpec/ComponentDescriptors.h>
|
|
6
|
+
#import <react/renderer/components/TinyuiSpec/Props.h>
|
|
7
|
+
#import <react/renderer/components/TinyuiSpec/RCTComponentViewHelpers.h>
|
|
8
|
+
|
|
9
|
+
using namespace facebook::react;
|
|
10
|
+
|
|
11
|
+
@interface TinyuiConcentricView () <RCTTinyuiConcentricViewViewProtocol>
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
@implementation TinyuiConcentricView
|
|
15
|
+
|
|
16
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
17
|
+
{
|
|
18
|
+
return concreteComponentDescriptorProvider<TinyuiConcentricViewComponentDescriptor>();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
22
|
+
{
|
|
23
|
+
if (self = [super initWithFrame:frame]) {
|
|
24
|
+
static const auto defaultProps = std::make_shared<const TinyuiConcentricViewProps>();
|
|
25
|
+
_props = defaultProps;
|
|
26
|
+
}
|
|
27
|
+
return self;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
|
|
31
|
+
{
|
|
32
|
+
[super finalizeUpdates:updateMask];
|
|
33
|
+
[self applyCornerConfiguration];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
|
37
|
+
{
|
|
38
|
+
[super traitCollectionDidChange:previousTraitCollection];
|
|
39
|
+
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
|
|
40
|
+
[self applyCornerConfiguration];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)applyCornerConfiguration
|
|
45
|
+
{
|
|
46
|
+
// Fabric updates layer.cornerRadius while applying view styles. Configure
|
|
47
|
+
// UIKit afterwards so it owns the resolved radii, including on layout changes.
|
|
48
|
+
if (@available(iOS 26.0, *)) {
|
|
49
|
+
self.cornerConfiguration = [UICornerConfiguration configurationWithRadius:
|
|
50
|
+
[UICornerRadius containerConcentricRadius]];
|
|
51
|
+
} else {
|
|
52
|
+
const auto &props = *std::static_pointer_cast<const TinyuiConcentricViewProps>(_props);
|
|
53
|
+
self.layer.cornerRadius = props.minimumRadius;
|
|
54
|
+
self.layer.cornerCurve = kCACornerCurveContinuous;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@end
|
|
59
|
+
|
|
60
|
+
#endif
|