react-native-tinyui 0.5.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.
Files changed (140) hide show
  1. package/README.md +351 -22
  2. package/ios/ConcentricView/TinyuiConcentricView.h +12 -0
  3. package/ios/ConcentricView/TinyuiConcentricView.mm +60 -0
  4. package/ios/Core/Tinyui.mm +54 -0
  5. package/ios/Core/TinyuiComponentClasses.mm +44 -0
  6. package/ios/Menu/TinyuiMenuBuilder.swift +238 -0
  7. package/ios/Menu/TinyuiMenuProvider.swift +51 -85
  8. package/ios/Menu/TinyuiMenuView.mm +12 -35
  9. package/ios/Popover/TinyuiPopoverProvider.swift +198 -44
  10. package/ios/Popover/TinyuiPopoverView.mm +26 -1
  11. package/ios/SFSymbol/TinyuiSFSymbolEffects.h +9 -0
  12. package/ios/SFSymbol/TinyuiSFSymbolEffects.mm +180 -0
  13. package/ios/SFSymbol/TinyuiSFSymbolImage.h +8 -0
  14. package/ios/SFSymbol/TinyuiSFSymbolImage.mm +141 -0
  15. package/ios/SFSymbol/TinyuiSFSymbolShadowNode.h +31 -0
  16. package/ios/SFSymbol/TinyuiSFSymbolShadowNode.mm +30 -0
  17. package/ios/SFSymbol/TinyuiSFSymbolView.h +4 -0
  18. package/ios/SFSymbol/TinyuiSFSymbolView.mm +173 -0
  19. package/ios/Stepper/TinyuiStepperShadowNode.h +32 -0
  20. package/ios/Stepper/TinyuiStepperShadowNode.mm +28 -0
  21. package/ios/Stepper/TinyuiStepperView.h +12 -0
  22. package/ios/Stepper/TinyuiStepperView.mm +128 -0
  23. package/ios/TipKit/TinyuiTip.swift +31 -0
  24. package/ios/TipKit/TinyuiTipPopoverProvider.swift +276 -0
  25. package/ios/TipKit/TinyuiTipPopoverView.h +8 -0
  26. package/ios/TipKit/TinyuiTipPopoverView.mm +114 -0
  27. package/ios/TipKit/TinyuiTipsRuntime.swift +148 -0
  28. package/lib/module/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
  29. package/lib/module/ConcentricView/index.js +29 -0
  30. package/lib/module/ConcentricView/index.js.map +1 -0
  31. package/lib/module/ConcentricView/types.js +4 -0
  32. package/lib/module/ConcentricView/types.js.map +1 -0
  33. package/lib/module/Menu/MenuNativeComponent.ts +1 -3
  34. package/lib/module/Menu/index.js +4 -9
  35. package/lib/module/Menu/index.js.map +1 -1
  36. package/lib/module/Menu/options.js +4 -12
  37. package/lib/module/Menu/options.js.map +1 -1
  38. package/lib/module/NativeTinyui.js.map +1 -1
  39. package/lib/module/Popover/index.js +4 -2
  40. package/lib/module/Popover/index.js.map +1 -1
  41. package/lib/module/SFSymbol/SFSymbolNativeComponent.ts +20 -0
  42. package/lib/module/SFSymbol/index.js +73 -0
  43. package/lib/module/SFSymbol/index.js.map +1 -0
  44. package/lib/module/SFSymbol/options.js +79 -0
  45. package/lib/module/SFSymbol/options.js.map +1 -0
  46. package/lib/module/SFSymbol/types.js +4 -0
  47. package/lib/module/SFSymbol/types.js.map +1 -0
  48. package/lib/module/Stepper/StepperNativeComponent.ts +29 -0
  49. package/lib/module/Stepper/index.js +75 -0
  50. package/lib/module/Stepper/index.js.map +1 -0
  51. package/lib/module/Stepper/types.js +4 -0
  52. package/lib/module/Stepper/types.js.map +1 -0
  53. package/lib/module/TipKit/Popover.js +89 -0
  54. package/lib/module/TipKit/Popover.js.map +1 -0
  55. package/lib/module/TipKit/TipPopoverNativeComponent.ts +37 -0
  56. package/lib/module/TipKit/index.js +28 -0
  57. package/lib/module/TipKit/index.js.map +1 -0
  58. package/lib/module/TipKit/types.js +4 -0
  59. package/lib/module/TipKit/types.js.map +1 -0
  60. package/lib/module/TipKit/validation.js +24 -0
  61. package/lib/module/TipKit/validation.js.map +1 -0
  62. package/lib/module/index.js +4 -0
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/utils/getNativeTinyui.js.map +1 -1
  65. package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts +7 -0
  66. package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts.map +1 -0
  67. package/lib/typescript/src/ConcentricView/index.d.ts +4 -0
  68. package/lib/typescript/src/ConcentricView/index.d.ts.map +1 -0
  69. package/lib/typescript/src/ConcentricView/types.d.ts +6 -0
  70. package/lib/typescript/src/ConcentricView/types.d.ts.map +1 -0
  71. package/lib/typescript/src/Menu/MenuNativeComponent.d.ts +2 -3
  72. package/lib/typescript/src/Menu/MenuNativeComponent.d.ts.map +1 -1
  73. package/lib/typescript/src/Menu/index.d.ts +1 -1
  74. package/lib/typescript/src/Menu/index.d.ts.map +1 -1
  75. package/lib/typescript/src/Menu/options.d.ts +1 -5
  76. package/lib/typescript/src/Menu/options.d.ts.map +1 -1
  77. package/lib/typescript/src/Menu/types.d.ts +16 -4
  78. package/lib/typescript/src/Menu/types.d.ts.map +1 -1
  79. package/lib/typescript/src/NativeTinyui.d.ts +2 -0
  80. package/lib/typescript/src/NativeTinyui.d.ts.map +1 -1
  81. package/lib/typescript/src/Popover/index.d.ts.map +1 -1
  82. package/lib/typescript/src/Popover/types.d.ts +2 -0
  83. package/lib/typescript/src/Popover/types.d.ts.map +1 -1
  84. package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts +11 -0
  85. package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts.map +1 -0
  86. package/lib/typescript/src/SFSymbol/index.d.ts +4 -0
  87. package/lib/typescript/src/SFSymbol/index.d.ts.map +1 -0
  88. package/lib/typescript/src/SFSymbol/options.d.ts +86 -0
  89. package/lib/typescript/src/SFSymbol/options.d.ts.map +1 -0
  90. package/lib/typescript/src/SFSymbol/types.d.ts +113 -0
  91. package/lib/typescript/src/SFSymbol/types.d.ts.map +1 -0
  92. package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts +21 -0
  93. package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts.map +1 -0
  94. package/lib/typescript/src/Stepper/index.d.ts +4 -0
  95. package/lib/typescript/src/Stepper/index.d.ts.map +1 -0
  96. package/lib/typescript/src/Stepper/types.d.ts +24 -0
  97. package/lib/typescript/src/Stepper/types.d.ts.map +1 -0
  98. package/lib/typescript/src/TipKit/Popover.d.ts +3 -0
  99. package/lib/typescript/src/TipKit/Popover.d.ts.map +1 -0
  100. package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts +38 -0
  101. package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts.map +1 -0
  102. package/lib/typescript/src/TipKit/index.d.ts +10 -0
  103. package/lib/typescript/src/TipKit/index.d.ts.map +1 -0
  104. package/lib/typescript/src/TipKit/types.d.ts +46 -0
  105. package/lib/typescript/src/TipKit/types.d.ts.map +1 -0
  106. package/lib/typescript/src/TipKit/validation.d.ts +4 -0
  107. package/lib/typescript/src/TipKit/validation.d.ts.map +1 -0
  108. package/lib/typescript/src/index.d.ts +9 -1
  109. package/lib/typescript/src/index.d.ts.map +1 -1
  110. package/lib/typescript/src/utils/getNativeTinyui.d.ts +1 -1
  111. package/lib/typescript/src/utils/getNativeTinyui.d.ts.map +1 -1
  112. package/package.json +37 -2
  113. package/{Tinyui.podspec → react-native-tinyui.podspec} +23 -2
  114. package/src/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
  115. package/src/ConcentricView/index.tsx +34 -0
  116. package/src/ConcentricView/types.ts +6 -0
  117. package/src/Menu/MenuNativeComponent.ts +1 -3
  118. package/src/Menu/index.tsx +6 -6
  119. package/src/Menu/options.ts +2 -20
  120. package/src/Menu/types.ts +18 -4
  121. package/src/NativeTinyui.ts +2 -0
  122. package/src/Popover/index.tsx +8 -3
  123. package/src/Popover/types.ts +2 -0
  124. package/src/SFSymbol/SFSymbolNativeComponent.ts +20 -0
  125. package/src/SFSymbol/index.tsx +89 -0
  126. package/src/SFSymbol/options.ts +116 -0
  127. package/src/SFSymbol/types.ts +158 -0
  128. package/src/Stepper/StepperNativeComponent.ts +29 -0
  129. package/src/Stepper/index.tsx +91 -0
  130. package/src/Stepper/types.ts +24 -0
  131. package/src/TipKit/Popover.tsx +87 -0
  132. package/src/TipKit/TipPopoverNativeComponent.ts +37 -0
  133. package/src/TipKit/index.ts +47 -0
  134. package/src/TipKit/types.ts +57 -0
  135. package/src/TipKit/validation.ts +39 -0
  136. package/src/index.tsx +30 -0
  137. package/src/utils/getNativeTinyui.ts +8 -1
  138. package/ios/Core/TinyuiHostingHelpers.swift +0 -74
  139. package/ios/Menu/TinyuiMenuViewSwift.swift +0 -326
  140. package/ios/Popover/TinyuiPopoverViewSwift.swift +0 -88
