@studio-piot/native-ui 0.2.2 → 0.2.3
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 +2 -1
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -0
- package/dist/components/SegmentedControl/SegmentedControl.js +181 -0
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -0
- package/dist/components/SegmentedControl/index.d.ts +4 -0
- package/dist/components/SegmentedControl/index.d.ts.map +1 -0
- package/dist/components/SegmentedControl/index.js +8 -0
- package/dist/components/SegmentedControl/index.js.map +1 -0
- package/dist/components/SegmentedControl/segmentIndicatorFrame.d.ts +16 -0
- package/dist/components/SegmentedControl/segmentIndicatorFrame.d.ts.map +1 -0
- package/dist/components/SegmentedControl/segmentIndicatorFrame.js +27 -0
- package/dist/components/SegmentedControl/segmentIndicatorFrame.js.map +1 -0
- package/dist/components/SegmentedControl/segmentThemeRoles.d.ts +10 -0
- package/dist/components/SegmentedControl/segmentThemeRoles.d.ts.map +1 -0
- package/dist/components/SegmentedControl/segmentThemeRoles.js +12 -0
- package/dist/components/SegmentedControl/segmentThemeRoles.js.map +1 -0
- package/dist/components/SegmentedControl/types.d.ts +23 -0
- package/dist/components/SegmentedControl/types.d.ts.map +1 -0
- package/dist/components/SegmentedControl/types.js +3 -0
- package/dist/components/SegmentedControl/types.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -1
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/docs/components/segmented-control.md +95 -0
- package/package.json +1 -1
- package/src/components/SegmentedControl/SegmentedControl.tsx +234 -0
- package/src/components/SegmentedControl/index.ts +6 -0
- package/src/components/SegmentedControl/segmentIndicatorFrame.test.ts +39 -0
- package/src/components/SegmentedControl/segmentIndicatorFrame.ts +31 -0
- package/src/components/SegmentedControl/segmentThemeRoles.test.ts +17 -0
- package/src/components/SegmentedControl/segmentThemeRoles.ts +10 -0
- package/src/components/SegmentedControl/types.ts +24 -0
- package/src/components/index.ts +7 -0
- package/src/index.ts +8 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ The package is the native counterpart of `@studio-piot/ui`. Apps own their palet
|
|
|
10
10
|
npm install @studio-piot/native-ui
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`Select` peers `@gorhom/bottom-sheet`, `react-native-reanimated`, `react-native-gesture-handler`, and `react-native-screens` (iOS `FullWindowOverlay` so the sheet appears above React Navigation modals). `Slider` peers `@react-native-community/slider`.
|
|
13
|
+
`Select` peers `@gorhom/bottom-sheet`, `react-native-reanimated`, `react-native-gesture-handler`, and `react-native-screens` (iOS `FullWindowOverlay` so the sheet appears above React Navigation modals). `SegmentedControl` uses that same `react-native-reanimated` peer to slide the selection fill. `Slider` peers `@react-native-community/slider`.
|
|
14
14
|
|
|
15
15
|
## Docs
|
|
16
16
|
|
|
@@ -20,6 +20,7 @@ npm install @studio-piot/native-ui
|
|
|
20
20
|
- [Form usage](./docs/components/form.md)
|
|
21
21
|
- [RadioField usage](./docs/components/radio-field.md)
|
|
22
22
|
- [Select usage](./docs/components/select.md)
|
|
23
|
+
- [SegmentedControl usage](./docs/components/segmented-control.md)
|
|
23
24
|
- [Slider usage](./docs/components/slider.md)
|
|
24
25
|
- [Design: theme provider and Button](./docs/superpowers/specs/2026-09-24-theme-provider-button-design.md)
|
|
25
26
|
- [Design: LinkBlocks](./docs/superpowers/specs/2026-09-24-link-blocks-design.md)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SegmentedControlProps } from "./types";
|
|
2
|
+
export declare function SegmentedControl<T extends string = string>({ value, onChange, options, label, radius, disabled, backgroundColor: backgroundColorProp, borderColor: borderColorProp, selectedBackgroundColor: selectedBackgroundColorProp, labelColor: labelColorProp, selectedLabelColor: selectedLabelColorProp, titleColor: titleColorProp, style, }: SegmentedControlProps<T>): import("react").JSX.Element | null;
|
|
3
|
+
//# sourceMappingURL=SegmentedControl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAIrD,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EAC1D,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACL,MAAa,EACb,QAAgB,EAChB,eAAe,EAAE,mBAAmB,EACpC,WAAW,EAAE,eAAe,EAC5B,uBAAuB,EAAE,2BAA2B,EACpD,UAAU,EAAE,cAAc,EAC1B,kBAAkB,EAAE,sBAAsB,EAC1C,UAAU,EAAE,cAAc,EAC1B,KAAK,GACN,EAAE,qBAAqB,CAAC,CAAC,CAAC,sCAuJ1B"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SegmentedControl = SegmentedControl;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const react_1 = require("react");
|
|
39
|
+
const react_native_1 = require("react-native");
|
|
40
|
+
const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
|
|
41
|
+
const buttonRadius_1 = require("../Button/buttonRadius");
|
|
42
|
+
const useResolvedFieldColor_1 = require("../Field/useResolvedFieldColor");
|
|
43
|
+
const segmentIndicatorFrame_1 = require("./segmentIndicatorFrame");
|
|
44
|
+
const segmentThemeRoles_1 = require("./segmentThemeRoles");
|
|
45
|
+
const SLIDE_MS = 220;
|
|
46
|
+
function SegmentedControl({ value, onChange, options, label, radius = "lg", disabled = false, backgroundColor: backgroundColorProp, borderColor: borderColorProp, selectedBackgroundColor: selectedBackgroundColorProp, labelColor: labelColorProp, selectedLabelColor: selectedLabelColorProp, titleColor: titleColorProp, style, }) {
|
|
47
|
+
const backgroundColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(backgroundColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.backgroundColor);
|
|
48
|
+
const borderColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(borderColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.borderColor);
|
|
49
|
+
const selectedBackgroundColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(selectedBackgroundColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.selectedBackgroundColor);
|
|
50
|
+
const labelColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(labelColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.labelColor);
|
|
51
|
+
const selectedLabelColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(selectedLabelColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.selectedLabelColor);
|
|
52
|
+
const titleColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(titleColorProp, segmentThemeRoles_1.SEGMENTED_THEME_ROLES.titleColor);
|
|
53
|
+
const index = (0, segmentIndicatorFrame_1.resolveSegmentIndex)(value, options);
|
|
54
|
+
const trackWidth = (0, react_1.useRef)(0);
|
|
55
|
+
const laidOut = (0, react_1.useRef)(false);
|
|
56
|
+
const indexSV = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
57
|
+
const widthSV = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
58
|
+
const opacitySV = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
59
|
+
const borderRadius = (0, buttonRadius_1.buttonRadiusValue)(radius);
|
|
60
|
+
const pillRadius = Math.max(0, borderRadius - segmentIndicatorFrame_1.SEGMENT_INSET);
|
|
61
|
+
const applyTrackWidth = (nextWidth, count) => {
|
|
62
|
+
trackWidth.current = nextWidth;
|
|
63
|
+
const frame = (0, segmentIndicatorFrame_1.segmentIndicatorFrame)(0, count, nextWidth, segmentIndicatorFrame_1.SEGMENT_INSET);
|
|
64
|
+
widthSV.value = frame?.width ?? 0;
|
|
65
|
+
};
|
|
66
|
+
(0, react_1.useEffect)(() => {
|
|
67
|
+
if (trackWidth.current > 0) {
|
|
68
|
+
applyTrackWidth(trackWidth.current, options.length);
|
|
69
|
+
}
|
|
70
|
+
}, [options.length, widthSV]);
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
72
|
+
if (!laidOut.current) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (index < 0) {
|
|
76
|
+
opacitySV.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 160 });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
opacitySV.value = (0, react_native_reanimated_1.withTiming)(1, { duration: 160 });
|
|
80
|
+
indexSV.value = (0, react_native_reanimated_1.withTiming)(index, {
|
|
81
|
+
duration: SLIDE_MS,
|
|
82
|
+
easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.cubic),
|
|
83
|
+
});
|
|
84
|
+
}, [index, indexSV, opacitySV]);
|
|
85
|
+
const indicatorStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
86
|
+
width: widthSV.value,
|
|
87
|
+
opacity: opacitySV.value,
|
|
88
|
+
transform: [{ translateX: segmentIndicatorFrame_1.SEGMENT_INSET + indexSV.value * widthSV.value }],
|
|
89
|
+
}));
|
|
90
|
+
if (options.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.group, disabled && styles.blocked, style], children: [label ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.title, { color: titleColor }], children: label })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { accessibilityLabel: label, accessibilityRole: "radiogroup", style: [
|
|
94
|
+
styles.track,
|
|
95
|
+
{ backgroundColor, borderColor, borderRadius },
|
|
96
|
+
], children: [(0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { pointerEvents: "none", style: [
|
|
97
|
+
styles.indicator,
|
|
98
|
+
indicatorStyle,
|
|
99
|
+
{
|
|
100
|
+
backgroundColor: selectedBackgroundColor,
|
|
101
|
+
borderRadius: pillRadius,
|
|
102
|
+
},
|
|
103
|
+
] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { onLayout: (event) => {
|
|
104
|
+
const nextWidth = event.nativeEvent.layout.width;
|
|
105
|
+
if (nextWidth <= 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
applyTrackWidth(nextWidth, options.length);
|
|
109
|
+
if (!laidOut.current) {
|
|
110
|
+
indexSV.value = index < 0 ? 0 : index;
|
|
111
|
+
opacitySV.value = index < 0 ? 0 : 1;
|
|
112
|
+
laidOut.current = true;
|
|
113
|
+
}
|
|
114
|
+
}, style: styles.row, children: options.map((option) => {
|
|
115
|
+
const selected = value === option.value;
|
|
116
|
+
const blocked = disabled || !!option.disabled;
|
|
117
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { accessibilityLabel: option.label, accessibilityRole: "radio", accessibilityState: {
|
|
118
|
+
selected,
|
|
119
|
+
checked: selected,
|
|
120
|
+
disabled: blocked,
|
|
121
|
+
}, disabled: blocked, onPress: () => onChange(option.value), style: ({ pressed }) => [
|
|
122
|
+
styles.segment,
|
|
123
|
+
pressed && !blocked && styles.pressed,
|
|
124
|
+
option.disabled && styles.segmentBlocked,
|
|
125
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
126
|
+
styles.label,
|
|
127
|
+
{
|
|
128
|
+
color: selected ? selectedLabelColor : labelColor,
|
|
129
|
+
fontWeight: selected ? "600" : "500",
|
|
130
|
+
},
|
|
131
|
+
], children: option.label }) }, option.value));
|
|
132
|
+
}) })] })] }));
|
|
133
|
+
}
|
|
134
|
+
const styles = react_native_1.StyleSheet.create({
|
|
135
|
+
group: {
|
|
136
|
+
width: "100%",
|
|
137
|
+
gap: 8,
|
|
138
|
+
},
|
|
139
|
+
blocked: {
|
|
140
|
+
opacity: 0.4,
|
|
141
|
+
},
|
|
142
|
+
title: {
|
|
143
|
+
fontSize: 13,
|
|
144
|
+
fontWeight: "600",
|
|
145
|
+
paddingHorizontal: 4,
|
|
146
|
+
},
|
|
147
|
+
track: {
|
|
148
|
+
width: "100%",
|
|
149
|
+
borderWidth: 1,
|
|
150
|
+
padding: segmentIndicatorFrame_1.SEGMENT_INSET,
|
|
151
|
+
overflow: "hidden",
|
|
152
|
+
},
|
|
153
|
+
indicator: {
|
|
154
|
+
position: "absolute",
|
|
155
|
+
top: segmentIndicatorFrame_1.SEGMENT_INSET,
|
|
156
|
+
bottom: segmentIndicatorFrame_1.SEGMENT_INSET,
|
|
157
|
+
left: 0,
|
|
158
|
+
},
|
|
159
|
+
row: {
|
|
160
|
+
flexDirection: "row",
|
|
161
|
+
},
|
|
162
|
+
segment: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
minHeight: 36,
|
|
165
|
+
paddingVertical: 8,
|
|
166
|
+
paddingHorizontal: 8,
|
|
167
|
+
alignItems: "center",
|
|
168
|
+
justifyContent: "center",
|
|
169
|
+
},
|
|
170
|
+
pressed: {
|
|
171
|
+
opacity: 0.65,
|
|
172
|
+
},
|
|
173
|
+
segmentBlocked: {
|
|
174
|
+
opacity: 0.4,
|
|
175
|
+
},
|
|
176
|
+
label: {
|
|
177
|
+
fontSize: 15,
|
|
178
|
+
textAlign: "center",
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=SegmentedControl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentedControl.js","sourceRoot":"","sources":["../../../src/components/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedControl/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SEGMENTED_THEME_ROLES = exports.SegmentedControl = void 0;
|
|
4
|
+
var SegmentedControl_1 = require("./SegmentedControl");
|
|
5
|
+
Object.defineProperty(exports, "SegmentedControl", { enumerable: true, get: function () { return SegmentedControl_1.SegmentedControl; } });
|
|
6
|
+
var segmentThemeRoles_1 = require("./segmentThemeRoles");
|
|
7
|
+
Object.defineProperty(exports, "SEGMENTED_THEME_ROLES", { enumerable: true, get: function () { return segmentThemeRoles_1.SEGMENTED_THEME_ROLES; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/SegmentedControl/index.ts"],"names":[],"mappings":";;;AAAA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,yDAG6B;AAF3B,0HAAA,qBAAqB,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Gap between the track edge and the sliding selection fill. */
|
|
2
|
+
export declare const SEGMENT_INSET = 3;
|
|
3
|
+
export declare function resolveSegmentIndex<T extends string>(value: T | null, options: readonly {
|
|
4
|
+
value: T;
|
|
5
|
+
}[]): number;
|
|
6
|
+
/**
|
|
7
|
+
* Equal-width selection fill for a segment row.
|
|
8
|
+
* `segmentRowWidth` is the row already inset from the track edge.
|
|
9
|
+
* `inset` is that same gap, applied on the leading side (and, by symmetry, the trailing side).
|
|
10
|
+
* `index` 0 is the leading segment. Returns null when there is nothing to draw.
|
|
11
|
+
*/
|
|
12
|
+
export declare function segmentIndicatorFrame(index: number, count: number, segmentRowWidth: number, inset?: number): {
|
|
13
|
+
x: number;
|
|
14
|
+
width: number;
|
|
15
|
+
} | null;
|
|
16
|
+
//# sourceMappingURL=segmentIndicatorFrame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segmentIndicatorFrame.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedControl/segmentIndicatorFrame.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAClD,KAAK,EAAE,CAAC,GAAG,IAAI,EACf,OAAO,EAAE,SAAS;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,EAAE,GAC/B,MAAM,CAKR;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,EACvB,KAAK,GAAE,MAAsB,GAC5B;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMrC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SEGMENT_INSET = void 0;
|
|
4
|
+
exports.resolveSegmentIndex = resolveSegmentIndex;
|
|
5
|
+
exports.segmentIndicatorFrame = segmentIndicatorFrame;
|
|
6
|
+
/** Gap between the track edge and the sliding selection fill. */
|
|
7
|
+
exports.SEGMENT_INSET = 3;
|
|
8
|
+
function resolveSegmentIndex(value, options) {
|
|
9
|
+
if (value == null) {
|
|
10
|
+
return -1;
|
|
11
|
+
}
|
|
12
|
+
return options.findIndex((option) => option.value === value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Equal-width selection fill for a segment row.
|
|
16
|
+
* `segmentRowWidth` is the row already inset from the track edge.
|
|
17
|
+
* `inset` is that same gap, applied on the leading side (and, by symmetry, the trailing side).
|
|
18
|
+
* `index` 0 is the leading segment. Returns null when there is nothing to draw.
|
|
19
|
+
*/
|
|
20
|
+
function segmentIndicatorFrame(index, count, segmentRowWidth, inset = exports.SEGMENT_INSET) {
|
|
21
|
+
if (index < 0 || count < 1 || segmentRowWidth <= 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const width = segmentRowWidth / count;
|
|
25
|
+
return { x: inset + index * width, width };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=segmentIndicatorFrame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segmentIndicatorFrame.js","sourceRoot":"","sources":["../../../src/components/SegmentedControl/segmentIndicatorFrame.ts"],"names":[],"mappings":";;;AAGA,kDAQC;AAQD,sDAWC;AA9BD,iEAAiE;AACpD,QAAA,aAAa,GAAG,CAAC,CAAC;AAE/B,SAAgB,mBAAmB,CACjC,KAAe,EACf,OAAgC;IAEhC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,SAAgB,qBAAqB,CACnC,KAAa,EACb,KAAa,EACb,eAAuB,EACvB,QAAgB,qBAAa;IAE7B,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,eAAe,GAAG,KAAK,CAAC;IACtC,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const SEGMENTED_THEME_ROLES: {
|
|
2
|
+
readonly backgroundColor: "fieldBackground";
|
|
3
|
+
readonly borderColor: "fieldBorder";
|
|
4
|
+
readonly selectedBackgroundColor: "fieldRadioSelected";
|
|
5
|
+
readonly labelColor: "fieldText";
|
|
6
|
+
readonly selectedLabelColor: "fieldBackground";
|
|
7
|
+
readonly titleColor: "fieldLabel";
|
|
8
|
+
};
|
|
9
|
+
export type SegmentedThemeColorProp = keyof typeof SEGMENTED_THEME_ROLES;
|
|
10
|
+
//# sourceMappingURL=segmentThemeRoles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segmentThemeRoles.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedControl/segmentThemeRoles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB;;;;;;;CAOxB,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,MAAM,OAAO,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SEGMENTED_THEME_ROLES = void 0;
|
|
4
|
+
exports.SEGMENTED_THEME_ROLES = {
|
|
5
|
+
backgroundColor: "fieldBackground",
|
|
6
|
+
borderColor: "fieldBorder",
|
|
7
|
+
selectedBackgroundColor: "fieldRadioSelected",
|
|
8
|
+
labelColor: "fieldText",
|
|
9
|
+
selectedLabelColor: "fieldBackground",
|
|
10
|
+
titleColor: "fieldLabel",
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=segmentThemeRoles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segmentThemeRoles.js","sourceRoot":"","sources":["../../../src/components/SegmentedControl/segmentThemeRoles.ts"],"names":[],"mappings":";;;AAAa,QAAA,qBAAqB,GAAG;IACnC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,uBAAuB,EAAE,oBAAoB;IAC7C,UAAU,EAAE,WAAW;IACvB,kBAAkB,EAAE,iBAAiB;IACrC,UAAU,EAAE,YAAY;CAChB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
import type { ButtonRadius } from "../Button/buttonRadius";
|
|
3
|
+
export type SegmentedOption<T extends string = string> = {
|
|
4
|
+
value: T;
|
|
5
|
+
label: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type SegmentedControlProps<T extends string = string> = {
|
|
9
|
+
value: T | null;
|
|
10
|
+
onChange: (value: T) => void;
|
|
11
|
+
options: SegmentedOption<T>[];
|
|
12
|
+
label?: string;
|
|
13
|
+
radius?: ButtonRadius;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
backgroundColor?: string;
|
|
16
|
+
borderColor?: string;
|
|
17
|
+
selectedBackgroundColor?: string;
|
|
18
|
+
labelColor?: string;
|
|
19
|
+
selectedLabelColor?: string;
|
|
20
|
+
titleColor?: string;
|
|
21
|
+
style?: StyleProp<ViewStyle>;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedControl/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACvD,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IAC7D,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/SegmentedControl/types.ts"],"names":[],"mappings":""}
|
|
@@ -4,5 +4,6 @@ export { FieldText, FieldTextarea, FIELD_THEME_ROLES, type FieldThemeColorProp,
|
|
|
4
4
|
export { Form, FormCloseButton, type FormProps } from "./Form";
|
|
5
5
|
export { RadioField, RADIO_THEME_ROLES, type RadioThemeColorProp, type RadioOption, type RadioFieldProps, } from "./RadioField";
|
|
6
6
|
export { Select, SelectCloseButton, SELECT_THEME_ROLES, DEFAULT_SELECT_DETENTS, DEFAULT_SELECT_INITIAL_DETENT, detentsToSnapPoints, resolveSelectDetentIndex, type SelectThemeColorProp, type SelectCloseButtonSlot, type SelectOption, type SelectProps, } from "./Select";
|
|
7
|
+
export { SegmentedControl, SEGMENTED_THEME_ROLES, type SegmentedThemeColorProp, type SegmentedOption, type SegmentedControlProps, } from "./SegmentedControl";
|
|
7
8
|
export { Slider, SLIDER_THEME_ROLES, type SliderThemeColorProp, type SliderProps, } from "./Slider";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SLIDER_THEME_ROLES = exports.Slider = exports.resolveSelectDetentIndex = exports.detentsToSnapPoints = exports.DEFAULT_SELECT_INITIAL_DETENT = exports.DEFAULT_SELECT_DETENTS = exports.SELECT_THEME_ROLES = exports.SelectCloseButton = exports.Select = exports.RADIO_THEME_ROLES = exports.RadioField = exports.FormCloseButton = exports.Form = exports.FIELD_THEME_ROLES = exports.FieldTextarea = exports.FieldText = exports.filterLinkBlockGroups = exports.LinkBlocks = exports.buttonRadiusValue = exports.Button = void 0;
|
|
3
|
+
exports.SLIDER_THEME_ROLES = exports.Slider = exports.SEGMENTED_THEME_ROLES = exports.SegmentedControl = exports.resolveSelectDetentIndex = exports.detentsToSnapPoints = exports.DEFAULT_SELECT_INITIAL_DETENT = exports.DEFAULT_SELECT_DETENTS = exports.SELECT_THEME_ROLES = exports.SelectCloseButton = exports.Select = exports.RADIO_THEME_ROLES = exports.RadioField = exports.FormCloseButton = exports.Form = exports.FIELD_THEME_ROLES = exports.FieldTextarea = exports.FieldText = exports.filterLinkBlockGroups = exports.LinkBlocks = exports.buttonRadiusValue = exports.Button = void 0;
|
|
4
4
|
var Button_1 = require("./Button");
|
|
5
5
|
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return Button_1.Button; } });
|
|
6
6
|
Object.defineProperty(exports, "buttonRadiusValue", { enumerable: true, get: function () { return Button_1.buttonRadiusValue; } });
|
|
@@ -25,6 +25,9 @@ Object.defineProperty(exports, "DEFAULT_SELECT_DETENTS", { enumerable: true, get
|
|
|
25
25
|
Object.defineProperty(exports, "DEFAULT_SELECT_INITIAL_DETENT", { enumerable: true, get: function () { return Select_1.DEFAULT_SELECT_INITIAL_DETENT; } });
|
|
26
26
|
Object.defineProperty(exports, "detentsToSnapPoints", { enumerable: true, get: function () { return Select_1.detentsToSnapPoints; } });
|
|
27
27
|
Object.defineProperty(exports, "resolveSelectDetentIndex", { enumerable: true, get: function () { return Select_1.resolveSelectDetentIndex; } });
|
|
28
|
+
var SegmentedControl_1 = require("./SegmentedControl");
|
|
29
|
+
Object.defineProperty(exports, "SegmentedControl", { enumerable: true, get: function () { return SegmentedControl_1.SegmentedControl; } });
|
|
30
|
+
Object.defineProperty(exports, "SEGMENTED_THEME_ROLES", { enumerable: true, get: function () { return SegmentedControl_1.SEGMENTED_THEME_ROLES; } });
|
|
28
31
|
var Slider_1 = require("./Slider");
|
|
29
32
|
Object.defineProperty(exports, "Slider", { enumerable: true, get: function () { return Slider_1.Slider; } });
|
|
30
33
|
Object.defineProperty(exports, "SLIDER_THEME_ROLES", { enumerable: true, get: function () { return Slider_1.SLIDER_THEME_ROLES; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":";;;AAAA,mCAKkB;AAJhB,gGAAA,MAAM,OAAA;AAEN,2GAAA,iBAAiB,OAAA;AAGnB,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAKvB,iCAQiB;AAPf,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AAMnB,+BAA+D;AAAtD,4FAAA,IAAI,OAAA;AAAE,uGAAA,eAAe,OAAA;AAC9B,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,+GAAA,iBAAiB,OAAA;AAKnB,mCAYkB;AAXhB,gGAAA,MAAM,OAAA;AACN,2GAAA,iBAAiB,OAAA;AACjB,4GAAA,kBAAkB,OAAA;AAClB,gHAAA,sBAAsB,OAAA;AACtB,uHAAA,6BAA6B,OAAA;AAC7B,6GAAA,mBAAmB,OAAA;AACnB,kHAAA,wBAAwB,OAAA;AAM1B,mCAKkB;AAJhB,gGAAA,MAAM,OAAA;AACN,4GAAA,kBAAkB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":";;;AAAA,mCAKkB;AAJhB,gGAAA,MAAM,OAAA;AAEN,2GAAA,iBAAiB,OAAA;AAGnB,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAKvB,iCAQiB;AAPf,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AAMnB,+BAA+D;AAAtD,4FAAA,IAAI,OAAA;AAAE,uGAAA,eAAe,OAAA;AAC9B,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,+GAAA,iBAAiB,OAAA;AAKnB,mCAYkB;AAXhB,gGAAA,MAAM,OAAA;AACN,2GAAA,iBAAiB,OAAA;AACjB,4GAAA,kBAAkB,OAAA;AAClB,gHAAA,sBAAsB,OAAA;AACtB,uHAAA,6BAA6B,OAAA;AAC7B,6GAAA,mBAAmB,OAAA;AACnB,kHAAA,wBAAwB,OAAA;AAM1B,uDAM4B;AAL1B,oHAAA,gBAAgB,OAAA;AAChB,yHAAA,qBAAqB,OAAA;AAKvB,mCAKkB;AAJhB,gGAAA,MAAM,OAAA;AACN,4GAAA,kBAAkB,OAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const version = "0.2.
|
|
1
|
+
export declare const version = "0.2.3";
|
|
2
2
|
export * from "./theme";
|
|
3
3
|
export { Button, type ButtonProps, buttonRadiusValue, type ButtonRadius, } from "./components/Button";
|
|
4
4
|
export { LinkBlocks, filterLinkBlockGroups, type LinkBlockGroup, type LinkBlockItem, type LinkBlocksProps, } from "./components/LinkBlocks";
|
|
@@ -6,6 +6,7 @@ export { FieldText, FieldTextarea, FIELD_THEME_ROLES, type FieldThemeColorProp,
|
|
|
6
6
|
export { Form, FormCloseButton, type FormProps, } from "./components/Form";
|
|
7
7
|
export { RadioField, RADIO_THEME_ROLES, type RadioThemeColorProp, type RadioOption, type RadioFieldProps, } from "./components/RadioField";
|
|
8
8
|
export { Select, SelectCloseButton, SELECT_THEME_ROLES, DEFAULT_SELECT_DETENTS, DEFAULT_SELECT_INITIAL_DETENT, detentsToSnapPoints, resolveSelectDetentIndex, type SelectThemeColorProp, type SelectCloseButtonSlot, type SelectOption, type SelectProps, } from "./components/Select";
|
|
9
|
+
export { SegmentedControl, SEGMENTED_THEME_ROLES, type SegmentedThemeColorProp, type SegmentedOption, type SegmentedControlProps, } from "./components/SegmentedControl";
|
|
9
10
|
export { Slider, SLIDER_THEME_ROLES, type SliderThemeColorProp, type SliderProps, } from "./components/Slider";
|
|
10
11
|
export * as components from "./components";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,IAAI,EACJ,eAAe,EACf,KAAK,SAAS,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,YAAY,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,IAAI,EACJ,eAAe,EACf,KAAK,SAAS,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -36,8 +36,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.components = exports.SLIDER_THEME_ROLES = exports.Slider = exports.resolveSelectDetentIndex = exports.detentsToSnapPoints = exports.DEFAULT_SELECT_INITIAL_DETENT = exports.DEFAULT_SELECT_DETENTS = exports.SELECT_THEME_ROLES = exports.SelectCloseButton = exports.Select = exports.RADIO_THEME_ROLES = exports.RadioField = exports.FormCloseButton = exports.Form = exports.FIELD_THEME_ROLES = exports.FieldTextarea = exports.FieldText = exports.filterLinkBlockGroups = exports.LinkBlocks = exports.buttonRadiusValue = exports.Button = exports.version = void 0;
|
|
40
|
-
exports.version = "0.2.
|
|
39
|
+
exports.components = exports.SLIDER_THEME_ROLES = exports.Slider = exports.SEGMENTED_THEME_ROLES = exports.SegmentedControl = exports.resolveSelectDetentIndex = exports.detentsToSnapPoints = exports.DEFAULT_SELECT_INITIAL_DETENT = exports.DEFAULT_SELECT_DETENTS = exports.SELECT_THEME_ROLES = exports.SelectCloseButton = exports.Select = exports.RADIO_THEME_ROLES = exports.RadioField = exports.FormCloseButton = exports.Form = exports.FIELD_THEME_ROLES = exports.FieldTextarea = exports.FieldText = exports.filterLinkBlockGroups = exports.LinkBlocks = exports.buttonRadiusValue = exports.Button = exports.version = void 0;
|
|
40
|
+
exports.version = "0.2.3";
|
|
41
41
|
__exportStar(require("./theme"), exports);
|
|
42
42
|
var Button_1 = require("./components/Button");
|
|
43
43
|
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return Button_1.Button; } });
|
|
@@ -63,6 +63,9 @@ Object.defineProperty(exports, "DEFAULT_SELECT_DETENTS", { enumerable: true, get
|
|
|
63
63
|
Object.defineProperty(exports, "DEFAULT_SELECT_INITIAL_DETENT", { enumerable: true, get: function () { return Select_1.DEFAULT_SELECT_INITIAL_DETENT; } });
|
|
64
64
|
Object.defineProperty(exports, "detentsToSnapPoints", { enumerable: true, get: function () { return Select_1.detentsToSnapPoints; } });
|
|
65
65
|
Object.defineProperty(exports, "resolveSelectDetentIndex", { enumerable: true, get: function () { return Select_1.resolveSelectDetentIndex; } });
|
|
66
|
+
var SegmentedControl_1 = require("./components/SegmentedControl");
|
|
67
|
+
Object.defineProperty(exports, "SegmentedControl", { enumerable: true, get: function () { return SegmentedControl_1.SegmentedControl; } });
|
|
68
|
+
Object.defineProperty(exports, "SEGMENTED_THEME_ROLES", { enumerable: true, get: function () { return SegmentedControl_1.SEGMENTED_THEME_ROLES; } });
|
|
66
69
|
var Slider_1 = require("./components/Slider");
|
|
67
70
|
Object.defineProperty(exports, "Slider", { enumerable: true, get: function () { return Slider_1.Slider; } });
|
|
68
71
|
Object.defineProperty(exports, "SLIDER_THEME_ROLES", { enumerable: true, get: function () { return Slider_1.SLIDER_THEME_ROLES; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAa,QAAA,OAAO,GAAG,OAAO,CAAC;AAE/B,0CAAwB;AACxB,8CAK6B;AAJ3B,gGAAA,MAAM,OAAA;AAEN,2GAAA,iBAAiB,OAAA;AAGnB,sDAMiC;AAL/B,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAKvB,4CAQ4B;AAP1B,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AAMnB,0CAI2B;AAHzB,4FAAA,IAAI,OAAA;AACJ,uGAAA,eAAe,OAAA;AAGjB,sDAMiC;AAL/B,wGAAA,UAAU,OAAA;AACV,+GAAA,iBAAiB,OAAA;AAKnB,8CAY6B;AAX3B,gGAAA,MAAM,OAAA;AACN,2GAAA,iBAAiB,OAAA;AACjB,4GAAA,kBAAkB,OAAA;AAClB,gHAAA,sBAAsB,OAAA;AACtB,uHAAA,6BAA6B,OAAA;AAC7B,6GAAA,mBAAmB,OAAA;AACnB,kHAAA,wBAAwB,OAAA;AAM1B,8CAK6B;AAJ3B,gGAAA,MAAM,OAAA;AACN,4GAAA,kBAAkB,OAAA;AAIpB,2DAA2C"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAa,QAAA,OAAO,GAAG,OAAO,CAAC;AAE/B,0CAAwB;AACxB,8CAK6B;AAJ3B,gGAAA,MAAM,OAAA;AAEN,2GAAA,iBAAiB,OAAA;AAGnB,sDAMiC;AAL/B,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAKvB,4CAQ4B;AAP1B,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,0GAAA,iBAAiB,OAAA;AAMnB,0CAI2B;AAHzB,4FAAA,IAAI,OAAA;AACJ,uGAAA,eAAe,OAAA;AAGjB,sDAMiC;AAL/B,wGAAA,UAAU,OAAA;AACV,+GAAA,iBAAiB,OAAA;AAKnB,8CAY6B;AAX3B,gGAAA,MAAM,OAAA;AACN,2GAAA,iBAAiB,OAAA;AACjB,4GAAA,kBAAkB,OAAA;AAClB,gHAAA,sBAAsB,OAAA;AACtB,uHAAA,6BAA6B,OAAA;AAC7B,6GAAA,mBAAmB,OAAA;AACnB,kHAAA,wBAAwB,OAAA;AAM1B,kEAMuC;AALrC,oHAAA,gBAAgB,OAAA;AAChB,yHAAA,qBAAqB,OAAA;AAKvB,8CAK6B;AAJ3B,gGAAA,MAAM,OAAA;AACN,4GAAA,kBAAkB,OAAA;AAIpB,2DAA2C"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SegmentedControl — usage
|
|
2
|
+
|
|
3
|
+
Presentational controlled choice shown as a row of pressable segments. One selection fill sits behind the selected segment and slides to the next choice. It is a radio group: one value, `onChange` with the next value. Colours default from `ThemeProvider`; pass props only to override. Your app owns form state.
|
|
4
|
+
|
|
5
|
+
React Native has no segmented control. iOS `UISegmentedControl` and Android Material segmented buttons are separate platform widgets, and they do not use this package’s theme roles. This control draws the row itself and moves the selection fill with `react-native-reanimated`, which is already a peer (used by `Select`).
|
|
6
|
+
|
|
7
|
+
## SegmentedControl
|
|
8
|
+
|
|
9
|
+
`SegmentedControl` is controlled only (`value` + `onChange`). When colour props are omitted, colours resolve from fixed theme roles (see below). Explicit colour props always win. `ThemeProvider` is required when at least one colour prop is omitted. Full width of the parent is fixed. An empty `options` array renders nothing. `value={null}` shows the track with no selection fill.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { SegmentedControl } from "@studio-piot/native-ui";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
|
|
15
|
+
type DipslideStatus = "ON" | "OFF";
|
|
16
|
+
|
|
17
|
+
const statusOptions = [
|
|
18
|
+
{ value: "ON" as const, label: "On" },
|
|
19
|
+
{ value: "OFF" as const, label: "Off" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function DipslideStatusField() {
|
|
23
|
+
const [status, setStatus] = useState<DipslideStatus>("ON");
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<SegmentedControl
|
|
27
|
+
label="Dipslide status"
|
|
28
|
+
value={status}
|
|
29
|
+
onChange={setStatus}
|
|
30
|
+
options={statusOptions}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
No new theme keys. The selection fill reuses `fieldRadioSelected`. Selected label text reuses `fieldBackground`, which contrasts with that fill in the same light and dark maps.
|
|
37
|
+
|
|
38
|
+
### Colour props (optional overrides)
|
|
39
|
+
|
|
40
|
+
| Prop | Default theme role | Role |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `backgroundColor` | `fieldBackground` | Track fill |
|
|
43
|
+
| `borderColor` | `fieldBorder` | Track border |
|
|
44
|
+
| `selectedBackgroundColor` | `fieldRadioSelected` | Sliding selection fill |
|
|
45
|
+
| `labelColor` | `fieldText` | Unselected label |
|
|
46
|
+
| `selectedLabelColor` | `fieldBackground` | Label on the selection fill |
|
|
47
|
+
| `titleColor` | `fieldLabel` | Group label above the track |
|
|
48
|
+
|
|
49
|
+
### Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Notes |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `value` | `T \| null` | — | Required. Controlled selection; `null` = none selected |
|
|
54
|
+
| `onChange` | `(value: T) => void` | — | Required |
|
|
55
|
+
| `options` | `SegmentedOption<T>[]` | — | Required. Empty list → renders nothing |
|
|
56
|
+
| `label` | `string` | — | Optional caption above the track |
|
|
57
|
+
| `radius` | `ButtonRadius` | `"lg"` | Same steps as `Button` (8px) |
|
|
58
|
+
| `disabled` | `boolean` | `false` | Whole control at 40% opacity; not selectable |
|
|
59
|
+
| `backgroundColor` | `string` | theme `fieldBackground` | Optional override |
|
|
60
|
+
| `borderColor` | `string` | theme `fieldBorder` | Optional override |
|
|
61
|
+
| `selectedBackgroundColor` | `string` | theme `fieldRadioSelected` | Optional override |
|
|
62
|
+
| `labelColor` | `string` | theme `fieldText` | Optional override |
|
|
63
|
+
| `selectedLabelColor` | `string` | theme `fieldBackground` | Optional override |
|
|
64
|
+
| `titleColor` | `string` | theme `fieldLabel` | Optional override |
|
|
65
|
+
| `style` | view style | — | Outer column |
|
|
66
|
+
|
|
67
|
+
### SegmentedOption
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Notes |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `value` | `T` | Stable string id |
|
|
72
|
+
| `label` | `string` | Segment title |
|
|
73
|
+
| `disabled` | `boolean` | Segment at 40% opacity; not selectable |
|
|
74
|
+
|
|
75
|
+
### Layout
|
|
76
|
+
|
|
77
|
+
Segments share the track width equally. The selection fill is inset by 3px on every side: the same gap above and below it, before the first segment, and after the last. It translates to the selected index over 220ms. Track radius defaults to `"lg"` (8). See the [Button radius table](./button.md#radius).
|
|
78
|
+
|
|
79
|
+
### Accessibility
|
|
80
|
+
|
|
81
|
+
The track uses `accessibilityRole="radiogroup"`. Each segment is `accessibilityRole="radio"` with `accessibilityState` including `selected`, `checked` (same as selected; required for Android TalkBack radio semantics), and `disabled`. The sliding fill is decorative.
|
|
82
|
+
|
|
83
|
+
## Imports
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
SegmentedControl,
|
|
88
|
+
SEGMENTED_THEME_ROLES,
|
|
89
|
+
type SegmentedOption,
|
|
90
|
+
type SegmentedControlProps,
|
|
91
|
+
} from "@studio-piot/native-ui";
|
|
92
|
+
|
|
93
|
+
// Or components only:
|
|
94
|
+
import { SegmentedControl } from "@studio-piot/native-ui/components";
|
|
95
|
+
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import Animated, {
|
|
4
|
+
Easing,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withTiming,
|
|
8
|
+
} from "react-native-reanimated";
|
|
9
|
+
import { buttonRadiusValue } from "../Button/buttonRadius";
|
|
10
|
+
import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
|
|
11
|
+
import {
|
|
12
|
+
resolveSegmentIndex,
|
|
13
|
+
SEGMENT_INSET,
|
|
14
|
+
segmentIndicatorFrame,
|
|
15
|
+
} from "./segmentIndicatorFrame";
|
|
16
|
+
import { SEGMENTED_THEME_ROLES } from "./segmentThemeRoles";
|
|
17
|
+
import type { SegmentedControlProps } from "./types";
|
|
18
|
+
|
|
19
|
+
const SLIDE_MS = 220;
|
|
20
|
+
|
|
21
|
+
export function SegmentedControl<T extends string = string>({
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
options,
|
|
25
|
+
label,
|
|
26
|
+
radius = "lg",
|
|
27
|
+
disabled = false,
|
|
28
|
+
backgroundColor: backgroundColorProp,
|
|
29
|
+
borderColor: borderColorProp,
|
|
30
|
+
selectedBackgroundColor: selectedBackgroundColorProp,
|
|
31
|
+
labelColor: labelColorProp,
|
|
32
|
+
selectedLabelColor: selectedLabelColorProp,
|
|
33
|
+
titleColor: titleColorProp,
|
|
34
|
+
style,
|
|
35
|
+
}: SegmentedControlProps<T>) {
|
|
36
|
+
const backgroundColor = useResolvedFieldColor(
|
|
37
|
+
backgroundColorProp,
|
|
38
|
+
SEGMENTED_THEME_ROLES.backgroundColor,
|
|
39
|
+
);
|
|
40
|
+
const borderColor = useResolvedFieldColor(
|
|
41
|
+
borderColorProp,
|
|
42
|
+
SEGMENTED_THEME_ROLES.borderColor,
|
|
43
|
+
);
|
|
44
|
+
const selectedBackgroundColor = useResolvedFieldColor(
|
|
45
|
+
selectedBackgroundColorProp,
|
|
46
|
+
SEGMENTED_THEME_ROLES.selectedBackgroundColor,
|
|
47
|
+
);
|
|
48
|
+
const labelColor = useResolvedFieldColor(
|
|
49
|
+
labelColorProp,
|
|
50
|
+
SEGMENTED_THEME_ROLES.labelColor,
|
|
51
|
+
);
|
|
52
|
+
const selectedLabelColor = useResolvedFieldColor(
|
|
53
|
+
selectedLabelColorProp,
|
|
54
|
+
SEGMENTED_THEME_ROLES.selectedLabelColor,
|
|
55
|
+
);
|
|
56
|
+
const titleColor = useResolvedFieldColor(
|
|
57
|
+
titleColorProp,
|
|
58
|
+
SEGMENTED_THEME_ROLES.titleColor,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const index = resolveSegmentIndex(value, options);
|
|
62
|
+
const trackWidth = useRef(0);
|
|
63
|
+
const laidOut = useRef(false);
|
|
64
|
+
const indexSV = useSharedValue(0);
|
|
65
|
+
const widthSV = useSharedValue(0);
|
|
66
|
+
const opacitySV = useSharedValue(0);
|
|
67
|
+
|
|
68
|
+
const borderRadius = buttonRadiusValue(radius);
|
|
69
|
+
const pillRadius = Math.max(0, borderRadius - SEGMENT_INSET);
|
|
70
|
+
|
|
71
|
+
const applyTrackWidth = (nextWidth: number, count: number) => {
|
|
72
|
+
trackWidth.current = nextWidth;
|
|
73
|
+
const frame = segmentIndicatorFrame(0, count, nextWidth, SEGMENT_INSET);
|
|
74
|
+
widthSV.value = frame?.width ?? 0;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (trackWidth.current > 0) {
|
|
79
|
+
applyTrackWidth(trackWidth.current, options.length);
|
|
80
|
+
}
|
|
81
|
+
}, [options.length, widthSV]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!laidOut.current) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (index < 0) {
|
|
88
|
+
opacitySV.value = withTiming(0, { duration: 160 });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
opacitySV.value = withTiming(1, { duration: 160 });
|
|
92
|
+
indexSV.value = withTiming(index, {
|
|
93
|
+
duration: SLIDE_MS,
|
|
94
|
+
easing: Easing.out(Easing.cubic),
|
|
95
|
+
});
|
|
96
|
+
}, [index, indexSV, opacitySV]);
|
|
97
|
+
|
|
98
|
+
const indicatorStyle = useAnimatedStyle(() => ({
|
|
99
|
+
width: widthSV.value,
|
|
100
|
+
opacity: opacitySV.value,
|
|
101
|
+
transform: [{ translateX: SEGMENT_INSET + indexSV.value * widthSV.value }],
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
if (options.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<View style={[styles.group, disabled && styles.blocked, style]}>
|
|
110
|
+
{label ? (
|
|
111
|
+
<Text style={[styles.title, { color: titleColor }]}>{label}</Text>
|
|
112
|
+
) : null}
|
|
113
|
+
<View
|
|
114
|
+
accessibilityLabel={label}
|
|
115
|
+
accessibilityRole="radiogroup"
|
|
116
|
+
style={[
|
|
117
|
+
styles.track,
|
|
118
|
+
{ backgroundColor, borderColor, borderRadius },
|
|
119
|
+
]}
|
|
120
|
+
>
|
|
121
|
+
<Animated.View
|
|
122
|
+
pointerEvents="none"
|
|
123
|
+
style={[
|
|
124
|
+
styles.indicator,
|
|
125
|
+
indicatorStyle,
|
|
126
|
+
{
|
|
127
|
+
backgroundColor: selectedBackgroundColor,
|
|
128
|
+
borderRadius: pillRadius,
|
|
129
|
+
},
|
|
130
|
+
]}
|
|
131
|
+
/>
|
|
132
|
+
<View
|
|
133
|
+
onLayout={(event) => {
|
|
134
|
+
const nextWidth = event.nativeEvent.layout.width;
|
|
135
|
+
if (nextWidth <= 0) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
applyTrackWidth(nextWidth, options.length);
|
|
139
|
+
if (!laidOut.current) {
|
|
140
|
+
indexSV.value = index < 0 ? 0 : index;
|
|
141
|
+
opacitySV.value = index < 0 ? 0 : 1;
|
|
142
|
+
laidOut.current = true;
|
|
143
|
+
}
|
|
144
|
+
}}
|
|
145
|
+
style={styles.row}
|
|
146
|
+
>
|
|
147
|
+
{options.map((option) => {
|
|
148
|
+
const selected = value === option.value;
|
|
149
|
+
const blocked = disabled || !!option.disabled;
|
|
150
|
+
return (
|
|
151
|
+
<Pressable
|
|
152
|
+
key={option.value}
|
|
153
|
+
accessibilityLabel={option.label}
|
|
154
|
+
accessibilityRole="radio"
|
|
155
|
+
accessibilityState={{
|
|
156
|
+
selected,
|
|
157
|
+
checked: selected,
|
|
158
|
+
disabled: blocked,
|
|
159
|
+
}}
|
|
160
|
+
disabled={blocked}
|
|
161
|
+
onPress={() => onChange(option.value)}
|
|
162
|
+
style={({ pressed }) => [
|
|
163
|
+
styles.segment,
|
|
164
|
+
pressed && !blocked && styles.pressed,
|
|
165
|
+
option.disabled && styles.segmentBlocked,
|
|
166
|
+
]}
|
|
167
|
+
>
|
|
168
|
+
<Text
|
|
169
|
+
style={[
|
|
170
|
+
styles.label,
|
|
171
|
+
{
|
|
172
|
+
color: selected ? selectedLabelColor : labelColor,
|
|
173
|
+
fontWeight: selected ? "600" : "500",
|
|
174
|
+
},
|
|
175
|
+
]}
|
|
176
|
+
>
|
|
177
|
+
{option.label}
|
|
178
|
+
</Text>
|
|
179
|
+
</Pressable>
|
|
180
|
+
);
|
|
181
|
+
})}
|
|
182
|
+
</View>
|
|
183
|
+
</View>
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const styles = StyleSheet.create({
|
|
189
|
+
group: {
|
|
190
|
+
width: "100%",
|
|
191
|
+
gap: 8,
|
|
192
|
+
},
|
|
193
|
+
blocked: {
|
|
194
|
+
opacity: 0.4,
|
|
195
|
+
},
|
|
196
|
+
title: {
|
|
197
|
+
fontSize: 13,
|
|
198
|
+
fontWeight: "600",
|
|
199
|
+
paddingHorizontal: 4,
|
|
200
|
+
},
|
|
201
|
+
track: {
|
|
202
|
+
width: "100%",
|
|
203
|
+
borderWidth: 1,
|
|
204
|
+
padding: SEGMENT_INSET,
|
|
205
|
+
overflow: "hidden",
|
|
206
|
+
},
|
|
207
|
+
indicator: {
|
|
208
|
+
position: "absolute",
|
|
209
|
+
top: SEGMENT_INSET,
|
|
210
|
+
bottom: SEGMENT_INSET,
|
|
211
|
+
left: 0,
|
|
212
|
+
},
|
|
213
|
+
row: {
|
|
214
|
+
flexDirection: "row",
|
|
215
|
+
},
|
|
216
|
+
segment: {
|
|
217
|
+
flex: 1,
|
|
218
|
+
minHeight: 36,
|
|
219
|
+
paddingVertical: 8,
|
|
220
|
+
paddingHorizontal: 8,
|
|
221
|
+
alignItems: "center",
|
|
222
|
+
justifyContent: "center",
|
|
223
|
+
},
|
|
224
|
+
pressed: {
|
|
225
|
+
opacity: 0.65,
|
|
226
|
+
},
|
|
227
|
+
segmentBlocked: {
|
|
228
|
+
opacity: 0.4,
|
|
229
|
+
},
|
|
230
|
+
label: {
|
|
231
|
+
fontSize: 15,
|
|
232
|
+
textAlign: "center",
|
|
233
|
+
},
|
|
234
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
resolveSegmentIndex,
|
|
5
|
+
segmentIndicatorFrame,
|
|
6
|
+
SEGMENT_INSET,
|
|
7
|
+
} from "./segmentIndicatorFrame.ts";
|
|
8
|
+
|
|
9
|
+
describe("resolveSegmentIndex", () => {
|
|
10
|
+
const options = [{ value: "on" }, { value: "off" }, { value: "auto" }];
|
|
11
|
+
|
|
12
|
+
it("returns the matching option index", () => {
|
|
13
|
+
assert.equal(resolveSegmentIndex("off", options), 1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns -1 when nothing is selected", () => {
|
|
17
|
+
assert.equal(resolveSegmentIndex(null, options), -1);
|
|
18
|
+
assert.equal(resolveSegmentIndex("missing", options), -1);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("segmentIndicatorFrame", () => {
|
|
23
|
+
it("keeps the same inset before the first segment and after the last", () => {
|
|
24
|
+
const rowWidth = 300;
|
|
25
|
+
const first = segmentIndicatorFrame(0, 3, rowWidth, SEGMENT_INSET);
|
|
26
|
+
const last = segmentIndicatorFrame(2, 3, rowWidth, SEGMENT_INSET);
|
|
27
|
+
assert.deepEqual(first, { x: SEGMENT_INSET, width: 100 });
|
|
28
|
+
assert.deepEqual(last, { x: 203, width: 100 });
|
|
29
|
+
assert.ok(last);
|
|
30
|
+
const trackInnerWidth = rowWidth + SEGMENT_INSET * 2;
|
|
31
|
+
assert.equal(trackInnerWidth - (last.x + last.width), SEGMENT_INSET);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns null when there is no segment to highlight", () => {
|
|
35
|
+
assert.equal(segmentIndicatorFrame(-1, 3, 300), null);
|
|
36
|
+
assert.equal(segmentIndicatorFrame(0, 0, 300), null);
|
|
37
|
+
assert.equal(segmentIndicatorFrame(0, 2, 0), null);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Gap between the track edge and the sliding selection fill. */
|
|
2
|
+
export const SEGMENT_INSET = 3;
|
|
3
|
+
|
|
4
|
+
export function resolveSegmentIndex<T extends string>(
|
|
5
|
+
value: T | null,
|
|
6
|
+
options: readonly { value: T }[],
|
|
7
|
+
): number {
|
|
8
|
+
if (value == null) {
|
|
9
|
+
return -1;
|
|
10
|
+
}
|
|
11
|
+
return options.findIndex((option) => option.value === value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Equal-width selection fill for a segment row.
|
|
16
|
+
* `segmentRowWidth` is the row already inset from the track edge.
|
|
17
|
+
* `inset` is that same gap, applied on the leading side (and, by symmetry, the trailing side).
|
|
18
|
+
* `index` 0 is the leading segment. Returns null when there is nothing to draw.
|
|
19
|
+
*/
|
|
20
|
+
export function segmentIndicatorFrame(
|
|
21
|
+
index: number,
|
|
22
|
+
count: number,
|
|
23
|
+
segmentRowWidth: number,
|
|
24
|
+
inset: number = SEGMENT_INSET,
|
|
25
|
+
): { x: number; width: number } | null {
|
|
26
|
+
if (index < 0 || count < 1 || segmentRowWidth <= 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const width = segmentRowWidth / count;
|
|
30
|
+
return { x: inset + index * width, width };
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { SEGMENTED_THEME_ROLES } from "./segmentThemeRoles.ts";
|
|
4
|
+
|
|
5
|
+
describe("SEGMENTED_THEME_ROLES", () => {
|
|
6
|
+
it("maps colour props onto existing Field and radio roles", () => {
|
|
7
|
+
assert.equal(SEGMENTED_THEME_ROLES.backgroundColor, "fieldBackground");
|
|
8
|
+
assert.equal(SEGMENTED_THEME_ROLES.borderColor, "fieldBorder");
|
|
9
|
+
assert.equal(
|
|
10
|
+
SEGMENTED_THEME_ROLES.selectedBackgroundColor,
|
|
11
|
+
"fieldRadioSelected",
|
|
12
|
+
);
|
|
13
|
+
assert.equal(SEGMENTED_THEME_ROLES.labelColor, "fieldText");
|
|
14
|
+
assert.equal(SEGMENTED_THEME_ROLES.selectedLabelColor, "fieldBackground");
|
|
15
|
+
assert.equal(SEGMENTED_THEME_ROLES.titleColor, "fieldLabel");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const SEGMENTED_THEME_ROLES = {
|
|
2
|
+
backgroundColor: "fieldBackground",
|
|
3
|
+
borderColor: "fieldBorder",
|
|
4
|
+
selectedBackgroundColor: "fieldRadioSelected",
|
|
5
|
+
labelColor: "fieldText",
|
|
6
|
+
selectedLabelColor: "fieldBackground",
|
|
7
|
+
titleColor: "fieldLabel",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type SegmentedThemeColorProp = keyof typeof SEGMENTED_THEME_ROLES;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
import type { ButtonRadius } from "../Button/buttonRadius";
|
|
3
|
+
|
|
4
|
+
export type SegmentedOption<T extends string = string> = {
|
|
5
|
+
value: T;
|
|
6
|
+
label: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type SegmentedControlProps<T extends string = string> = {
|
|
11
|
+
value: T | null;
|
|
12
|
+
onChange: (value: T) => void;
|
|
13
|
+
options: SegmentedOption<T>[];
|
|
14
|
+
label?: string;
|
|
15
|
+
radius?: ButtonRadius;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
backgroundColor?: string;
|
|
18
|
+
borderColor?: string;
|
|
19
|
+
selectedBackgroundColor?: string;
|
|
20
|
+
labelColor?: string;
|
|
21
|
+
selectedLabelColor?: string;
|
|
22
|
+
titleColor?: string;
|
|
23
|
+
style?: StyleProp<ViewStyle>;
|
|
24
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -41,6 +41,13 @@ export {
|
|
|
41
41
|
type SelectOption,
|
|
42
42
|
type SelectProps,
|
|
43
43
|
} from "./Select";
|
|
44
|
+
export {
|
|
45
|
+
SegmentedControl,
|
|
46
|
+
SEGMENTED_THEME_ROLES,
|
|
47
|
+
type SegmentedThemeColorProp,
|
|
48
|
+
type SegmentedOption,
|
|
49
|
+
type SegmentedControlProps,
|
|
50
|
+
} from "./SegmentedControl";
|
|
44
51
|
export {
|
|
45
52
|
Slider,
|
|
46
53
|
SLIDER_THEME_ROLES,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const version = "0.2.
|
|
1
|
+
export const version = "0.2.3";
|
|
2
2
|
|
|
3
3
|
export * from "./theme";
|
|
4
4
|
export {
|
|
@@ -48,6 +48,13 @@ export {
|
|
|
48
48
|
type SelectOption,
|
|
49
49
|
type SelectProps,
|
|
50
50
|
} from "./components/Select";
|
|
51
|
+
export {
|
|
52
|
+
SegmentedControl,
|
|
53
|
+
SEGMENTED_THEME_ROLES,
|
|
54
|
+
type SegmentedThemeColorProp,
|
|
55
|
+
type SegmentedOption,
|
|
56
|
+
type SegmentedControlProps,
|
|
57
|
+
} from "./components/SegmentedControl";
|
|
51
58
|
export {
|
|
52
59
|
Slider,
|
|
53
60
|
SLIDER_THEME_ROLES,
|