react-native-platform-components 1.2.0 → 1.4.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 (242) hide show
  1. package/README.md +33 -31
  2. package/android/src/main/java/com/platformcomponents/PCButtonGroupView.kt +498 -0
  3. package/android/src/main/java/com/platformcomponents/PCButtonGroupViewManager.kt +191 -0
  4. package/android/src/main/java/com/platformcomponents/PCButtonSupport.kt +127 -0
  5. package/android/src/main/java/com/platformcomponents/PCButtonView.kt +345 -0
  6. package/android/src/main/java/com/platformcomponents/PCButtonViewManager.kt +126 -0
  7. package/android/src/main/java/com/platformcomponents/PCExpressive.kt +107 -0
  8. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarView.kt +115 -0
  9. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarViewManager.kt +65 -0
  10. package/android/src/main/java/com/platformcomponents/PCNativeThemeModule.kt +3 -0
  11. package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +13 -5
  12. package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +6 -1
  13. package/android/src/main/java/com/platformcomponents/PCTextFieldView.kt +1018 -0
  14. package/android/src/main/java/com/platformcomponents/PCTextFieldViewManager.kt +248 -0
  15. package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +13 -6
  16. package/android/src/main/jni/CMakeLists.txt +13 -1
  17. package/android/src/main/res/values/attrs.xml +13 -0
  18. package/android/src/main/res/values/styles.xml +183 -0
  19. package/ios/PCButton.h +10 -0
  20. package/ios/PCButton.mm +196 -0
  21. package/ios/PCButtonGroup.h +10 -0
  22. package/ios/PCButtonGroup.mm +256 -0
  23. package/ios/PCButtonGroupView.swift +254 -0
  24. package/ios/PCButtonSupport.swift +141 -0
  25. package/ios/PCButtonView.swift +155 -0
  26. package/ios/PCFloatingToolbar.h +10 -0
  27. package/ios/PCFloatingToolbar.mm +76 -0
  28. package/ios/PCFloatingToolbarView.swift +95 -0
  29. package/ios/PCNativeThemeModule.mm +13 -0
  30. package/ios/PCSelectionMenu.swift +9 -1
  31. package/ios/PCTextField.h +10 -0
  32. package/ios/PCTextField.mm +391 -0
  33. package/ios/PCTextFieldView.swift +1012 -0
  34. package/lib/commonjs/Button.js +77 -0
  35. package/lib/commonjs/Button.js.map +1 -0
  36. package/lib/commonjs/ButtonGroup.js +99 -0
  37. package/lib/commonjs/ButtonGroup.js.map +1 -0
  38. package/lib/commonjs/ButtonGroupNativeComponent.ts +121 -0
  39. package/lib/commonjs/ButtonNativeComponent.ts +76 -0
  40. package/lib/commonjs/ContextMenuNativeComponent.ts +5 -4
  41. package/lib/commonjs/DatePickerNativeComponent.ts +17 -11
  42. package/lib/commonjs/FloatingToolbar.js +69 -0
  43. package/lib/commonjs/FloatingToolbar.js.map +1 -0
  44. package/lib/commonjs/FloatingToolbarNativeComponent.ts +31 -0
  45. package/lib/commonjs/LiquidGlass.js +24 -4
  46. package/lib/commonjs/LiquidGlass.js.map +1 -1
  47. package/lib/commonjs/LiquidGlassNativeComponent.ts +6 -5
  48. package/lib/commonjs/NativePlatformComponentsTheme.js.map +1 -1
  49. package/lib/commonjs/SegmentedControl.js +11 -85
  50. package/lib/commonjs/SegmentedControl.js.map +1 -1
  51. package/lib/commonjs/SegmentedControlNativeComponent.ts +15 -11
  52. package/lib/commonjs/SelectionMenuNativeComponent.ts +6 -5
  53. package/lib/commonjs/TextField.js +195 -0
  54. package/lib/commonjs/TextField.js.map +1 -0
  55. package/lib/commonjs/TextFieldNativeComponent.ts +218 -0
  56. package/lib/commonjs/codegenTypes.js +6 -0
  57. package/lib/commonjs/codegenTypes.js.map +1 -0
  58. package/lib/commonjs/focusRegistry.js +78 -0
  59. package/lib/commonjs/focusRegistry.js.map +1 -0
  60. package/lib/commonjs/icons.js +92 -0
  61. package/lib/commonjs/icons.js.map +1 -0
  62. package/lib/commonjs/index.js +66 -0
  63. package/lib/commonjs/index.js.map +1 -1
  64. package/lib/commonjs/index.web.js +32 -2
  65. package/lib/commonjs/index.web.js.map +1 -1
  66. package/lib/commonjs/labelStyle.js +27 -0
  67. package/lib/commonjs/labelStyle.js.map +1 -0
  68. package/lib/commonjs/reactNativeInternals.d.js +2 -0
  69. package/lib/commonjs/reactNativeInternals.d.js.map +1 -0
  70. package/lib/module/Button.js +70 -0
  71. package/lib/module/Button.js.map +1 -0
  72. package/lib/module/ButtonGroup.js +92 -0
  73. package/lib/module/ButtonGroup.js.map +1 -0
  74. package/lib/module/ButtonGroupNativeComponent.ts +121 -0
  75. package/lib/module/ButtonNativeComponent.ts +76 -0
  76. package/lib/module/ContextMenuNativeComponent.ts +5 -4
  77. package/lib/module/DatePickerNativeComponent.ts +17 -11
  78. package/lib/module/FloatingToolbar.js +62 -0
  79. package/lib/module/FloatingToolbar.js.map +1 -0
  80. package/lib/module/FloatingToolbarNativeComponent.ts +31 -0
  81. package/lib/module/LiquidGlass.js +24 -4
  82. package/lib/module/LiquidGlass.js.map +1 -1
  83. package/lib/module/LiquidGlassNativeComponent.ts +6 -5
  84. package/lib/module/NativePlatformComponentsTheme.js.map +1 -1
  85. package/lib/module/SegmentedControl.js +12 -87
  86. package/lib/module/SegmentedControl.js.map +1 -1
  87. package/lib/module/SegmentedControlNativeComponent.ts +15 -11
  88. package/lib/module/SelectionMenuNativeComponent.ts +6 -5
  89. package/lib/module/TextField.js +189 -0
  90. package/lib/module/TextField.js.map +1 -0
  91. package/lib/module/TextFieldNativeComponent.ts +218 -0
  92. package/lib/module/codegenTypes.js +4 -0
  93. package/lib/module/codegenTypes.js.map +1 -0
  94. package/lib/module/focusRegistry.js +74 -0
  95. package/lib/module/focusRegistry.js.map +1 -0
  96. package/lib/module/icons.js +87 -0
  97. package/lib/module/icons.js.map +1 -0
  98. package/lib/module/index.js +6 -0
  99. package/lib/module/index.js.map +1 -1
  100. package/lib/module/index.web.js +28 -0
  101. package/lib/module/index.web.js.map +1 -1
  102. package/lib/module/labelStyle.js +23 -0
  103. package/lib/module/labelStyle.js.map +1 -0
  104. package/lib/module/reactNativeInternals.d.js +2 -0
  105. package/lib/module/reactNativeInternals.d.js.map +1 -0
  106. package/lib/typescript/commonjs/docs/sidebars.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/src/Button.d.ts +76 -0
  108. package/lib/typescript/commonjs/src/Button.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/src/ButtonGroup.d.ts +102 -0
  110. package/lib/typescript/commonjs/src/ButtonGroup.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts +93 -0
  112. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts +57 -0
  114. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts +5 -4
  116. package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/src/DatePickerNativeComponent.d.ts +12 -11
  118. package/lib/typescript/commonjs/src/DatePickerNativeComponent.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts +45 -0
  120. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts +24 -0
  122. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/src/LiquidGlass.d.ts +4 -3
  124. package/lib/typescript/commonjs/src/LiquidGlass.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts +6 -5
  126. package/lib/typescript/commonjs/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/src/NativePlatformComponentsTheme.d.ts +10 -2
  128. package/lib/typescript/commonjs/src/NativePlatformComponentsTheme.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/src/SegmentedControl.d.ts +17 -46
  130. package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +9 -6
  132. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/src/SelectionMenuNativeComponent.d.ts +6 -5
  134. package/lib/typescript/commonjs/src/SelectionMenuNativeComponent.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/src/TextField.d.ts +174 -0
  136. package/lib/typescript/commonjs/src/TextField.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts +162 -0
  138. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/src/codegenTypes.d.ts +12 -0
  140. package/lib/typescript/commonjs/src/codegenTypes.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/src/focusRegistry.d.ts +13 -0
  142. package/lib/typescript/commonjs/src/focusRegistry.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/src/icons.d.ts +52 -0
  144. package/lib/typescript/commonjs/src/icons.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/src/index.d.ts +6 -0
  146. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/src/index.web.d.ts +121 -0
  148. package/lib/typescript/commonjs/src/index.web.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/src/labelStyle.d.ts +20 -0
  150. package/lib/typescript/commonjs/src/labelStyle.d.ts.map +1 -0
  151. package/lib/typescript/commonjs/src/sharedTypes.d.ts +8 -3
  152. package/lib/typescript/commonjs/src/sharedTypes.d.ts.map +1 -1
  153. package/lib/typescript/module/docs/sidebars.d.ts.map +1 -1
  154. package/lib/typescript/module/src/Button.d.ts +76 -0
  155. package/lib/typescript/module/src/Button.d.ts.map +1 -0
  156. package/lib/typescript/module/src/ButtonGroup.d.ts +102 -0
  157. package/lib/typescript/module/src/ButtonGroup.d.ts.map +1 -0
  158. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts +93 -0
  159. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/module/src/ButtonNativeComponent.d.ts +57 -0
  161. package/lib/typescript/module/src/ButtonNativeComponent.d.ts.map +1 -0
  162. package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts +5 -4
  163. package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts.map +1 -1
  164. package/lib/typescript/module/src/DatePickerNativeComponent.d.ts +12 -11
  165. package/lib/typescript/module/src/DatePickerNativeComponent.d.ts.map +1 -1
  166. package/lib/typescript/module/src/FloatingToolbar.d.ts +45 -0
  167. package/lib/typescript/module/src/FloatingToolbar.d.ts.map +1 -0
  168. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts +24 -0
  169. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
  170. package/lib/typescript/module/src/LiquidGlass.d.ts +4 -3
  171. package/lib/typescript/module/src/LiquidGlass.d.ts.map +1 -1
  172. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts +6 -5
  173. package/lib/typescript/module/src/LiquidGlassNativeComponent.d.ts.map +1 -1
  174. package/lib/typescript/module/src/NativePlatformComponentsTheme.d.ts +10 -2
  175. package/lib/typescript/module/src/NativePlatformComponentsTheme.d.ts.map +1 -1
  176. package/lib/typescript/module/src/SegmentedControl.d.ts +17 -46
  177. package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -1
  178. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +9 -6
  179. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  180. package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts +6 -5
  181. package/lib/typescript/module/src/SelectionMenuNativeComponent.d.ts.map +1 -1
  182. package/lib/typescript/module/src/TextField.d.ts +174 -0
  183. package/lib/typescript/module/src/TextField.d.ts.map +1 -0
  184. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts +162 -0
  185. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts.map +1 -0
  186. package/lib/typescript/module/src/codegenTypes.d.ts +12 -0
  187. package/lib/typescript/module/src/codegenTypes.d.ts.map +1 -0
  188. package/lib/typescript/module/src/focusRegistry.d.ts +13 -0
  189. package/lib/typescript/module/src/focusRegistry.d.ts.map +1 -0
  190. package/lib/typescript/module/src/icons.d.ts +52 -0
  191. package/lib/typescript/module/src/icons.d.ts.map +1 -0
  192. package/lib/typescript/module/src/index.d.ts +6 -0
  193. package/lib/typescript/module/src/index.d.ts.map +1 -1
  194. package/lib/typescript/module/src/index.web.d.ts +121 -0
  195. package/lib/typescript/module/src/index.web.d.ts.map +1 -1
  196. package/lib/typescript/module/src/labelStyle.d.ts +20 -0
  197. package/lib/typescript/module/src/labelStyle.d.ts.map +1 -0
  198. package/lib/typescript/module/src/sharedTypes.d.ts +8 -3
  199. package/lib/typescript/module/src/sharedTypes.d.ts.map +1 -1
  200. package/package.json +28 -6
  201. package/react-native.config.js +3 -0
  202. package/shared/PCButtonComponentDescriptors-custom.h +22 -0
  203. package/shared/PCButtonGroupComponentDescriptors-custom.h +22 -0
  204. package/shared/PCButtonGroupShadowNode-custom.cpp +48 -0
  205. package/shared/PCButtonGroupShadowNode-custom.h +55 -0
  206. package/shared/PCButtonGroupState-custom.h +61 -0
  207. package/shared/PCButtonShadowNode-custom.cpp +48 -0
  208. package/shared/PCButtonShadowNode-custom.h +55 -0
  209. package/shared/PCButtonState-custom.h +61 -0
  210. package/shared/PCDatePickerState-custom.h +2 -2
  211. package/shared/PCSegmentedControlState-custom.h +2 -2
  212. package/shared/PCSelectionMenuState-custom.h +2 -2
  213. package/shared/PCTextFieldComponentDescriptors-custom.h +22 -0
  214. package/shared/PCTextFieldShadowNode-custom.cpp +47 -0
  215. package/shared/PCTextFieldShadowNode-custom.h +55 -0
  216. package/shared/PCTextFieldState-custom.h +61 -0
  217. package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +3 -0
  218. package/src/Button.tsx +144 -0
  219. package/src/ButtonGroup.tsx +233 -0
  220. package/src/ButtonGroupNativeComponent.ts +121 -0
  221. package/src/ButtonNativeComponent.ts +76 -0
  222. package/src/ContextMenuNativeComponent.ts +5 -4
  223. package/src/DatePickerNativeComponent.ts +17 -11
  224. package/src/FloatingToolbar.tsx +113 -0
  225. package/src/FloatingToolbarNativeComponent.ts +31 -0
  226. package/src/LiquidGlass.tsx +24 -5
  227. package/src/LiquidGlassNativeComponent.ts +6 -5
  228. package/src/NativePlatformComponentsTheme.ts +11 -2
  229. package/src/SegmentedControl.tsx +26 -118
  230. package/src/SegmentedControlNativeComponent.ts +15 -11
  231. package/src/SelectionMenuNativeComponent.ts +6 -5
  232. package/src/TextField.tsx +494 -0
  233. package/src/TextFieldNativeComponent.ts +218 -0
  234. package/src/codegenTypes.ts +40 -0
  235. package/src/focusRegistry.ts +89 -0
  236. package/src/icons.ts +116 -0
  237. package/src/index.tsx +6 -0
  238. package/src/index.web.tsx +194 -0
  239. package/src/labelStyle.ts +36 -0
  240. package/src/reactNativeInternals.d.ts +22 -0
  241. package/src/sharedTypes.ts +9 -3
  242. /package/{app.plugin.cjs → app.plugin.js} +0 -0
