@studio-piot/native-ui 0.2.0 → 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 +4 -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/Select/Select.d.ts +3 -1
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.js +72 -28
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/SelectCloseButton.d.ts +9 -3
- package/dist/components/Select/SelectCloseButton.d.ts.map +1 -1
- package/dist/components/Select/SelectCloseButton.js +2 -5
- package/dist/components/Select/SelectCloseButton.js.map +1 -1
- package/dist/components/Select/presentSelectSheet.d.ts +7 -0
- package/dist/components/Select/presentSelectSheet.d.ts.map +1 -0
- package/dist/components/Select/presentSelectSheet.js +14 -0
- package/dist/components/Select/presentSelectSheet.js.map +1 -0
- package/dist/components/Select/selectThemeRoles.d.ts +1 -1
- package/dist/components/Select/selectThemeRoles.js +1 -1
- package/dist/components/Select/selectThemeRoles.js.map +1 -1
- package/dist/components/Select/types.d.ts +2 -2
- package/dist/components/Select/types.d.ts.map +1 -1
- package/dist/components/Slider/Slider.d.ts +3 -0
- package/dist/components/Slider/Slider.d.ts.map +1 -0
- package/dist/components/Slider/Slider.js +46 -0
- package/dist/components/Slider/Slider.js.map +1 -0
- package/dist/components/Slider/index.d.ts +4 -0
- package/dist/components/Slider/index.d.ts.map +1 -0
- package/dist/components/Slider/index.js +8 -0
- package/dist/components/Slider/index.js.map +1 -0
- package/dist/components/Slider/sliderThemeRoles.d.ts +8 -0
- package/dist/components/Slider/sliderThemeRoles.d.ts.map +1 -0
- package/dist/components/Slider/sliderThemeRoles.js +10 -0
- package/dist/components/Slider/sliderThemeRoles.js.map +1 -0
- package/dist/components/Slider/types.d.ts +18 -0
- package/dist/components/Slider/types.d.ts.map +1 -0
- package/dist/components/Slider/types.js +3 -0
- package/dist/components/Slider/types.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -1
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/docs/components/segmented-control.md +95 -0
- package/docs/components/select.md +11 -6
- package/docs/components/slider.md +65 -0
- package/docs/superpowers/plans/2026-09-25-slider.md +35 -0
- package/docs/superpowers/specs/2026-09-25-select-design.md +3 -3
- package/docs/superpowers/specs/2026-09-25-slider-design.md +117 -0
- package/package.json +6 -2
- 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/Select/Select.tsx +133 -64
- package/src/components/Select/SelectCloseButton.tsx +15 -7
- package/src/components/Select/selectThemeRoles.test.ts +2 -2
- package/src/components/Select/selectThemeRoles.ts +1 -1
- package/src/components/Select/types.ts +2 -2
- package/src/components/Slider/Slider.tsx +95 -0
- package/src/components/Slider/index.ts +6 -0
- package/src/components/Slider/sliderThemeRoles.test.ts +12 -0
- package/src/components/Slider/sliderThemeRoles.ts +8 -0
- package/src/components/Slider/types.ts +18 -0
- package/src/components/index.ts +13 -0
- package/src/index.ts +14 -1
|
@@ -5,26 +5,41 @@ import {
|
|
|
5
5
|
type ElementRef,
|
|
6
6
|
type ReactNode,
|
|
7
7
|
} from "react";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Platform,
|
|
10
|
+
Pressable,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
Text,
|
|
13
|
+
useWindowDimensions,
|
|
14
|
+
View,
|
|
15
|
+
} from "react-native";
|
|
9
16
|
import {
|
|
10
17
|
BottomSheetBackdrop,
|
|
18
|
+
BottomSheetHandle,
|
|
11
19
|
BottomSheetModal,
|
|
12
20
|
BottomSheetScrollView,
|
|
13
21
|
type BottomSheetBackdropProps,
|
|
22
|
+
type BottomSheetHandleProps,
|
|
14
23
|
} from "@gorhom/bottom-sheet";
|
|
24
|
+
import { FullWindowOverlay } from "react-native-screens";
|
|
15
25
|
import { buttonRadiusValue } from "../Button/buttonRadius";
|
|
16
26
|
import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
DEFAULT_SELECT_INITIAL_DETENT,
|
|
20
|
-
detentsToSnapPoints,
|
|
21
|
-
resolveSelectDetentIndex,
|
|
22
|
-
} from "./resolveSelectDetentIndex";
|
|
27
|
+
import { useThemeColor } from "../../theme/useThemeColor";
|
|
28
|
+
import { DEFAULT_SELECT_DETENTS } from "./resolveSelectDetentIndex";
|
|
23
29
|
import { SelectCloseButton } from "./SelectCloseButton";
|
|
24
30
|
import { SELECT_THEME_ROLES } from "./selectThemeRoles";
|
|
25
31
|
import type { SelectOption, SelectProps } from "./types";
|
|
26
32
|
|
|
27
33
|
const BACKDROP_OPACITY = 0.72;
|
|
34
|
+
const SHEET_TOP_RADIUS = 20;
|
|
35
|
+
|
|
36
|
+
/** Renders the sheet above React Navigation native modals (iOS). */
|
|
37
|
+
function SheetContainer({ children }: { children?: ReactNode }) {
|
|
38
|
+
if (Platform.OS === "ios") {
|
|
39
|
+
return <FullWindowOverlay>{children}</FullWindowOverlay>;
|
|
40
|
+
}
|
|
41
|
+
return <>{children}</>;
|
|
42
|
+
}
|
|
28
43
|
|
|
29
44
|
export function Select({
|
|
30
45
|
value,
|
|
@@ -36,8 +51,9 @@ export function Select({
|
|
|
36
51
|
error,
|
|
37
52
|
sheetTitle,
|
|
38
53
|
closeButton,
|
|
54
|
+
/** Max sheet height as a fraction of the window (largest value wins). Default `[0.25, 0.5, 0.75]` → 75%. */
|
|
39
55
|
detents = [...DEFAULT_SELECT_DETENTS],
|
|
40
|
-
initialDetent
|
|
56
|
+
initialDetent: _initialDetent,
|
|
41
57
|
radius = "xl",
|
|
42
58
|
editable = true,
|
|
43
59
|
backgroundColor: backgroundColorProp,
|
|
@@ -51,6 +67,7 @@ export function Select({
|
|
|
51
67
|
style,
|
|
52
68
|
}: SelectProps) {
|
|
53
69
|
const modalRef = useRef<ElementRef<typeof BottomSheetModal>>(null);
|
|
70
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
54
71
|
|
|
55
72
|
const backgroundColor = useResolvedFieldColor(
|
|
56
73
|
backgroundColorProp,
|
|
@@ -84,6 +101,9 @@ export function Select({
|
|
|
84
101
|
selectedColorProp,
|
|
85
102
|
SELECT_THEME_ROLES.selectedColor,
|
|
86
103
|
);
|
|
104
|
+
// Resolved on the trigger tree (has ThemeProvider), then passed into the portal.
|
|
105
|
+
const closeDiscColor = useThemeColor("text");
|
|
106
|
+
const closeIconColor = useThemeColor("background");
|
|
87
107
|
|
|
88
108
|
const hasError = Boolean(error);
|
|
89
109
|
const selected = options.find((option) => option.value === value);
|
|
@@ -91,8 +111,11 @@ export function Select({
|
|
|
91
111
|
const isPlaceholder = selected == null;
|
|
92
112
|
const borderRadius = buttonRadiusValue(radius);
|
|
93
113
|
const title = sheetTitle ?? label ?? "Select";
|
|
94
|
-
|
|
95
|
-
const
|
|
114
|
+
|
|
115
|
+
const maxDynamicContentSize = useMemo(() => {
|
|
116
|
+
const maxDetent = Math.max(...detents, 0.75);
|
|
117
|
+
return Math.round(windowHeight * maxDetent);
|
|
118
|
+
}, [detents, windowHeight]);
|
|
96
119
|
|
|
97
120
|
const dismiss = useCallback(() => {
|
|
98
121
|
modalRef.current?.dismiss();
|
|
@@ -119,7 +142,52 @@ export function Select({
|
|
|
119
142
|
const resolvedClose: ReactNode =
|
|
120
143
|
typeof closeButton === "function"
|
|
121
144
|
? closeButton(dismiss)
|
|
122
|
-
: (closeButton ??
|
|
145
|
+
: (closeButton ?? (
|
|
146
|
+
<SelectCloseButton
|
|
147
|
+
backgroundColor={closeDiscColor}
|
|
148
|
+
iconColor={closeIconColor}
|
|
149
|
+
onPress={dismiss}
|
|
150
|
+
/>
|
|
151
|
+
));
|
|
152
|
+
|
|
153
|
+
// Header lives in the handle so it stays fixed; only options scroll.
|
|
154
|
+
const renderHandle = useCallback(
|
|
155
|
+
(props: BottomSheetHandleProps) => (
|
|
156
|
+
<View
|
|
157
|
+
style={[
|
|
158
|
+
styles.handleRoot,
|
|
159
|
+
{
|
|
160
|
+
backgroundColor,
|
|
161
|
+
borderTopLeftRadius: SHEET_TOP_RADIUS,
|
|
162
|
+
borderTopRightRadius: SHEET_TOP_RADIUS,
|
|
163
|
+
},
|
|
164
|
+
]}
|
|
165
|
+
>
|
|
166
|
+
<BottomSheetHandle
|
|
167
|
+
{...props}
|
|
168
|
+
indicatorStyle={{
|
|
169
|
+
backgroundColor: placeholderColor,
|
|
170
|
+
opacity: 0.9,
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
<View style={styles.header}>
|
|
174
|
+
<Text style={[styles.sheetTitle, { color: textColor, flex: 1 }]}>
|
|
175
|
+
{title}
|
|
176
|
+
</Text>
|
|
177
|
+
{resolvedClose}
|
|
178
|
+
</View>
|
|
179
|
+
<View style={[styles.headerDivider, { backgroundColor: borderColor }]} />
|
|
180
|
+
</View>
|
|
181
|
+
),
|
|
182
|
+
[
|
|
183
|
+
backgroundColor,
|
|
184
|
+
borderColor,
|
|
185
|
+
placeholderColor,
|
|
186
|
+
resolvedClose,
|
|
187
|
+
textColor,
|
|
188
|
+
title,
|
|
189
|
+
],
|
|
190
|
+
);
|
|
123
191
|
|
|
124
192
|
const onSelect = (option: SelectOption) => {
|
|
125
193
|
if (option.disabled) return;
|
|
@@ -176,67 +244,62 @@ export function Select({
|
|
|
176
244
|
<BottomSheetModal
|
|
177
245
|
ref={modalRef}
|
|
178
246
|
backdropComponent={renderBackdrop}
|
|
179
|
-
backgroundStyle={{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
opacity: 0.9,
|
|
247
|
+
backgroundStyle={{
|
|
248
|
+
backgroundColor,
|
|
249
|
+
borderTopLeftRadius: SHEET_TOP_RADIUS,
|
|
250
|
+
borderTopRightRadius: SHEET_TOP_RADIUS,
|
|
184
251
|
}}
|
|
185
|
-
|
|
186
|
-
|
|
252
|
+
containerComponent={SheetContainer}
|
|
253
|
+
enableDynamicSizing
|
|
254
|
+
enablePanDownToClose
|
|
255
|
+
handleComponent={renderHandle}
|
|
256
|
+
handleStyle={styles.handleShell}
|
|
257
|
+
maxDynamicContentSize={maxDynamicContentSize}
|
|
258
|
+
stackBehavior="push"
|
|
187
259
|
>
|
|
188
|
-
<BottomSheetScrollView
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
</Text>
|
|
193
|
-
{resolvedClose}
|
|
194
|
-
</View>
|
|
260
|
+
<BottomSheetScrollView
|
|
261
|
+
contentContainerStyle={styles.sheetContent}
|
|
262
|
+
enableFooterMarginAdjustment={false}
|
|
263
|
+
>
|
|
195
264
|
{options.length === 0 ? (
|
|
196
265
|
<Text style={[styles.empty, { color: placeholderColor }]}>
|
|
197
266
|
No options
|
|
198
267
|
</Text>
|
|
199
268
|
) : (
|
|
200
|
-
options.map((option
|
|
269
|
+
options.map((option) => {
|
|
201
270
|
const isSelected = option.value === value;
|
|
202
271
|
return (
|
|
203
|
-
<
|
|
204
|
-
{
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
272
|
+
<Pressable
|
|
273
|
+
key={option.value}
|
|
274
|
+
accessibilityRole="button"
|
|
275
|
+
accessibilityState={{
|
|
276
|
+
selected: isSelected,
|
|
277
|
+
disabled: !!option.disabled,
|
|
278
|
+
}}
|
|
279
|
+
disabled={option.disabled}
|
|
280
|
+
onPress={() => onSelect(option)}
|
|
281
|
+
style={[
|
|
282
|
+
styles.option,
|
|
283
|
+
option.disabled && styles.optionBlocked,
|
|
284
|
+
]}
|
|
285
|
+
>
|
|
286
|
+
<Text
|
|
217
287
|
style={[
|
|
218
|
-
styles.
|
|
219
|
-
|
|
288
|
+
styles.optionLabel,
|
|
289
|
+
{
|
|
290
|
+
color: isSelected ? selectedColor : textColor,
|
|
291
|
+
flex: 1,
|
|
292
|
+
},
|
|
220
293
|
]}
|
|
221
294
|
>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
flex: 1,
|
|
228
|
-
},
|
|
229
|
-
]}
|
|
230
|
-
>
|
|
231
|
-
{option.label}
|
|
295
|
+
{option.label}
|
|
296
|
+
</Text>
|
|
297
|
+
{isSelected ? (
|
|
298
|
+
<Text style={[styles.check, { color: selectedColor }]}>
|
|
299
|
+
✓
|
|
232
300
|
</Text>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
✓
|
|
236
|
-
</Text>
|
|
237
|
-
) : null}
|
|
238
|
-
</Pressable>
|
|
239
|
-
</View>
|
|
301
|
+
) : null}
|
|
302
|
+
</Pressable>
|
|
240
303
|
);
|
|
241
304
|
})
|
|
242
305
|
)}
|
|
@@ -275,10 +338,17 @@ const styles = StyleSheet.create({
|
|
|
275
338
|
fontSize: 22,
|
|
276
339
|
fontWeight: "300",
|
|
277
340
|
lineHeight: 24,
|
|
341
|
+
transform: [{ rotate: "90deg" }],
|
|
278
342
|
},
|
|
279
343
|
helper: {
|
|
280
344
|
fontSize: 13,
|
|
281
345
|
},
|
|
346
|
+
handleShell: {
|
|
347
|
+
paddingBottom: 0,
|
|
348
|
+
},
|
|
349
|
+
handleRoot: {
|
|
350
|
+
overflow: "hidden",
|
|
351
|
+
},
|
|
282
352
|
sheetContent: {
|
|
283
353
|
paddingBottom: 24,
|
|
284
354
|
},
|
|
@@ -287,9 +357,12 @@ const styles = StyleSheet.create({
|
|
|
287
357
|
alignItems: "center",
|
|
288
358
|
gap: 12,
|
|
289
359
|
paddingHorizontal: 16,
|
|
290
|
-
paddingTop:
|
|
360
|
+
paddingTop: 4,
|
|
291
361
|
paddingBottom: 12,
|
|
292
362
|
},
|
|
363
|
+
headerDivider: {
|
|
364
|
+
height: StyleSheet.hairlineWidth,
|
|
365
|
+
},
|
|
293
366
|
sheetTitle: {
|
|
294
367
|
fontSize: 17,
|
|
295
368
|
fontWeight: "600",
|
|
@@ -316,8 +389,4 @@ const styles = StyleSheet.create({
|
|
|
316
389
|
fontSize: 17,
|
|
317
390
|
fontWeight: "600",
|
|
318
391
|
},
|
|
319
|
-
divider: {
|
|
320
|
-
height: StyleSheet.hairlineWidth,
|
|
321
|
-
marginHorizontal: 16,
|
|
322
|
-
},
|
|
323
392
|
});
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { Pressable, StyleSheet, Text } from "react-native";
|
|
2
|
-
import { useThemeColor } from "../../theme/useThemeColor";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
type SelectCloseButtonProps = {
|
|
4
|
+
onPress: () => void;
|
|
5
|
+
/** Disc fill. Select passes theme colours so this stays portal-safe. */
|
|
6
|
+
backgroundColor?: string;
|
|
7
|
+
/** Cross colour. */
|
|
8
|
+
iconColor?: string;
|
|
9
|
+
};
|
|
8
10
|
|
|
11
|
+
/** Default Select sheet close: filled disc + contrasting cross. */
|
|
12
|
+
export function SelectCloseButton({
|
|
13
|
+
onPress,
|
|
14
|
+
backgroundColor = "#111111",
|
|
15
|
+
iconColor = "#FFFFFF",
|
|
16
|
+
}: SelectCloseButtonProps) {
|
|
9
17
|
return (
|
|
10
18
|
<Pressable
|
|
11
19
|
accessibilityLabel="Close"
|
|
12
20
|
accessibilityRole="button"
|
|
13
21
|
hitSlop={8}
|
|
14
22
|
onPress={onPress}
|
|
15
|
-
style={[styles.disc, { backgroundColor
|
|
23
|
+
style={[styles.disc, { backgroundColor }]}
|
|
16
24
|
>
|
|
17
|
-
<Text style={[styles.discX, { color:
|
|
25
|
+
<Text style={[styles.discX, { color: iconColor }]}>×</Text>
|
|
18
26
|
</Pressable>
|
|
19
27
|
);
|
|
20
28
|
}
|
|
@@ -3,7 +3,7 @@ import { describe, it } from "node:test";
|
|
|
3
3
|
import { SELECT_THEME_ROLES } from "./selectThemeRoles.ts";
|
|
4
4
|
|
|
5
5
|
describe("SELECT_THEME_ROLES", () => {
|
|
6
|
-
it("maps colour props to Field roles plus
|
|
6
|
+
it("maps colour props to Field roles plus fieldSelectSelected", () => {
|
|
7
7
|
assert.equal(SELECT_THEME_ROLES.backgroundColor, "fieldBackground");
|
|
8
8
|
assert.equal(SELECT_THEME_ROLES.textColor, "fieldText");
|
|
9
9
|
assert.equal(SELECT_THEME_ROLES.placeholderColor, "fieldPlaceholder");
|
|
@@ -11,6 +11,6 @@ describe("SELECT_THEME_ROLES", () => {
|
|
|
11
11
|
assert.equal(SELECT_THEME_ROLES.descriptionColor, "fieldDescription");
|
|
12
12
|
assert.equal(SELECT_THEME_ROLES.borderColor, "fieldBorder");
|
|
13
13
|
assert.equal(SELECT_THEME_ROLES.errorColor, "fieldError");
|
|
14
|
-
assert.equal(SELECT_THEME_ROLES.selectedColor, "
|
|
14
|
+
assert.equal(SELECT_THEME_ROLES.selectedColor, "fieldSelectSelected");
|
|
15
15
|
});
|
|
16
16
|
});
|
|
@@ -6,7 +6,7 @@ export const SELECT_THEME_ROLES = {
|
|
|
6
6
|
descriptionColor: "fieldDescription",
|
|
7
7
|
borderColor: "fieldBorder",
|
|
8
8
|
errorColor: "fieldError",
|
|
9
|
-
selectedColor: "
|
|
9
|
+
selectedColor: "fieldSelectSelected",
|
|
10
10
|
} as const;
|
|
11
11
|
|
|
12
12
|
export type SelectThemeColorProp = keyof typeof SELECT_THEME_ROLES;
|
|
@@ -23,9 +23,9 @@ export type SelectProps = {
|
|
|
23
23
|
sheetTitle?: string;
|
|
24
24
|
/** DiscClose by default. Pass `(dismiss) => <YourClose onPress={dismiss} />` to override. */
|
|
25
25
|
closeButton?: SelectCloseButton;
|
|
26
|
-
/** Detent fractions 0–1
|
|
26
|
+
/** Detent fractions 0–1. Largest value caps sheet height (content-sized up to that max). Default `[0.25, 0.5, 0.75]`. */
|
|
27
27
|
detents?: number[];
|
|
28
|
-
/**
|
|
28
|
+
/** @deprecated Ignored — sheet opens at content height (capped by `detents`). */
|
|
29
29
|
initialDetent?: number;
|
|
30
30
|
radius?: ButtonRadius;
|
|
31
31
|
editable?: boolean;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
2
|
+
import CommunitySlider from "@react-native-community/slider";
|
|
3
|
+
import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
|
|
4
|
+
import { SLIDER_THEME_ROLES } from "./sliderThemeRoles";
|
|
5
|
+
import type { SliderProps } from "./types";
|
|
6
|
+
|
|
7
|
+
export function Slider({
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
minimumValue,
|
|
11
|
+
maximumValue,
|
|
12
|
+
step,
|
|
13
|
+
label,
|
|
14
|
+
formatValue = String,
|
|
15
|
+
disabled = false,
|
|
16
|
+
labelColor: labelColorProp,
|
|
17
|
+
valueColor: valueColorProp,
|
|
18
|
+
highlightColor: highlightColorProp,
|
|
19
|
+
trackColor: trackColorProp,
|
|
20
|
+
style,
|
|
21
|
+
}: SliderProps) {
|
|
22
|
+
const labelColor = useResolvedFieldColor(
|
|
23
|
+
labelColorProp,
|
|
24
|
+
SLIDER_THEME_ROLES.labelColor,
|
|
25
|
+
);
|
|
26
|
+
const valueColor = useResolvedFieldColor(
|
|
27
|
+
valueColorProp,
|
|
28
|
+
SLIDER_THEME_ROLES.valueColor,
|
|
29
|
+
);
|
|
30
|
+
const highlightColor = useResolvedFieldColor(
|
|
31
|
+
highlightColorProp,
|
|
32
|
+
SLIDER_THEME_ROLES.highlightColor,
|
|
33
|
+
);
|
|
34
|
+
const trackColor = useResolvedFieldColor(
|
|
35
|
+
trackColorProp,
|
|
36
|
+
SLIDER_THEME_ROLES.trackColor,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const display = formatValue(value);
|
|
40
|
+
const accessibilityLabel = label ? `${label}. ${display}` : display;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View style={[styles.column, disabled && styles.blocked, style]}>
|
|
44
|
+
<View style={styles.header}>
|
|
45
|
+
{label ? (
|
|
46
|
+
<Text style={[styles.label, { color: labelColor, flex: 1 }]}>
|
|
47
|
+
{label}
|
|
48
|
+
</Text>
|
|
49
|
+
) : (
|
|
50
|
+
<View style={{ flex: 1 }} />
|
|
51
|
+
)}
|
|
52
|
+
<Text style={[styles.value, { color: valueColor }]}>{display}</Text>
|
|
53
|
+
</View>
|
|
54
|
+
<CommunitySlider
|
|
55
|
+
accessibilityLabel={accessibilityLabel}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
maximumTrackTintColor={trackColor}
|
|
58
|
+
maximumValue={maximumValue}
|
|
59
|
+
minimumTrackTintColor={highlightColor}
|
|
60
|
+
minimumValue={minimumValue}
|
|
61
|
+
onValueChange={onChange}
|
|
62
|
+
step={step}
|
|
63
|
+
style={styles.track}
|
|
64
|
+
thumbTintColor={highlightColor}
|
|
65
|
+
value={value}
|
|
66
|
+
/>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const styles = StyleSheet.create({
|
|
72
|
+
column: {
|
|
73
|
+
width: "100%",
|
|
74
|
+
gap: 6,
|
|
75
|
+
},
|
|
76
|
+
blocked: {
|
|
77
|
+
opacity: 0.4,
|
|
78
|
+
},
|
|
79
|
+
header: {
|
|
80
|
+
flexDirection: "row",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
gap: 12,
|
|
83
|
+
},
|
|
84
|
+
label: {
|
|
85
|
+
fontSize: 13,
|
|
86
|
+
fontWeight: "600",
|
|
87
|
+
},
|
|
88
|
+
value: {
|
|
89
|
+
fontSize: 17,
|
|
90
|
+
},
|
|
91
|
+
track: {
|
|
92
|
+
width: "100%",
|
|
93
|
+
height: 40,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { SLIDER_THEME_ROLES } from "./sliderThemeRoles.ts";
|
|
4
|
+
|
|
5
|
+
describe("SLIDER_THEME_ROLES", () => {
|
|
6
|
+
it("maps colour props to Field roles plus fieldSliderHighlight", () => {
|
|
7
|
+
assert.equal(SLIDER_THEME_ROLES.labelColor, "fieldLabel");
|
|
8
|
+
assert.equal(SLIDER_THEME_ROLES.valueColor, "fieldText");
|
|
9
|
+
assert.equal(SLIDER_THEME_ROLES.highlightColor, "fieldSliderHighlight");
|
|
10
|
+
assert.equal(SLIDER_THEME_ROLES.trackColor, "fieldBorder");
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
export type SliderProps = {
|
|
4
|
+
value: number;
|
|
5
|
+
onChange: (value: number) => void;
|
|
6
|
+
minimumValue: number;
|
|
7
|
+
maximumValue: number;
|
|
8
|
+
step?: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Defaults to `String(value)`. */
|
|
11
|
+
formatValue?: (value: number) => string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
labelColor?: string;
|
|
14
|
+
valueColor?: string;
|
|
15
|
+
highlightColor?: string;
|
|
16
|
+
trackColor?: string;
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -41,3 +41,16 @@ 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";
|
|
51
|
+
export {
|
|
52
|
+
Slider,
|
|
53
|
+
SLIDER_THEME_ROLES,
|
|
54
|
+
type SliderThemeColorProp,
|
|
55
|
+
type SliderProps,
|
|
56
|
+
} from "./Slider";
|
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,4 +48,17 @@ 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";
|
|
58
|
+
export {
|
|
59
|
+
Slider,
|
|
60
|
+
SLIDER_THEME_ROLES,
|
|
61
|
+
type SliderThemeColorProp,
|
|
62
|
+
type SliderProps,
|
|
63
|
+
} from "./components/Slider";
|
|
51
64
|
export * as components from "./components";
|