package/src/index.tsx CHANGED
@@ -1,6 +1,18 @@
1
1
  export { Menu } from './Menu';
2
+ export { TipKit } from './TipKit';
3
+ export type {
4
+ TipAction,
5
+ TipDisplayFrequency,
6
+ TipError,
7
+ TipInvalidationReason,
8
+ TipPopoverProps,
9
+ TipKitConfiguration,
10
+ TipStatus,
11
+ } from './TipKit';
2
12
  export type {
3
13
  MenuActionOption,
14
+ MenuActionPressEvent,
15
+ MenuActionPressEventData,
4
16
  MenuDividerOption,
5
17
  MenuItemState,
6
18
  MenuOption,
@@ -9,6 +21,24 @@ export type {
9
21
  MenuSubmenuOption,
10
22
  } from './Menu';
11
23
 
24
+ export { Stepper } from './Stepper';
25
+ export type { StepperProps } from './Stepper';
26
+
27
+ export { ConcentricView } from './ConcentricView';
28
+ export type { ConcentricViewProps } from './ConcentricView';
29
+
30
+ export { SFSymbol } from './SFSymbol';
31
+ export type {
32
+ SFSymbolContentTransition,
33
+ SFSymbolEffect,
34
+ SFSymbolEffectOptions,
35
+ SFSymbolProps,
36
+ SFSymbolRenderingMode,
37
+ SFSymbolScale,
38
+ SFSymbolTextStyle,
39
+ SFSymbolWeight,
40
+ } from './SFSymbol';
41
+
12
42
  export { Popover, PopoverClose } from './Popover';
13
43
  export type {
14
44
  PopoverArrowEdge,
@@ -1,6 +1,13 @@
1
1
  import NativeTinyui, { type Spec } from '../NativeTinyui';
2
2
 
3
- export type TinyuiComponent = 'Menu' | 'Popover' | 'LiquidGlassText';
3
+ export type TinyuiComponent =
4
+ | 'Menu'
5
+ | 'Popover'
6
+ | 'LiquidGlassText'
7
+ | 'Stepper'
8
+ | 'ConcentricView'
9
+ | 'SFSymbol'
10
+ | 'TipKit';
4
11
 
5
12
  let nativeTinyui: Spec | null | undefined;
6
13
  let enabledComponents: ReadonlySet<string> | undefined;
@@ -1,74 +0,0 @@
1
- import SwiftUI
2
- import UIKit
3
-
4
- /**
5
- * Identifiable wrapper used to render React Native views (UIView) inside
6
- * SwiftUI. Modeled after react-native-pager-view's bridge.
7
- */
8
- struct IdentifiablePlatformView: Identifiable, Equatable {
9
- let id = UUID()
10
- let view: UIView
11
-
12
- init(_ view: UIView) {
13
- self.view = view
14
- }
15
- }
16
-
17
- /**
18
- * Renders a UIView inside SwiftUI. Uses UIViewControllerRepresentable so the
19
- * hosted React view keeps a valid view-controller hierarchy.
20
- */
21
- struct RepresentableView: UIViewControllerRepresentable {
22
- var view: UIView
23
-
24
- func makeUIViewController(context: Context) -> PlatformChildViewController {
25
- let viewController = PlatformChildViewController()
26
- viewController.wrappedView = view
27
- return viewController
28
- }
29
-
30
- func updateUIViewController(
31
- _ uiViewController: PlatformChildViewController,
32
- context: Context
33
- ) {
34
- if uiViewController.wrappedView !== view {
35
- uiViewController.wrappedView = view
36
- }
37
- }
38
- }
39
-
40
- class PlatformChildViewController: UIViewController {
41
- var wrappedView: UIView? {
42
- didSet {
43
- guard oldValue !== wrappedView else { return }
44
- oldValue?.removeFromSuperview()
45
- if isViewLoaded {
46
- mountWrappedView()
47
- }
48
- }
49
- }
50
-
51
- override func viewDidLoad() {
52
- super.viewDidLoad()
53
- view.backgroundColor = .clear
54
- mountWrappedView()
55
- }
56
-
57
- private func mountWrappedView() {
58
- guard let wrappedView else { return }
59
- view.addSubview(wrappedView)
60
- wrappedView.translatesAutoresizingMaskIntoConstraints = false
61
- wrappedView.pinEdges(to: view)
62
- }
63
- }
64
-
65
- extension UIView {
66
- func pinEdges(to other: UIView) {
67
- NSLayoutConstraint.activate([
68
- leadingAnchor.constraint(equalTo: other.leadingAnchor),
69
- trailingAnchor.constraint(equalTo: other.trailingAnchor),
70
- topAnchor.constraint(equalTo: other.topAnchor),
71
- bottomAnchor.constraint(equalTo: other.bottomAnchor),
72
- ])
73
- }
74
- }
@@ -1,326 +0,0 @@
1
- import React
2
- import SwiftUI
3
- import UIKit
4
-
5
- /**
6
- * Observable props shared between the Objective-C++ component view and the
7
- * SwiftUI `Menu` implementation. Mirror of `TinyuiMenuProps` in codegen.
8
- */
9
- class TinyuiMenuProps: ObservableObject {
10
- @Published var menuConfig: [String: Any] = [:]
11
- @Published var title: String = ""
12
- @Published var disabled: Bool = false
13
- @Published var hasPrimaryAction: Bool = false
14
- @Published var triggerView: UIView?
15
- }
16
-
17
- /**
18
- * SwiftUI `Menu` backed by UIKit's UIMenu, matching expo-ui's MenuView.
19
- *
20
- * The React Native trigger (a UIView) is embedded as the menu label through
21
- * `RepresentableView`, so the tap target remains the native RN subtree while
22
- * the menu content is built natively from the configuration object.
23
- *
24
- * Named distinctly from the Fabric component view `TinyuiMenuView`.
25
- */
26
- struct TinyuiMenuSwiftUIView: View {
27
- @ObservedObject var props: TinyuiMenuProps
28
- weak var delegate: TinyuiMenuViewDelegate?
29
-
30
- var body: some View {
31
- Group {
32
- if let triggerView = props.triggerView {
33
- if props.hasPrimaryAction {
34
- Menu {
35
- menuContent
36
- } label: {
37
- RepresentableView(view: triggerView)
38
- } primaryAction: {
39
- delegate?.onPrimaryAction()
40
- }
41
- .disabled(props.disabled)
42
- } else {
43
- Menu {
44
- menuContent
45
- } label: {
46
- RepresentableView(view: triggerView)
47
- }
48
- .disabled(props.disabled)
49
- }
50
- } else {
51
- // No trigger mounted yet; render an empty view until the RN child
52
- // is attached to the provider.
53
- Color.clear
54
- }
55
- }
56
- }
57
-
58
- @ViewBuilder
59
- private var menuContent: some View {
60
- if props.title.isEmpty {
61
- elementsContent
62
- } else {
63
- Section {
64
- elementsContent
65
- } header: {
66
- Text(props.title)
67
- }
68
- }
69
- }
70
-
71
- @ViewBuilder
72
- private var elementsContent: some View {
73
- ForEach(buildElements(from: props.menuConfig)) { element in
74
- element.buildView(delegate: delegate)
75
- }
76
- }
77
-
78
- /// Converts the menu config into a list of SwiftUI element builders.
79
- private func buildElements(from config: [String: Any]) -> [TinyuiMenuElement] {
80
- guard let items = config["items"] as? [[String: Any]] else {
81
- return []
82
- }
83
- return items.map { TinyuiMenuElement(dictionary: $0) }
84
- }
85
- }
86
-
87
- /**
88
- * Lightweight representation of a single UIMenu element read from the config.
89
- * Used to build SwiftUI menu content without resolving all elements
90
- * eagerly. Returns `AnyView` to avoid recursive opaque return type errors.
91
- */
92
- struct TinyuiMenuElement: Identifiable {
93
- let id = UUID()
94
- private let dictionary: [String: Any]
95
-
96
- init(dictionary: [String: Any]) {
97
- self.dictionary = dictionary
98
- }
99
-
100
- func buildView(
101
- delegate: TinyuiMenuViewDelegate?,
102
- inheritedDestructive: Bool = false
103
- ) -> AnyView {
104
- if dictionary["hidden"] as? Bool ?? false {
105
- return AnyView(EmptyView())
106
- }
107
-
108
- switch dictionary["type"] as? String {
109
- case "action":
110
- let destructive = inheritedDestructive || (dictionary["destructive"] as? Bool ?? false)
111
- let keepOpen = dictionary["keepOpen"] as? Bool ?? false
112
- let id = dictionary["id"] as? String ?? ""
113
- let state = dictionary["state"] as? String ?? "off"
114
-
115
- // `on`/`off` use SwiftUI's native `Toggle` support inside `Menu`,
116
- // which renders the system checkmark. `mixed` has no native SwiftUI
117
- // equivalent, so it falls back to the minus indicator.
118
- if state == "on" || state == "off" {
119
- let isOn = Binding<Bool>(
120
- get: { state == "on" },
121
- set: { _ in delegate?.onItemPress(id: id) }
122
- )
123
- return AnyView(
124
- Toggle(isOn: isOn) {
125
- TinyuiMenuLabel(
126
- dictionary: dictionary,
127
- destructive: destructive,
128
- showsState: false
129
- )
130
- }
131
- .disabled(dictionary["disabled"] as? Bool ?? false)
132
- )
133
- }
134
-
135
- return AnyView(
136
- Button(role: destructive ? .destructive : nil) {
137
- delegate?.onItemPress(id: id)
138
- } label: {
139
- TinyuiMenuLabel(
140
- dictionary: dictionary,
141
- destructive: destructive,
142
- showsState: true
143
- )
144
- }
145
- .disabled(dictionary["disabled"] as? Bool ?? false)
146
- .menuActionDismissBehavior(keepOpen ? .disabled : .automatic)
147
- )
148
-
149
- case "submenu":
150
- let children = (dictionary["children"] as? [[String: Any]] ?? [])
151
- .map { TinyuiMenuElement(dictionary: $0) }
152
- let destructive = inheritedDestructive || (dictionary["destructive"] as? Bool ?? false)
153
- let disabled = dictionary["disabled"] as? Bool ?? false
154
-
155
- if dictionary["displayInline"] as? Bool ?? false {
156
- return AnyView(
157
- Section {
158
- ForEach(children) { child in
159
- child.buildView(
160
- delegate: delegate,
161
- inheritedDestructive: destructive
162
- )
163
- }
164
- } header: {
165
- TinyuiMenuLabel(
166
- dictionary: dictionary,
167
- destructive: destructive,
168
- showsState: false
169
- )
170
- }
171
- .disabled(disabled)
172
- )
173
- }
174
-
175
- return AnyView(
176
- Menu {
177
- ForEach(children) { child in
178
- child.buildView(
179
- delegate: delegate,
180
- inheritedDestructive: destructive
181
- )
182
- }
183
- } label: {
184
- TinyuiMenuLabel(
185
- dictionary: dictionary,
186
- destructive: destructive,
187
- showsState: false
188
- )
189
- }
190
- .disabled(disabled)
191
- )
192
-
193
- case "section":
194
- let children = (dictionary["children"] as? [[String: Any]] ?? [])
195
- .map { TinyuiMenuElement(dictionary: $0) }
196
- return AnyView(
197
- Section {
198
- ForEach(children) { child in
199
- child.buildView(
200
- delegate: delegate,
201
- inheritedDestructive: inheritedDestructive
202
- )
203
- }
204
- } header: {
205
- if let title = dictionary["title"] as? String, !title.isEmpty {
206
- Text(title)
207
- }
208
- }
209
- )
210
-
211
- case "separator":
212
- return AnyView(Divider())
213
-
214
- default:
215
- return AnyView(EmptyView())
216
- }
217
- }
218
- }
219
-
220
- /** Label content shared by actions and submenus. */
221
- private struct TinyuiMenuLabel: View {
222
- let dictionary: [String: Any]
223
- let destructive: Bool
224
- let showsState: Bool
225
-
226
- @ViewBuilder
227
- var body: some View {
228
- // Menu controls interpret the first text as the title and the second as
229
- // the subtitle. When an icon is present, `Label` replaces the first Text,
230
- // matching SwiftUI's native menu-item representation.
231
- if hasIcon {
232
- Label {
233
- title
234
- } icon: {
235
- menuIcon
236
- }
237
- } else {
238
- title
239
- }
240
-
241
- if let subtitle {
242
- Text(subtitle)
243
- }
244
-
245
- if showsState {
246
- stateIndicator
247
- }
248
- }
249
-
250
- private var subtitle: String? {
251
- (dictionary["subtitle"] as? String).flatMap {
252
- $0.isEmpty ? nil : $0
253
- }
254
- }
255
-
256
- private var hasIcon: Bool {
257
- if let icon = dictionary["icon"] as? String, !icon.isEmpty {
258
- return UIImage(named: icon) != nil
259
- }
260
- return !(dictionary["systemImage"] as? String ?? "").isEmpty
261
- }
262
-
263
- private var stateIndicator: some View {
264
- // Only `mixed` reaches here; `on`/`off` are handled natively by `Toggle`.
265
- // UIKit's menu system renders "mixed" state with a minus/dash mark.
266
- if dictionary["state"] as? String == "mixed" {
267
- return AnyView(
268
- Image(systemName: "minus")
269
- .font(.system(size: 13, weight: .semibold))
270
- )
271
- }
272
- return AnyView(EmptyView())
273
- }
274
-
275
- private var title: Text {
276
- let text = Text(dictionary["title"] as? String ?? "")
277
-
278
- if destructive {
279
- return text.foregroundColor(.red)
280
- } else if let color = TinyuiMenuColor.uiColor(from: dictionary["titleColor"]) {
281
- return text.foregroundColor(Color(uiColor: color))
282
- } else {
283
- return text
284
- }
285
- }
286
-
287
- @ViewBuilder
288
- private var menuIcon: some View {
289
- let iconColor = TinyuiMenuColor.uiColor(from: dictionary["iconColor"])
290
-
291
- if let icon = dictionary["icon"] as? String, !icon.isEmpty {
292
- if let image = UIImage(named: icon) {
293
- if let iconColor {
294
- Image(
295
- uiImage: image.withTintColor(
296
- iconColor,
297
- renderingMode: .alwaysOriginal
298
- )
299
- )
300
- } else {
301
- Image(uiImage: image.withRenderingMode(.alwaysTemplate))
302
- }
303
- }
304
- } else if let systemImage = dictionary["systemImage"] as? String,
305
- !systemImage.isEmpty {
306
- if let iconColor {
307
- Image(systemName: systemImage)
308
- .foregroundStyle(Color(uiColor: iconColor))
309
- } else {
310
- Image(systemName: systemImage)
311
- }
312
- }
313
- }
314
- }
315
-
316
- /** Converts React Native's processed ColorValue JSON into a dynamic UIColor. */
317
- private enum TinyuiMenuColor {
318
- static func uiColor(from value: Any?) -> UIColor? {
319
- value.flatMap(RCTConvert.uiColor)
320
- }
321
- }
322
-
323
- @objc public protocol TinyuiMenuViewDelegate {
324
- func onItemPress(id: String)
325
- func onPrimaryAction()
326
- }
@@ -1,88 +0,0 @@
1
- import SwiftUI
2
- import UIKit
3
-
4
- /**
5
- * Observable props shared between the Objective-C++ component view and the
6
- * SwiftUI `Popover` implementation. Mirror of `TinyuiPopoverProps` in codegen.
7
- */
8
- class TinyuiPopoverProps: ObservableObject {
9
- @Published var isPresented: Bool = false
10
- @Published var attachmentAnchor: String = "center"
11
- @Published var arrowEdge: String = "none"
12
- @Published var triggerView: UIView?
13
- @Published var contentView: UIView?
14
- }
15
-
16
- /**
17
- * SwiftUI `popover` backed by UIPopoverPresentationController, matching
18
- * expo-ui's PopoverView. The RN trigger is embedded as the anchor view and the
19
- * RN content view is presented inside the popover.
20
- *
21
- * Named distinctly from the Fabric component view `TinyuiPopoverView`.
22
- */
23
- struct TinyuiPopoverSwiftUIView: View {
24
- @ObservedObject var props: TinyuiPopoverProps
25
- weak var delegate: TinyuiPopoverViewDelegate?
26
-
27
- @State private var isPresented: Bool = false
28
-
29
- var body: some View {
30
- if let triggerView = props.triggerView {
31
- RepresentableView(view: triggerView)
32
- .popover(
33
- isPresented: $isPresented,
34
- attachmentAnchor: attachmentAnchorValue,
35
- arrowEdge: arrowEdgeValue
36
- ) {
37
- if #available(iOS 16.4, *) {
38
- popoverContent
39
- .presentationCompactAdaptation(.popover)
40
- } else {
41
- popoverContent
42
- }
43
- }
44
- .onChange(of: isPresented) { newValue in
45
- delegate?.onIsPresentedChange(isPresented: newValue)
46
- }
47
- .onChange(of: props.isPresented) { newValue in
48
- isPresented = newValue
49
- }
50
- .onAppear {
51
- isPresented = props.isPresented
52
- }
53
- } else {
54
- Color.clear
55
- }
56
- }
57
-
58
- private var attachmentAnchorValue: PopoverAttachmentAnchor {
59
- switch props.attachmentAnchor {
60
- case "top": return .point(.top)
61
- case "bottom": return .point(.bottom)
62
- case "leading": return .point(.leading)
63
- case "trailing": return .point(.trailing)
64
- default: return .point(.center)
65
- }
66
- }
67
-
68
- private var arrowEdgeValue: Edge? {
69
- switch props.arrowEdge {
70
- case "top": return .top
71
- case "bottom": return .bottom
72
- case "leading": return .leading
73
- case "trailing": return .trailing
74
- default: return nil
75
- }
76
- }
77
-
78
- @ViewBuilder
79
- private var popoverContent: some View {
80
- if let contentView = props.contentView {
81
- RepresentableView(view: contentView)
82
- }
83
- }
84
- }
85
-
86
- @objc public protocol TinyuiPopoverViewDelegate {
87
- func onIsPresentedChange(isPresented: Bool)
88
- }