@delacour/react-native-ui 0.1.0-alpha.20260925053522
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 +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +193 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +434 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +242 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +246 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +150 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +105 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +293 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +220 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +112 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +47 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +95 -0
- package/src/components/field/field.tsx +157 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +240 -0
- package/src/components/field/field.variants.ts +156 -0
- package/src/components/field/index.ts +22 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +74 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +261 -0
- package/src/components/pressable/pressable.variants.test.ts +128 -0
- package/src/components/pressable/pressable.variants.ts +80 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +245 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +580 -0
- package/src/components/radio/radio.variants.ts +271 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +50 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +193 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +856 -0
- package/src/components/slider/slider.variants.ts +661 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +287 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +670 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { useSharedValue } from "react-native-reanimated";
|
|
4
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
5
|
+
import { useFieldContext } from "../field/field.context";
|
|
6
|
+
import type { HapticFeedback } from "../pressable";
|
|
7
|
+
import { type SliderContextValue, SliderProvider } from "./slider.context";
|
|
8
|
+
import type { SliderRenderProps } from "./slider.types";
|
|
9
|
+
import {
|
|
10
|
+
clampThumb,
|
|
11
|
+
fromValueArray,
|
|
12
|
+
resolveSliderAxes,
|
|
13
|
+
SLIDER_MAX_VALUE,
|
|
14
|
+
SLIDER_MIN_VALUE,
|
|
15
|
+
SLIDER_STEP,
|
|
16
|
+
type SliderColor,
|
|
17
|
+
type SliderOrientation,
|
|
18
|
+
type SliderSize,
|
|
19
|
+
sliderVariants,
|
|
20
|
+
snapToStep,
|
|
21
|
+
toValueArray,
|
|
22
|
+
} from "./slider.variants";
|
|
23
|
+
import { SliderFill } from "./slider-fill";
|
|
24
|
+
import { SliderOutput } from "./slider-output";
|
|
25
|
+
import { SliderThumb } from "./slider-thumb";
|
|
26
|
+
import { SliderTrack } from "./slider-track";
|
|
27
|
+
|
|
28
|
+
export type SliderProps = Omit<ViewProps, "children"> & {
|
|
29
|
+
/** Controlled value. An array makes it a range, one thumb per entry. */
|
|
30
|
+
value?: number | number[];
|
|
31
|
+
/** Starting value while uncontrolled. An array makes it a range. */
|
|
32
|
+
defaultValue?: number | number[];
|
|
33
|
+
minValue?: number;
|
|
34
|
+
maxValue?: number;
|
|
35
|
+
/** The increment the value snaps to. `0` is continuous, and never ticks. */
|
|
36
|
+
step?: number;
|
|
37
|
+
/** Passed to `Intl.NumberFormat` by `Slider.Output`. */
|
|
38
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
39
|
+
/** `vertical` counts up from the bottom, and needs a definite height. */
|
|
40
|
+
orientation?: SliderOrientation;
|
|
41
|
+
/** What the filled part of the track means. */
|
|
42
|
+
color?: SliderColor;
|
|
43
|
+
size?: SliderSize;
|
|
44
|
+
/** Blocks the drag and fades the control. Inherited from an enclosing `Field`. */
|
|
45
|
+
isDisabled?: boolean;
|
|
46
|
+
/** Reports an invalid value. Inherited from an enclosing `Field`. */
|
|
47
|
+
isInvalid?: boolean;
|
|
48
|
+
/** Played on grab and as the value crosses a step. `false` silences it. */
|
|
49
|
+
haptic?: false | HapticFeedback;
|
|
50
|
+
/** Fires on every change during the drag. */
|
|
51
|
+
onChange?: (value: number | number[]) => void;
|
|
52
|
+
/** Fires once, when the drag is released. Where a network write belongs. */
|
|
53
|
+
onChangeEnd?: (value: number | number[]) => void;
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function SliderRoot({
|
|
58
|
+
value,
|
|
59
|
+
defaultValue = SLIDER_MIN_VALUE,
|
|
60
|
+
minValue = SLIDER_MIN_VALUE,
|
|
61
|
+
maxValue = SLIDER_MAX_VALUE,
|
|
62
|
+
step = SLIDER_STEP,
|
|
63
|
+
formatOptions,
|
|
64
|
+
orientation,
|
|
65
|
+
color,
|
|
66
|
+
size,
|
|
67
|
+
isDisabled,
|
|
68
|
+
isInvalid,
|
|
69
|
+
haptic = "selection",
|
|
70
|
+
onChange,
|
|
71
|
+
onChangeEnd,
|
|
72
|
+
className,
|
|
73
|
+
children,
|
|
74
|
+
...props
|
|
75
|
+
}: SliderProps): ReactElement {
|
|
76
|
+
const field = useFieldContext();
|
|
77
|
+
const axes = resolveSliderAxes({ field, own: { color, isDisabled, isInvalid, orientation, size } });
|
|
78
|
+
|
|
79
|
+
const [current, setCurrent] = useControllableState<number | number[]>({
|
|
80
|
+
defaultValue,
|
|
81
|
+
onChange,
|
|
82
|
+
value,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Which shape the caller speaks is locked on first render, exactly the way
|
|
86
|
+
// `useControllableState` locks controlled versus uncontrolled — a slider that
|
|
87
|
+
// silently started reporting an array to a caller holding a number would be a
|
|
88
|
+
// bug with no error attached. Warn once per flip, and follow the caller rather
|
|
89
|
+
// than fighting them.
|
|
90
|
+
const isRangeNow = Array.isArray(value ?? defaultValue);
|
|
91
|
+
const wasRange = useRef(isRangeNow);
|
|
92
|
+
if (process.env.NODE_ENV !== "production" && wasRange.current !== isRangeNow) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`Slider: switched from ${wasRange.current ? "a range" : "a single value"} to ` +
|
|
95
|
+
`${isRangeNow ? "a range" : "a single value"}. Pick one for the lifetime of the component.`
|
|
96
|
+
);
|
|
97
|
+
wasRange.current = isRangeNow;
|
|
98
|
+
}
|
|
99
|
+
const isRange = wasRange.current;
|
|
100
|
+
|
|
101
|
+
const values = useMemo(() => toValueArray(current), [current]);
|
|
102
|
+
|
|
103
|
+
const positions = useSharedValue<number[]>(toValueArray(current));
|
|
104
|
+
const trackSize = useSharedValue(0);
|
|
105
|
+
const thumbSize = useSharedValue(0);
|
|
106
|
+
const activeIndex = useSharedValue(-1);
|
|
107
|
+
|
|
108
|
+
// A ref rather than state: the pan reads it a few times a drag and nothing
|
|
109
|
+
// renders differently for it, so a re-render on touch-down would be pure cost.
|
|
110
|
+
const isDragging = useRef(false);
|
|
111
|
+
// Bumped when a drag finishes, purely to make the sync effect below run again.
|
|
112
|
+
// Without it a controlled parent that *rejects* the dragged value leaves the
|
|
113
|
+
// thumb where the finger let go, because `current` never changed to sync from.
|
|
114
|
+
const [settledDrags, setSettledDrags] = useState(0);
|
|
115
|
+
|
|
116
|
+
// `settledDrags` is not read in the body — being unread is the whole point of
|
|
117
|
+
// it. A controlled parent that rejects a dragged value leaves `current`
|
|
118
|
+
// unchanged, so without a token that moves on every release there is nothing to
|
|
119
|
+
// re-run on, and the thumb stays where the finger let go rather than snapping
|
|
120
|
+
// back to the value the parent actually holds.
|
|
121
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: the extra dependency is the re-run trigger, see above
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (isDragging.current) return;
|
|
124
|
+
positions.value = toValueArray(current);
|
|
125
|
+
}, [current, positions, settledDrags]);
|
|
126
|
+
|
|
127
|
+
const setDragging = useCallback((dragging: boolean) => {
|
|
128
|
+
isDragging.current = dragging;
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
const commitValues = useCallback(
|
|
132
|
+
(next: number[]) => {
|
|
133
|
+
setCurrent(fromValueArray(next, isRange));
|
|
134
|
+
},
|
|
135
|
+
[isRange, setCurrent]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const commitEnd = useCallback(
|
|
139
|
+
(next: number[]) => {
|
|
140
|
+
setSettledDrags((count) => count + 1);
|
|
141
|
+
onChangeEnd?.(fromValueArray(next, isRange));
|
|
142
|
+
},
|
|
143
|
+
[isRange, onChangeEnd]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// The one way into the value that has no gesture behind it — an assistive
|
|
147
|
+
// increment. It runs the same snap and the same clamp the pan does, from the
|
|
148
|
+
// JS thread, and writes both sides so the thumb moves without waiting on a
|
|
149
|
+
// render.
|
|
150
|
+
const valuesRef = useRef(values);
|
|
151
|
+
valuesRef.current = values;
|
|
152
|
+
|
|
153
|
+
const updateValue = useCallback(
|
|
154
|
+
(index: number, next: number) => {
|
|
155
|
+
const from = valuesRef.current;
|
|
156
|
+
const settled = clampThumb(snapToStep(next, step, minValue, maxValue), from, index, minValue, maxValue);
|
|
157
|
+
if (settled === from[index]) return;
|
|
158
|
+
|
|
159
|
+
const updated = [...from];
|
|
160
|
+
updated[index] = settled;
|
|
161
|
+
positions.value = updated;
|
|
162
|
+
setCurrent(fromValueArray(updated, isRange));
|
|
163
|
+
onChangeEnd?.(fromValueArray(updated, isRange));
|
|
164
|
+
},
|
|
165
|
+
[isRange, maxValue, minValue, onChangeEnd, positions, setCurrent, step]
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const renderProps = useMemo<SliderRenderProps>(
|
|
169
|
+
() => ({
|
|
170
|
+
isDisabled: axes.isDisabled,
|
|
171
|
+
isInvalid: axes.isInvalid,
|
|
172
|
+
maxValue,
|
|
173
|
+
minValue,
|
|
174
|
+
orientation: axes.orientation,
|
|
175
|
+
step,
|
|
176
|
+
values,
|
|
177
|
+
}),
|
|
178
|
+
[axes.isDisabled, axes.isInvalid, axes.orientation, maxValue, minValue, step, values]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const context = useMemo<SliderContextValue>(
|
|
182
|
+
() => ({
|
|
183
|
+
activeIndex,
|
|
184
|
+
color: axes.color,
|
|
185
|
+
commitEnd,
|
|
186
|
+
commitValues,
|
|
187
|
+
formatOptions,
|
|
188
|
+
haptic,
|
|
189
|
+
isDisabled: axes.isDisabled,
|
|
190
|
+
isInvalid: axes.isInvalid,
|
|
191
|
+
maxValue,
|
|
192
|
+
minValue,
|
|
193
|
+
orientation: axes.orientation,
|
|
194
|
+
positions,
|
|
195
|
+
renderProps,
|
|
196
|
+
setDragging,
|
|
197
|
+
size: axes.size,
|
|
198
|
+
step,
|
|
199
|
+
thumbSize,
|
|
200
|
+
trackSize,
|
|
201
|
+
updateValue,
|
|
202
|
+
values,
|
|
203
|
+
}),
|
|
204
|
+
[
|
|
205
|
+
activeIndex,
|
|
206
|
+
axes.color,
|
|
207
|
+
axes.isDisabled,
|
|
208
|
+
axes.isInvalid,
|
|
209
|
+
axes.orientation,
|
|
210
|
+
axes.size,
|
|
211
|
+
commitEnd,
|
|
212
|
+
commitValues,
|
|
213
|
+
formatOptions,
|
|
214
|
+
haptic,
|
|
215
|
+
maxValue,
|
|
216
|
+
minValue,
|
|
217
|
+
positions,
|
|
218
|
+
renderProps,
|
|
219
|
+
setDragging,
|
|
220
|
+
step,
|
|
221
|
+
thumbSize,
|
|
222
|
+
trackSize,
|
|
223
|
+
updateValue,
|
|
224
|
+
values,
|
|
225
|
+
]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<SliderProvider value={context}>
|
|
230
|
+
<View className={sliderVariants(axes).root({ className })} {...props}>
|
|
231
|
+
{children}
|
|
232
|
+
</View>
|
|
233
|
+
</SliderProvider>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A value picked by dragging along a track — one value, or a range.
|
|
239
|
+
*
|
|
240
|
+
* The anatomy is written out rather than assembled from props: a `Slider.Track`
|
|
241
|
+
* holding a `Slider.Fill` and one `Slider.Thumb` per value, with an optional
|
|
242
|
+
* `Slider.Output` above it. A range's thumb count is data, so `Slider.Track`
|
|
243
|
+
* takes a function and maps over the values it is handed.
|
|
244
|
+
*
|
|
245
|
+
* **The whole track is the target.** Touching down grabs the nearest thumb and
|
|
246
|
+
* moves it to the finger; there is no separate tap mode. `Slider.Thumb` holds no
|
|
247
|
+
* gesture of its own, which is what makes a press on empty track lift the handle
|
|
248
|
+
* it is about to move.
|
|
249
|
+
*
|
|
250
|
+
* **State works either way from one hook**: pass `value` to control it, or
|
|
251
|
+
* nothing and let it hold its own. The shape is the caller's — a slider given a
|
|
252
|
+
* number reports a number, one given an array reports an array — and it is locked
|
|
253
|
+
* on first render.
|
|
254
|
+
*
|
|
255
|
+
* `onChange` fires throughout the drag; `onChangeEnd` fires once on release,
|
|
256
|
+
* which is where a network write or a persisted preference belongs.
|
|
257
|
+
*
|
|
258
|
+
* `isInvalid` and `isDisabled` cascade in from an enclosing `Field`, so
|
|
259
|
+
* `<Field isDisabled>` dims it with nothing said here — and an explicit prop still
|
|
260
|
+
* wins, in either direction.
|
|
261
|
+
*
|
|
262
|
+
* A haptic ticks on grab and as the value crosses a step, rate-limited by how far
|
|
263
|
+
* the drag has travelled so a fine step scale reads as a cadence rather than a
|
|
264
|
+
* buzz. `haptic={false}` silences it; a continuous slider never ticks.
|
|
265
|
+
*
|
|
266
|
+
* **A `vertical` slider needs a definite height from its parent** — it counts up
|
|
267
|
+
* from the bottom and fills whatever it is given, so a wrapper with `h-48` or
|
|
268
|
+
* `flex-1` is not optional.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* <Slider defaultValue={30}>
|
|
272
|
+
* <Slider.Output />
|
|
273
|
+
* <Slider.Track>
|
|
274
|
+
* <Slider.Fill />
|
|
275
|
+
* <Slider.Thumb />
|
|
276
|
+
* </Slider.Track>
|
|
277
|
+
* </Slider>
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* <Slider
|
|
281
|
+
* color="success"
|
|
282
|
+
* formatOptions={{ currency: "NZD", style: "currency" }}
|
|
283
|
+
* maxValue={1000}
|
|
284
|
+
* onChange={setPrice}
|
|
285
|
+
* step={10}
|
|
286
|
+
* value={price}
|
|
287
|
+
* >
|
|
288
|
+
* <Slider.Output />
|
|
289
|
+
* <Slider.Track>
|
|
290
|
+
* {({ values }) => (
|
|
291
|
+
* <>
|
|
292
|
+
* <Slider.Fill />
|
|
293
|
+
* {values.map((_, index) => (
|
|
294
|
+
* <Slider.Thumb index={index} key={index} />
|
|
295
|
+
* ))}
|
|
296
|
+
* </>
|
|
297
|
+
* )}
|
|
298
|
+
* </Slider.Track>
|
|
299
|
+
* </Slider>
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* <View className="h-48">
|
|
303
|
+
* <Slider defaultValue={40} orientation="vertical">
|
|
304
|
+
* <Slider.Track>
|
|
305
|
+
* <Slider.Fill />
|
|
306
|
+
* <Slider.Thumb />
|
|
307
|
+
* </Slider.Track>
|
|
308
|
+
* </Slider>
|
|
309
|
+
* </View>
|
|
310
|
+
*/
|
|
311
|
+
export const Slider = Object.assign(SliderRoot, {
|
|
312
|
+
/** The current value, formatted. Give it a function to label it yourself. */
|
|
313
|
+
Output: SliderOutput,
|
|
314
|
+
/** The groove the thumbs run along, and the surface the drag is claimed on. */
|
|
315
|
+
Track: SliderTrack,
|
|
316
|
+
/** The painted part of the groove, from the minimum — or between two thumbs. */
|
|
317
|
+
Fill: SliderFill,
|
|
318
|
+
/** The grab handle. `index` picks which value it drives in a range. */
|
|
319
|
+
Thumb: SliderThumb,
|
|
320
|
+
displayName: "DelacourUI.Slider",
|
|
321
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SliderOrientation } from "./slider.variants";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a render function inside a slider is handed.
|
|
5
|
+
*
|
|
6
|
+
* Shared by `Slider.Output` and `Slider.Track`, which is what puts it here rather
|
|
7
|
+
* than in either one's own file — two consumers is the bar a `{name}.types.ts`
|
|
8
|
+
* entry has to clear. See AGENTS.md.
|
|
9
|
+
*
|
|
10
|
+
* It carries the *settled* state, so a render function never has to know whether
|
|
11
|
+
* an axis came from its own call site or from an enclosing `Field`.
|
|
12
|
+
*/
|
|
13
|
+
export type SliderRenderProps = {
|
|
14
|
+
/** Every thumb's value, in the order the thumbs were given. */
|
|
15
|
+
values: number[];
|
|
16
|
+
minValue: number;
|
|
17
|
+
maxValue: number;
|
|
18
|
+
/** The increment the values snap to. `0` is continuous. */
|
|
19
|
+
step: number;
|
|
20
|
+
orientation: SliderOrientation;
|
|
21
|
+
isDisabled: boolean;
|
|
22
|
+
isInvalid: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** A part whose children may be written out or computed from the slider's state. */
|
|
26
|
+
export type SliderRenderChildren = React.ReactNode | ((props: SliderRenderProps) => React.ReactNode);
|