@@ -0,0 +1,254 @@
1
+ import UIKit
2
+
3
+ /// A row of native buttons (`UIButton` with configurations), with optional
4
+ /// single / multiple selection shown through the buttons' selected state.
5
+ @objcMembers
6
+ public final class PCButtonGroupView: UIView {
7
+ private struct Item {
8
+ let label: String
9
+ let value: String
10
+ let disabled: Bool
11
+ let icon: PCButtonSupport.Icon
12
+ let accessibilityLabel: String
13
+
14
+ /// What VoiceOver announces for the button.
15
+ var spokenLabel: String { accessibilityLabel.isEmpty ? label : accessibilityLabel }
16
+ }
17
+
18
+ // MARK: - Props (set from ObjC++)
19
+
20
+ /// ObjC++ sets this as an array of dictionaries; see `rebuild` for keys.
21
+ public var buttons: [Any] = [] { didSet { rebuild() } }
22
+
23
+ /// "filled" | "tonal" | "outlined" | "text" | "elevated"
24
+ public var variant: String = "outlined" {
25
+ didSet { if oldValue != variant { applyConfigurations() } }
26
+ }
27
+
28
+ /// "xsmall" | "small" | "medium" | "large" | "xlarge"
29
+ public var size: String = "small" {
30
+ didSet { if oldValue != size { applyConfigurations() } }
31
+ }
32
+
33
+ /// "" (UIKit default) | "round" | "square"
34
+ public var shape: String = "" {
35
+ didSet { if oldValue != shape { applyConfigurations() } }
36
+ }
37
+
38
+ /// Connected groups sit closer together (a hairline gap)
39
+ public var connected: Bool = false {
40
+ didSet { if oldValue != connected { applyLayout() } }
41
+ }
42
+
43
+ /// Gap between buttons in points; negative means the default
44
+ public var spacing: CGFloat = -1 {
45
+ didSet { if oldValue != spacing { applyLayout() } }
46
+ }
47
+
48
+ /// "none" | "single" | "multiple"
49
+ public var selection: String = "none" {
50
+ didSet { if oldValue != selection { applyConfigurations() } }
51
+ }
52
+
53
+ /// Controlled selection by value
54
+ public var selectedValues: [String] = [] {
55
+ didSet { if oldValue != selectedValues { applyConfigurations() } }
56
+ }
57
+
58
+ /// Whether at least one button must stay selected
59
+ public var selectionRequired: Bool = false
60
+
61
+ /// "enabled" | "disabled"
62
+ public var interactivity: String = "enabled" {
63
+ didSet { updateEnabled() }
64
+ }
65
+
66
+ /// Container color; nil keeps the configuration's color
67
+ public var containerColor: UIColor? {
68
+ didSet { applyConfigurations() }
69
+ }
70
+
71
+ /// Label and icon color; nil keeps the configuration's color
72
+ public var foregroundColor: UIColor? {
73
+ didSet { applyConfigurations() }
74
+ }
75
+
76
+ /// Label font; nil keeps the configuration's font
77
+ public var labelFont: UIFont? {
78
+ didSet { applyConfigurations() }
79
+ }
80
+
81
+ // MARK: - Events back to ObjC++
82
+
83
+ public var onPress: ((Int, String) -> Void)? // (index, value)
84
+ public var onSelectionChange: (([String]) -> Void)?
85
+
86
+ /// Called when content changed after layout (an icon finished loading),
87
+ /// so the Fabric measurement can be refreshed.
88
+ public var onNeedsRemeasure: (() -> Void)?
89
+
90
+ // MARK: - Internal
91
+
92
+ private let stack = UIStackView()
93
+ private var items: [Item] = []
94
+ private var uiButtons: [UIButton] = []
95
+ private var iconImages: [UIImage?] = []
96
+
97
+ /// Bumped on every rebuild so late image loads can't touch stale buttons.
98
+ private var rebuildGeneration = 0
99
+
100
+ // MARK: - Init
101
+
102
+ public override init(frame: CGRect) {
103
+ super.init(frame: frame)
104
+ setup()
105
+ }
106
+
107
+ public required init?(coder: NSCoder) {
108
+ super.init(coder: coder)
109
+ setup()
110
+ }
111
+
112
+ private func setup() {
113
+ stack.axis = .horizontal
114
+ stack.alignment = .fill
115
+ stack.translatesAutoresizingMaskIntoConstraints = false
116
+ addSubview(stack)
117
+
118
+ NSLayoutConstraint.activate([
119
+ stack.topAnchor.constraint(equalTo: topAnchor),
120
+ stack.bottomAnchor.constraint(equalTo: bottomAnchor),
121
+ stack.leadingAnchor.constraint(equalTo: leadingAnchor),
122
+ stack.trailingAnchor.constraint(equalTo: trailingAnchor),
123
+ ])
124
+
125
+ applyLayout()
126
+ }
127
+
128
+ // MARK: - Props handling
129
+
130
+ private func rebuild() {
131
+ items = buttons.compactMap { any in
132
+ guard let dict = any as? [String: Any] else { return nil }
133
+ return Item(
134
+ label: (dict["label"] as? String) ?? "",
135
+ value: (dict["value"] as? String) ?? "",
136
+ disabled: (dict["disabled"] as? String) == "disabled",
137
+ icon: PCButtonSupport.Icon(dictionary: dict),
138
+ accessibilityLabel: (dict["accessibilityLabel"] as? String) ?? ""
139
+ )
140
+ }
141
+
142
+ rebuildGeneration += 1
143
+ let generation = rebuildGeneration
144
+
145
+ uiButtons.forEach { $0.removeFromSuperview() }
146
+ uiButtons = []
147
+ iconImages = Array(repeating: nil, count: items.count)
148
+
149
+ for (index, item) in items.enumerated() {
150
+ let button = UIButton(type: .system)
151
+ button.tag = index
152
+ button.addTarget(self, action: #selector(tapped(_:)), for: .touchUpInside)
153
+ stack.addArrangedSubview(button)
154
+ uiButtons.append(button)
155
+
156
+ iconImages[index] = PCButtonSupport.image(for: item.icon) { [weak self] image in
157
+ guard let self, self.rebuildGeneration == generation, index < self.iconImages.count else { return }
158
+ self.iconImages[index] = image
159
+ self.applyConfigurations()
160
+ self.onNeedsRemeasure?()
161
+ }
162
+ }
163
+
164
+ applyConfigurations()
165
+ updateEnabled()
166
+ }
167
+
168
+ private func applyLayout() {
169
+ stack.spacing = spacing >= 0 ? spacing : (connected ? 2 : 8)
170
+ // Buttons size to their content; extra or missing width is shared in
171
+ // proportion when the group is stretched or squeezed.
172
+ stack.distribution = .fillProportionally
173
+ invalidateIntrinsicContentSize()
174
+ }
175
+
176
+ private func applyConfigurations() {
177
+ for (index, button) in uiButtons.enumerated() where index < items.count {
178
+ let item = items[index]
179
+ let selected = selection != "none" && selectedValues.contains(item.value)
180
+ button.isSelected = selected
181
+ button.configuration = PCButtonSupport.makeConfiguration(
182
+ variant: variant,
183
+ selected: selected,
184
+ size: size,
185
+ shape: shape,
186
+ label: item.label,
187
+ image: iconImages[index],
188
+ containerColor: containerColor,
189
+ foregroundColor: foregroundColor,
190
+ font: labelFont
191
+ )
192
+ button.accessibilityLabel = item.spokenLabel.isEmpty ? nil : item.spokenLabel
193
+ }
194
+ invalidateIntrinsicContentSize()
195
+ }
196
+
197
+ private func updateEnabled() {
198
+ let enabled = interactivity != "disabled"
199
+ alpha = enabled ? 1.0 : 0.5
200
+ for (index, button) in uiButtons.enumerated() where index < items.count {
201
+ button.isEnabled = enabled && !items[index].disabled
202
+ }
203
+ }
204
+
205
+ // MARK: - Selection
206
+
207
+ @objc private func tapped(_ sender: UIButton) {
208
+ let index = sender.tag
209
+ guard index >= 0, index < items.count else { return }
210
+ let item = items[index]
211
+ onPress?(index, item.value)
212
+
213
+ guard selection != "none", let next = nextSelection(tapping: item.value) else { return }
214
+ selectedValues = next
215
+ onSelectionChange?(next)
216
+ }
217
+
218
+ /// The selection after a tap, or nil when the tap changes nothing.
219
+ private func nextSelection(tapping value: String) -> [String]? {
220
+ let isSelected = selectedValues.contains(value)
221
+ if selection == "single" {
222
+ if isSelected {
223
+ return selectionRequired ? nil : []
224
+ }
225
+ return [value]
226
+ }
227
+
228
+ // multiple
229
+ if isSelected {
230
+ if selectionRequired && selectedValues.count == 1 { return nil }
231
+ return selectedValues.filter { $0 != value }
232
+ }
233
+ var chosen = Set(selectedValues)
234
+ chosen.insert(value)
235
+ // Report in button order
236
+ return items.map(\.value).filter { chosen.contains($0) }
237
+ }
238
+
239
+ // MARK: - Sizing
240
+
241
+ public override var intrinsicContentSize: CGSize {
242
+ stack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
243
+ }
244
+
245
+ public override func sizeThatFits(_ size: CGSize) -> CGSize {
246
+ stack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
247
+ }
248
+
249
+ /// Called by the measuring pipeline to get the size for Yoga layout.
250
+ @objc public func sizeForLayout(withConstrainedTo constrainedSize: CGSize) -> CGSize {
251
+ let fitted = stack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
252
+ return CGSize(width: ceil(fitted.width), height: ceil(fitted.height))
253
+ }
254
+ }
@@ -0,0 +1,141 @@
1
+ import UIKit
2
+
3
+ /// UIButton configuration and icon handling shared by PCButtonView and
4
+ /// PCButtonGroupView.
5
+ enum PCButtonSupport {
6
+ struct Icon: Equatable {
7
+ /// "", "sfSymbol", "drawable" (ignored on iOS) or "image"
8
+ var type: String = ""
9
+ var name: String = ""
10
+ var uri: String = ""
11
+ var scale: CGFloat = 1
12
+ var tinted: Bool = true
13
+
14
+ var isPresent: Bool { type == "sfSymbol" || type == "image" }
15
+
16
+ static let none = Icon()
17
+
18
+ init() {}
19
+
20
+ init(type: String, name: String, uri: String, scale: CGFloat, tinted: Bool) {
21
+ self.type = type
22
+ self.name = name
23
+ self.uri = uri
24
+ self.scale = scale > 0 ? scale : 1
25
+ self.tinted = tinted
26
+ }
27
+
28
+ /// Reads the flat icon fields (iconType, iconName, ...) bridged as a dictionary.
29
+ init(dictionary: [String: Any]) {
30
+ let scale = (dictionary["iconScale"] as? NSNumber).map { CGFloat(truncating: $0) } ?? 1
31
+ self.init(
32
+ type: (dictionary["iconType"] as? String) ?? "",
33
+ name: (dictionary["iconName"] as? String) ?? "",
34
+ uri: (dictionary["iconUri"] as? String) ?? "",
35
+ scale: scale,
36
+ tinted: (dictionary["iconTinted"] as? String) != "false"
37
+ )
38
+ }
39
+ }
40
+
41
+ /// The configuration for a variant. A selected button (single / multiple
42
+ /// selection in a group) uses the filled style, as UIKit's own toggle
43
+ /// buttons do.
44
+ ///
45
+ /// | Variant | Configuration |
46
+ /// | filled | .filled() |
47
+ /// | tonal | .tinted() |
48
+ /// | outlined | .bordered() |
49
+ /// | text | .plain() |
50
+ /// | elevated | .gray() |
51
+ static func configuration(variant: String, selected: Bool) -> UIButton.Configuration {
52
+ if selected { return .filled() }
53
+ switch variant {
54
+ case "tonal": return .tinted()
55
+ case "outlined": return .bordered()
56
+ case "text": return .plain()
57
+ case "elevated": return .gray()
58
+ default: return .filled()
59
+ }
60
+ }
61
+
62
+ /// Material's five sizes onto UIKit's four.
63
+ static func buttonSize(_ size: String) -> UIButton.Configuration.Size {
64
+ switch size {
65
+ case "xsmall": return .mini
66
+ case "medium": return .medium
67
+ case "large", "xlarge": return .large
68
+ default: return .small
69
+ }
70
+ }
71
+
72
+ /// `nil` keeps UIKit's dynamic corner style.
73
+ static func cornerStyle(_ shape: String) -> UIButton.Configuration.CornerStyle? {
74
+ switch shape {
75
+ case "round": return .capsule
76
+ case "square": return .large
77
+ default: return nil
78
+ }
79
+ }
80
+
81
+ static func makeConfiguration(
82
+ variant: String,
83
+ selected: Bool,
84
+ size: String,
85
+ shape: String,
86
+ label: String,
87
+ image: UIImage?,
88
+ containerColor: UIColor?,
89
+ foregroundColor: UIColor?,
90
+ font: UIFont?
91
+ ) -> UIButton.Configuration {
92
+ var config = configuration(variant: variant, selected: selected)
93
+ config.title = label.isEmpty ? nil : label
94
+ config.image = image
95
+ config.imagePadding = (image != nil && !label.isEmpty) ? 8 : 0
96
+ config.buttonSize = buttonSize(size)
97
+ config.titleLineBreakMode = .byTruncatingTail
98
+ if let corner = cornerStyle(shape) {
99
+ config.cornerStyle = corner
100
+ }
101
+ if let containerColor {
102
+ config.baseBackgroundColor = containerColor
103
+ }
104
+ if let foregroundColor {
105
+ config.baseForegroundColor = foregroundColor
106
+ }
107
+ if let font {
108
+ config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
109
+ var outgoing = incoming
110
+ outgoing.font = font
111
+ return outgoing
112
+ }
113
+ }
114
+ return config
115
+ }
116
+
117
+ /// Resolves the icon's image. SF Symbols resolve synchronously; images
118
+ /// that are still loading return nil and call `completion` on the main
119
+ /// thread once they arrive.
120
+ static func image(for icon: Icon, completion: @escaping (UIImage?) -> Void) -> UIImage? {
121
+ switch icon.type {
122
+ case "sfSymbol":
123
+ return UIImage(systemName: icon.name).map { decorate($0, tinted: icon.tinted) }
124
+
125
+ case "image":
126
+ let cached = PCImageLoader.shared.image(uri: icon.uri, scale: icon.scale) { image in
127
+ completion(image.map { decorate($0, tinted: icon.tinted) })
128
+ }
129
+ return cached.map { decorate($0, tinted: icon.tinted) }
130
+
131
+ default:
132
+ return nil
133
+ }
134
+ }
135
+
136
+ /// Template images take the button's foreground color; untinted images
137
+ /// keep their own colors.
138
+ static func decorate(_ image: UIImage, tinted: Bool) -> UIImage {
139
+ image.withRenderingMode(tinted ? .alwaysTemplate : .alwaysOriginal)
140
+ }
141
+ }
@@ -0,0 +1,155 @@
1
+ import UIKit
2
+
3
+ /// A native button: `UIButton` with a `UIButton.Configuration` chosen by the
4
+ /// variant, so it takes the system look of the iOS version it runs on.
5
+ @objcMembers
6
+ public final class PCButtonView: UIView {
7
+ // MARK: - Props (set from ObjC++)
8
+
9
+ public var label: String = "" {
10
+ didSet { if oldValue != label { applyConfiguration() } }
11
+ }
12
+
13
+ /// "filled" | "tonal" | "outlined" | "text" | "elevated"
14
+ public var variant: String = "filled" {
15
+ didSet { if oldValue != variant { applyConfiguration() } }
16
+ }
17
+
18
+ /// "xsmall" | "small" | "medium" | "large" | "xlarge"
19
+ public var size: String = "small" {
20
+ didSet { if oldValue != size { applyConfiguration() } }
21
+ }
22
+
23
+ /// "" (UIKit default) | "round" | "square"
24
+ public var shape: String = "" {
25
+ didSet { if oldValue != shape { applyConfiguration() } }
26
+ }
27
+
28
+ /// "enabled" | "disabled"
29
+ public var interactivity: String = "enabled" {
30
+ didSet { button.isEnabled = interactivity != "disabled" }
31
+ }
32
+
33
+ /// Container color; nil keeps the configuration's color
34
+ public var containerColor: UIColor? {
35
+ didSet { applyConfiguration() }
36
+ }
37
+
38
+ /// Label and icon color; nil keeps the configuration's color
39
+ public var foregroundColor: UIColor? {
40
+ didSet { applyConfiguration() }
41
+ }
42
+
43
+ /// Label font; nil keeps the configuration's font
44
+ public var labelFont: UIFont? {
45
+ didSet { applyConfiguration() }
46
+ }
47
+
48
+ /// Screen-reader label; empty uses the label
49
+ public var spokenLabel: String = "" {
50
+ didSet { button.accessibilityLabel = spokenLabel.isEmpty ? nil : spokenLabel }
51
+ }
52
+
53
+ // MARK: - Events back to ObjC++
54
+
55
+ public var onPress: (() -> Void)?
56
+
57
+ /// Called when content changed after layout (an icon finished loading),
58
+ /// so the Fabric measurement can be refreshed.
59
+ public var onNeedsRemeasure: (() -> Void)?
60
+
61
+ // MARK: - Internal
62
+
63
+ private let button = UIButton(type: .system)
64
+ private var icon = PCButtonSupport.Icon.none
65
+ private var iconImage: UIImage?
66
+
67
+ /// Bumped on every icon change so late image loads can't apply a stale icon.
68
+ private var iconGeneration = 0
69
+
70
+ // MARK: - Init
71
+
72
+ public override init(frame: CGRect) {
73
+ super.init(frame: frame)
74
+ setup()
75
+ }
76
+
77
+ public required init?(coder: NSCoder) {
78
+ super.init(coder: coder)
79
+ setup()
80
+ }
81
+
82
+ private func setup() {
83
+ button.translatesAutoresizingMaskIntoConstraints = false
84
+ addSubview(button)
85
+
86
+ NSLayoutConstraint.activate([
87
+ button.topAnchor.constraint(equalTo: topAnchor),
88
+ button.bottomAnchor.constraint(equalTo: bottomAnchor),
89
+ button.leadingAnchor.constraint(equalTo: leadingAnchor),
90
+ button.trailingAnchor.constraint(equalTo: trailingAnchor),
91
+ ])
92
+
93
+ button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
94
+ applyConfiguration()
95
+ }
96
+
97
+ @objc private func pressed() {
98
+ onPress?()
99
+ }
100
+
101
+ // MARK: - Props handling
102
+
103
+ /// Sets the icon from the flat spec fields. Images that are still loading
104
+ /// apply once they arrive.
105
+ public func setIcon(type: String, name: String, uri: String, scale: CGFloat, tinted: Bool) {
106
+ let next = PCButtonSupport.Icon(type: type, name: name, uri: uri, scale: scale, tinted: tinted)
107
+ guard next != icon else { return }
108
+ icon = next
109
+ iconGeneration += 1
110
+ let generation = iconGeneration
111
+
112
+ iconImage = PCButtonSupport.image(for: next) { [weak self] image in
113
+ guard let self, self.iconGeneration == generation else { return }
114
+ self.iconImage = image
115
+ self.applyConfiguration()
116
+ self.onNeedsRemeasure?()
117
+ }
118
+ applyConfiguration()
119
+ }
120
+
121
+ private func applyConfiguration() {
122
+ button.configuration = PCButtonSupport.makeConfiguration(
123
+ variant: variant,
124
+ selected: false,
125
+ size: size,
126
+ shape: shape,
127
+ label: label,
128
+ image: iconImage,
129
+ containerColor: containerColor,
130
+ foregroundColor: foregroundColor,
131
+ font: labelFont
132
+ )
133
+ invalidateIntrinsicContentSize()
134
+ }
135
+
136
+ // MARK: - Sizing
137
+
138
+ public override var intrinsicContentSize: CGSize {
139
+ button.intrinsicContentSize
140
+ }
141
+
142
+ public override func sizeThatFits(_ size: CGSize) -> CGSize {
143
+ button.sizeThatFits(size)
144
+ }
145
+
146
+ /// Called by the measuring pipeline to get the size for Yoga layout: the
147
+ /// button's natural size, or its height at the given width.
148
+ @objc public func sizeForLayout(withConstrainedTo constrainedSize: CGSize) -> CGSize {
149
+ let fitted = button.sizeThatFits(
150
+ CGSize(width: constrainedSize.width > 0 ? constrainedSize.width : .greatestFiniteMagnitude,
151
+ height: .greatestFiniteMagnitude)
152
+ )
153
+ return CGSize(width: ceil(fitted.width), height: ceil(fitted.height))
154
+ }
155
+ }
@@ -0,0 +1,10 @@
1
+ // PCFloatingToolbar.h
2
+
3
+ #import <React/RCTViewComponentView.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface PCFloatingToolbar : RCTViewComponentView
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,76 @@
1
+ // PCFloatingToolbar.mm
2
+ //
3
+ // A floating toolbar: a capsule of Liquid Glass on iOS 26 (a blur material
4
+ // with a soft shadow on earlier versions) hosting the React children.
5
+
6
+ #import "PCFloatingToolbar.h"
7
+
8
+ #import <React/RCTComponentViewFactory.h>
9
+ #import <React/RCTConversions.h>
10
+ #import <React/RCTFabricComponentsPlugins.h>
11
+
12
+ #import <react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h>
13
+ #import <react/renderer/components/PlatformComponentsViewSpec/EventEmitters.h>
14
+ #import <react/renderer/components/PlatformComponentsViewSpec/Props.h>
15
+
16
+ #if __has_include(<PlatformComponents/PlatformComponents-Swift.h>)
17
+ #import <PlatformComponents/PlatformComponents-Swift.h>
18
+ #else
19
+ #import "PlatformComponents-Swift.h"
20
+ #endif
21
+
22
+ using namespace facebook::react;
23
+
24
+ @implementation PCFloatingToolbar {
25
+ PCFloatingToolbarView *_view;
26
+ }
27
+
28
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
29
+ return concreteComponentDescriptorProvider<PCFloatingToolbarComponentDescriptor>();
30
+ }
31
+
32
+ - (instancetype)initWithFrame:(CGRect)frame {
33
+ if (self = [super initWithFrame:frame]) {
34
+ _view = [[PCFloatingToolbarView alloc] initWithFrame:self.bounds];
35
+ _view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
36
+ [self addSubview:_view];
37
+ }
38
+ return self;
39
+ }
40
+
41
+ // Mount children into the material's contentView
42
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
43
+ [_view.contentView insertSubview:childComponentView atIndex:index];
44
+ }
45
+
46
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
47
+ [childComponentView removeFromSuperview];
48
+ }
49
+
50
+ - (void)updateProps:(Props::Shared const &)props
51
+ oldProps:(Props::Shared const &)oldProps {
52
+ const auto &newProps = *std::static_pointer_cast<const PCFloatingToolbarProps>(props);
53
+ const auto prevProps = std::static_pointer_cast<const PCFloatingToolbarProps>(oldProps);
54
+
55
+ // color -> material tint (SharedColor, already processed by React Native)
56
+ if (!prevProps || newProps.color != prevProps->color) {
57
+ _view.tintUIColor = RCTUIColorFromSharedColor(newProps.color);
58
+ }
59
+
60
+ // ios.effect: "regular" | "clear"
61
+ const auto &newIos = newProps.ios;
62
+ const auto &oldIos = prevProps ? prevProps->ios : PCFloatingToolbarIosStruct{};
63
+ if (!prevProps || newIos.effect != oldIos.effect) {
64
+ _view.effectStyle = newIos.effect == "clear" ? @"clear" : @"regular";
65
+ }
66
+
67
+ // android.variant: Android only
68
+
69
+ [super updateProps:props oldProps:oldProps];
70
+ }
71
+
72
+ @end
73
+
74
+ Class<RCTComponentViewProtocol> PCFloatingToolbarCls(void) {
75
+ return PCFloatingToolbar.class;
76
+ }