react-native-platform-components 0.6.1 → 0.7.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 +153 -44
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +241 -0
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +105 -0
- package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +1 -0
- package/ios/PCDatePickerView.swift +16 -13
- package/ios/PCSegmentedControl.h +10 -0
- package/ios/PCSegmentedControl.mm +194 -0
- package/ios/PCSegmentedControl.swift +200 -0
- package/lib/commonjs/SegmentedControl.js +93 -0
- package/lib/commonjs/SegmentedControl.js.map +1 -0
- package/lib/commonjs/SegmentedControlNativeComponent.ts +79 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/SegmentedControl.js +87 -0
- package/lib/module/SegmentedControl.js.map +1 -0
- package/lib/module/SegmentedControlNativeComponent.ts +79 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts +62 -0
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +63 -0
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/SegmentedControl.d.ts +62 -0
- package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +63 -0
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +1 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +4 -3
- package/react-native.config.js +1 -0
- package/shared/PCSegmentedControlComponentDescriptors-custom.h +22 -0
- package/shared/PCSegmentedControlShadowNode-custom.cpp +54 -0
- package/shared/PCSegmentedControlShadowNode-custom.h +56 -0
- package/shared/PCSegmentedControlState-custom.h +62 -0
- package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +1 -0
- package/src/SegmentedControl.tsx +178 -0
- package/src/SegmentedControlNativeComponent.ts +79 -0
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// SegmentedControl.tsx
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewProps,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
|
|
11
|
+
import NativeSegmentedControl, {
|
|
12
|
+
type SegmentedControlSelectEvent,
|
|
13
|
+
} from './SegmentedControlNativeComponent';
|
|
14
|
+
|
|
15
|
+
// Android: Minimum height to ensure visibility.
|
|
16
|
+
// Fabric's shadow node measurement isn't being called on initial render,
|
|
17
|
+
// so we apply a minHeight that matches Material design touch target guidelines.
|
|
18
|
+
const ANDROID_MIN_HEIGHT = 48;
|
|
19
|
+
|
|
20
|
+
export interface SegmentedControlSegmentProps {
|
|
21
|
+
/** Display label for the segment */
|
|
22
|
+
label: string;
|
|
23
|
+
|
|
24
|
+
/** Unique value identifier for the segment */
|
|
25
|
+
value: string;
|
|
26
|
+
|
|
27
|
+
/** Whether this specific segment is disabled */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
|
|
30
|
+
/** Optional SF Symbol name (iOS) or drawable resource name (Android) */
|
|
31
|
+
icon?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SegmentedControlProps extends ViewProps {
|
|
35
|
+
/** Array of segments to display */
|
|
36
|
+
segments: readonly SegmentedControlSegmentProps[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Currently selected segment value.
|
|
40
|
+
* Use `null` for no selection.
|
|
41
|
+
*/
|
|
42
|
+
selectedValue: string | null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Called when the user selects a segment.
|
|
46
|
+
* @param value - The selected segment's value
|
|
47
|
+
* @param index - The selected segment's index
|
|
48
|
+
*/
|
|
49
|
+
onSelect?: (value: string, index: number) => void;
|
|
50
|
+
|
|
51
|
+
/** Whether the entire control is disabled */
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* iOS-specific configuration
|
|
56
|
+
*/
|
|
57
|
+
ios?: {
|
|
58
|
+
/**
|
|
59
|
+
* Momentary mode: segment springs back after touch (no persistent selection)
|
|
60
|
+
* Default: false
|
|
61
|
+
*/
|
|
62
|
+
momentary?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether segment widths are proportional to content
|
|
66
|
+
* Default: false (equal widths)
|
|
67
|
+
*/
|
|
68
|
+
apportionsSegmentWidthsByContent?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Selected segment tint color (hex string, e.g., "#007AFF")
|
|
72
|
+
*/
|
|
73
|
+
selectedSegmentTintColor?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Android-specific configuration
|
|
78
|
+
*/
|
|
79
|
+
android?: {
|
|
80
|
+
/**
|
|
81
|
+
* Whether one segment must always be selected.
|
|
82
|
+
* Default: false
|
|
83
|
+
*/
|
|
84
|
+
selectionRequired?: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Test identifier */
|
|
88
|
+
testID?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeSelectedValue(selected: string | null): string {
|
|
92
|
+
return selected ?? '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function SegmentedControl(
|
|
96
|
+
props: SegmentedControlProps
|
|
97
|
+
): React.ReactElement {
|
|
98
|
+
const {
|
|
99
|
+
style,
|
|
100
|
+
segments,
|
|
101
|
+
selectedValue,
|
|
102
|
+
disabled,
|
|
103
|
+
onSelect,
|
|
104
|
+
ios,
|
|
105
|
+
android,
|
|
106
|
+
...viewProps
|
|
107
|
+
} = props;
|
|
108
|
+
|
|
109
|
+
// Normalize segments for native
|
|
110
|
+
const nativeSegments = useMemo(() => {
|
|
111
|
+
return segments.map((seg) => ({
|
|
112
|
+
label: seg.label,
|
|
113
|
+
value: seg.value,
|
|
114
|
+
disabled: seg.disabled ? 'disabled' : 'enabled',
|
|
115
|
+
icon: seg.icon ?? '',
|
|
116
|
+
}));
|
|
117
|
+
}, [segments]);
|
|
118
|
+
|
|
119
|
+
const selectedData = useMemo(
|
|
120
|
+
() => normalizeSelectedValue(selectedValue),
|
|
121
|
+
[selectedValue]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const handleSelect = useCallback(
|
|
125
|
+
(e: { nativeEvent: SegmentedControlSelectEvent }) => {
|
|
126
|
+
const { index, value } = e.nativeEvent;
|
|
127
|
+
onSelect?.(value, index);
|
|
128
|
+
},
|
|
129
|
+
[onSelect]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Normalize iOS props to native string format
|
|
133
|
+
const nativeIos = useMemo(() => {
|
|
134
|
+
if (!ios) return undefined;
|
|
135
|
+
return {
|
|
136
|
+
momentary: ios.momentary ? 'true' : 'false',
|
|
137
|
+
apportionsSegmentWidthsByContent: ios.apportionsSegmentWidthsByContent
|
|
138
|
+
? 'true'
|
|
139
|
+
: 'false',
|
|
140
|
+
selectedSegmentTintColor: ios.selectedSegmentTintColor ?? '',
|
|
141
|
+
};
|
|
142
|
+
}, [ios]);
|
|
143
|
+
|
|
144
|
+
// Normalize Android props
|
|
145
|
+
const nativeAndroid = useMemo(() => {
|
|
146
|
+
if (!android) return undefined;
|
|
147
|
+
return {
|
|
148
|
+
selectionRequired: android.selectionRequired ? 'true' : 'false',
|
|
149
|
+
};
|
|
150
|
+
}, [android]);
|
|
151
|
+
|
|
152
|
+
// Merge user style with Android minHeight default
|
|
153
|
+
const mergedStyle = useMemo((): StyleProp<ViewStyle> => {
|
|
154
|
+
if (Platform.OS === 'android') {
|
|
155
|
+
return [styles.androidDefault, style];
|
|
156
|
+
}
|
|
157
|
+
return style;
|
|
158
|
+
}, [style]);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<NativeSegmentedControl
|
|
162
|
+
style={mergedStyle}
|
|
163
|
+
segments={nativeSegments}
|
|
164
|
+
selectedValue={selectedData}
|
|
165
|
+
interactivity={disabled ? 'disabled' : 'enabled'}
|
|
166
|
+
onSelect={onSelect ? handleSelect : undefined}
|
|
167
|
+
ios={nativeIos}
|
|
168
|
+
android={nativeAndroid}
|
|
169
|
+
{...viewProps}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const styles = StyleSheet.create({
|
|
175
|
+
androidDefault: {
|
|
176
|
+
minHeight: ANDROID_MIN_HEIGHT,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SegmentedControlNativeComponent.ts
|
|
2
|
+
import type { CodegenTypes, ViewProps } from 'react-native';
|
|
3
|
+
import { codegenNativeComponent } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A single segment in the control.
|
|
7
|
+
*/
|
|
8
|
+
export type SegmentedControlSegment = Readonly<{
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
disabled: string; // 'enabled' | 'disabled'
|
|
12
|
+
icon: string; // SF Symbol (iOS) or drawable name (Android), empty = none
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Event emitted when the user selects a segment.
|
|
17
|
+
*/
|
|
18
|
+
export type SegmentedControlSelectEvent = Readonly<{
|
|
19
|
+
/** Selected segment index */
|
|
20
|
+
index: CodegenTypes.Int32;
|
|
21
|
+
|
|
22
|
+
/** Selected segment value */
|
|
23
|
+
value: string;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
/** Interactivity state (no booleans). */
|
|
27
|
+
export type SegmentedControlInteractivity = 'enabled' | 'disabled';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* iOS-specific configuration.
|
|
31
|
+
*/
|
|
32
|
+
export type IOSProps = Readonly<{
|
|
33
|
+
/** Momentary mode: segment springs back after touch */
|
|
34
|
+
momentary?: string; // 'true' | 'false'
|
|
35
|
+
|
|
36
|
+
/** Whether segment widths are proportional to content */
|
|
37
|
+
apportionsSegmentWidthsByContent?: string; // 'true' | 'false'
|
|
38
|
+
|
|
39
|
+
/** Selected segment tint color (hex string) */
|
|
40
|
+
selectedSegmentTintColor?: string;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Android-specific configuration.
|
|
45
|
+
*/
|
|
46
|
+
export type AndroidProps = Readonly<{
|
|
47
|
+
/** Whether one segment must always be selected */
|
|
48
|
+
selectionRequired?: string; // 'true' | 'false'
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
export interface SegmentedControlProps extends ViewProps {
|
|
52
|
+
/**
|
|
53
|
+
* Segments to display.
|
|
54
|
+
*/
|
|
55
|
+
segments: ReadonlyArray<SegmentedControlSegment>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Controlled selection by `value`.
|
|
59
|
+
* Empty string means "no selection".
|
|
60
|
+
*/
|
|
61
|
+
selectedValue?: CodegenTypes.WithDefault<string, ''>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Enabled / disabled state.
|
|
65
|
+
*/
|
|
66
|
+
interactivity?: string; // SegmentedControlInteractivity
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fired when the user selects a segment.
|
|
70
|
+
*/
|
|
71
|
+
onSelect?: CodegenTypes.BubblingEventHandler<SegmentedControlSelectEvent>;
|
|
72
|
+
|
|
73
|
+
ios?: IOSProps;
|
|
74
|
+
android?: AndroidProps;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default codegenNativeComponent<SegmentedControlProps>(
|
|
78
|
+
'PCSegmentedControl'
|
|
79
|
+
);
|