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.
- package/README.md +351 -22
- 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/Menu/TinyuiMenuBuilder.swift +238 -0
- package/ios/Menu/TinyuiMenuProvider.swift +51 -85
- package/ios/Menu/TinyuiMenuView.mm +12 -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/Menu/MenuNativeComponent.ts +1 -3
- 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/Menu/MenuNativeComponent.d.ts +2 -3
- 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 +1 -5
- 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 +9 -1
- 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/Menu/MenuNativeComponent.ts +1 -3
- package/src/Menu/index.tsx +6 -6
- package/src/Menu/options.ts +2 -20
- 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 +30 -0
- package/src/utils/getNativeTinyui.ts +8 -1
- package/ios/Core/TinyuiHostingHelpers.swift +0 -74
- package/ios/Menu/TinyuiMenuViewSwift.swift +0 -326
- 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 =
|
|
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
|
-
}
|