@atlure/ui 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +39 -6
  2. package/package.json +5 -6
  3. package/src/components/alert-dialog/alert-dialog.tsx +59 -0
  4. package/src/components/alert-dialog/utils.ts +5 -0
  5. package/src/components/avatar/avatar-group.tsx +52 -0
  6. package/src/components/avatar/avatar.tsx +54 -11
  7. package/src/components/badge/urgency-badge.tsx +21 -0
  8. package/src/components/calendar/calendar.tsx +189 -0
  9. package/src/components/checkbox/checkbox.tsx +26 -9
  10. package/src/components/chip/chip.tsx +66 -0
  11. package/src/components/date-range-picker/date-range-picker.tsx +104 -0
  12. package/src/components/dialog/dialog.tsx +137 -0
  13. package/src/components/format/date-label.tsx +41 -0
  14. package/src/components/format/distance-label.tsx +13 -0
  15. package/src/components/format/duration-label.tsx +13 -0
  16. package/src/components/format/money-label.tsx +20 -0
  17. package/src/components/picker/picker.tsx +104 -0
  18. package/src/components/progress/progress.tsx +95 -0
  19. package/src/components/progress/utils.ts +10 -0
  20. package/src/components/radio-group/radio-group-context.tsx +27 -0
  21. package/src/components/radio-group/radio-group.tsx +150 -0
  22. package/src/components/screen-header/screen-header.tsx +62 -0
  23. package/src/components/segmented-control/segmented-control.tsx +67 -0
  24. package/src/components/select/select.tsx +134 -0
  25. package/src/components/settings-row/settings-row.tsx +52 -0
  26. package/src/components/sheet/sheet.tsx +139 -0
  27. package/src/components/sheet/utils.ts +55 -0
  28. package/src/components/slider/format-label.ts +3 -0
  29. package/src/components/slider/hooks/use-slider-drag.ts +47 -0
  30. package/src/components/slider/range-slider.tsx +203 -0
  31. package/src/components/slider/slider.tsx +148 -0
  32. package/src/components/slider/utils.ts +57 -0
  33. package/src/components/star-rating/star-rating.tsx +98 -0
  34. package/src/components/switch/switch.tsx +34 -6
  35. package/src/components/tabs/tabs-context.tsx +32 -0
  36. package/src/components/tabs/tabs.tsx +239 -0
  37. package/src/components/time-picker/time-picker.tsx +131 -0
  38. package/src/components/toast/toast-context.tsx +26 -0
  39. package/src/components/toast/toast-provider.tsx +70 -0
  40. package/src/components/toast/toast.tsx +41 -0
  41. package/src/components/toast/utils.ts +2 -0
  42. package/src/index.ts +54 -0
  43. package/src/lib/calendar.ts +204 -0
  44. package/src/lib/format/date.ts +46 -0
  45. package/src/lib/format/distance.ts +36 -0
  46. package/src/lib/format/duration.ts +28 -0
  47. package/src/lib/format/money.ts +20 -0
  48. package/src/lib/locale.tsx +36 -0
  49. package/src/lib/portal.tsx +54 -0
  50. package/src/lib/touch-target.ts +5 -1
  51. package/src/lib/use-sheet.ts +18 -0
  52. package/src/lib/use-toast.ts +17 -0
  53. package/src/variants/avatar-variants.ts +68 -15
  54. package/src/variants/badge-variants.ts +8 -0
  55. package/src/variants/calendar-variants.ts +26 -0
  56. package/src/variants/checkbox-variants.ts +5 -14
  57. package/src/variants/chip-variants.ts +38 -0
  58. package/src/variants/dialog-variants.ts +11 -0
  59. package/src/variants/index.ts +9 -0
  60. package/src/variants/overlay-variants.ts +1 -0
  61. package/src/variants/progress-variants.ts +18 -0
  62. package/src/variants/radio-group-variants.ts +48 -0
  63. package/src/variants/screen-header-variants.ts +33 -0
  64. package/src/variants/segmented-control-variants.ts +41 -0
  65. package/src/variants/select-variants.ts +48 -0
  66. package/src/variants/settings-row-variants.ts +18 -0
  67. package/src/variants/sheet-variants.ts +10 -0
  68. package/src/variants/slider-variants.ts +8 -0
  69. package/src/variants/star-rating-variants.ts +28 -0
  70. package/src/variants/switch-variants.ts +26 -4
  71. package/src/variants/tabs-variants.ts +46 -0
  72. package/src/variants/toast-variants.ts +31 -0
@@ -0,0 +1,104 @@
1
+ import { useState } from "react";
2
+ import { View } from "react-native";
3
+
4
+ import { daysBetween } from "../../lib/calendar";
5
+ import { cn } from "../../lib/cn";
6
+ import { formatDateRange, type DateStyle } from "../../lib/format/date";
7
+ import { useLocale } from "../../lib/locale";
8
+ import {
9
+ dateRangePickerContainerClassName,
10
+ dateRangePickerSummaryClassName,
11
+ } from "../../variants/calendar-variants";
12
+ import { Calendar } from "../calendar/calendar";
13
+ import { Text } from "../text/text";
14
+
15
+ export interface DateRange {
16
+ start?: string;
17
+ end?: string;
18
+ }
19
+
20
+ export interface DateRangePickerProps {
21
+ yearMonth: string;
22
+ onYearMonthChange?: (yearMonth: string) => void;
23
+ range?: DateRange;
24
+ onRangeChange: (range: { start: string; end: string }) => void;
25
+ minNights?: number;
26
+ maxNights?: number;
27
+ disabledDates?: (iso: string) => boolean;
28
+ minDate?: string;
29
+ maxDate?: string;
30
+ markers?: readonly string[];
31
+ accessibilityLabel?: string;
32
+ summaryAccessibilityLabel?: string;
33
+ summaryDateStyle?: DateStyle;
34
+ className?: string;
35
+ }
36
+
37
+ export function DateRangePicker({
38
+ yearMonth,
39
+ onYearMonthChange,
40
+ range,
41
+ onRangeChange,
42
+ minNights,
43
+ maxNights,
44
+ disabledDates,
45
+ minDate,
46
+ maxDate,
47
+ markers,
48
+ accessibilityLabel,
49
+ summaryAccessibilityLabel = "Selected date range",
50
+ summaryDateStyle = "medium",
51
+ className,
52
+ }: DateRangePickerProps) {
53
+ const { locale } = useLocale();
54
+ const [inProgressStart, setInProgressStart] = useState<string | undefined>(undefined);
55
+
56
+ const handleSelect = (iso: string) => {
57
+ if (inProgressStart === undefined) {
58
+ setInProgressStart(iso);
59
+ return;
60
+ }
61
+
62
+ setInProgressStart(undefined);
63
+
64
+ const [start, end] =
65
+ inProgressStart < iso ? [inProgressStart, iso] : [iso, inProgressStart];
66
+ const nights = daysBetween(start, end);
67
+
68
+ if (nights <= 0) return;
69
+ if (minNights !== undefined && nights < minNights) return;
70
+ if (maxNights !== undefined && nights > maxNights) return;
71
+
72
+ onRangeChange({ start, end });
73
+ };
74
+
75
+ return (
76
+ <View
77
+ accessibilityLabel={accessibilityLabel}
78
+ className={cn(dateRangePickerContainerClassName, className)}
79
+ >
80
+ <Calendar
81
+ yearMonth={yearMonth}
82
+ onMonthChange={onYearMonthChange}
83
+ onSelect={handleSelect}
84
+ disabledDates={disabledDates}
85
+ minDate={minDate}
86
+ maxDate={maxDate}
87
+ markers={markers}
88
+ highlight={{
89
+ start: range?.start,
90
+ end: range?.end,
91
+ inProgressStart,
92
+ }}
93
+ />
94
+ {range?.start !== undefined && range.end !== undefined && (
95
+ <Text
96
+ accessibilityLabel={summaryAccessibilityLabel}
97
+ className={dateRangePickerSummaryClassName}
98
+ >
99
+ {formatDateRange(range.start, range.end, locale, summaryDateStyle)}
100
+ </Text>
101
+ )}
102
+ </View>
103
+ );
104
+ }
@@ -0,0 +1,137 @@
1
+ import { type ReactNode, useEffect, useId } from "react";
2
+ import { BackHandler, Modal, Platform, Pressable, View } from "react-native";
3
+
4
+ import { cn } from "../../lib/cn";
5
+ import { Portal, usePortalRegistry } from "../../lib/portal";
6
+ import {
7
+ dialogContentClassName,
8
+ dialogDescriptionClassName,
9
+ dialogFooterClassName,
10
+ dialogHeaderClassName,
11
+ dialogOverlayClassName,
12
+ dialogTitleClassName,
13
+ } from "../../variants/dialog-variants";
14
+ import { overlayBackdropClassName } from "../../variants/overlay-variants";
15
+ import { Text } from "../text/text";
16
+
17
+ export interface DialogProps {
18
+ isOpen: boolean;
19
+ onClose: () => void;
20
+ backdropAccessibilityLabel?: string;
21
+ accessibilityLabel?: string;
22
+ isDismissible?: boolean;
23
+ className?: string;
24
+ children: ReactNode;
25
+ }
26
+
27
+ export function Dialog({
28
+ isOpen,
29
+ onClose,
30
+ backdropAccessibilityLabel,
31
+ accessibilityLabel,
32
+ isDismissible = true,
33
+ className,
34
+ children,
35
+ }: DialogProps) {
36
+ const { activeOverlayId, requestSlot, releaseSlot } = usePortalRegistry();
37
+ const overlayId = useId();
38
+
39
+ useEffect(() => {
40
+ if (!isOpen) return;
41
+
42
+ requestSlot(overlayId);
43
+ return () => releaseSlot(overlayId);
44
+ }, [isOpen, overlayId, requestSlot, releaseSlot]);
45
+
46
+ const isVisible = isOpen && activeOverlayId === overlayId;
47
+
48
+ useEffect(() => {
49
+ if (!isVisible || !isDismissible || Platform.OS !== "android") return;
50
+
51
+ const subscription = BackHandler.addEventListener("hardwareBackPress", () => {
52
+ onClose();
53
+ return true;
54
+ });
55
+
56
+ return () => subscription.remove();
57
+ }, [isVisible, isDismissible, onClose]);
58
+
59
+ return (
60
+ <Portal>
61
+ <Modal
62
+ visible={isVisible}
63
+ transparent
64
+ animationType="none"
65
+ onRequestClose={isDismissible ? onClose : undefined}
66
+ >
67
+ <View className={dialogOverlayClassName}>
68
+ {isDismissible ? (
69
+ <Pressable
70
+ accessibilityRole="button"
71
+ accessibilityLabel={backdropAccessibilityLabel}
72
+ className={overlayBackdropClassName}
73
+ onPress={onClose}
74
+ />
75
+ ) : (
76
+ <View className={overlayBackdropClassName} />
77
+ )}
78
+ <View
79
+ accessibilityViewIsModal
80
+ accessibilityLabel={accessibilityLabel}
81
+ className={cn(dialogContentClassName, className)}
82
+ >
83
+ {children}
84
+ </View>
85
+ </View>
86
+ </Modal>
87
+ </Portal>
88
+ );
89
+ }
90
+
91
+ export function DialogHeader({
92
+ className,
93
+ children,
94
+ }: {
95
+ className?: string;
96
+ children: ReactNode;
97
+ }) {
98
+ return <View className={cn(dialogHeaderClassName, className)}>{children}</View>;
99
+ }
100
+
101
+ export function DialogTitle({ className, children }: { className?: string; children: string }) {
102
+ return (
103
+ <Text role="heading" aria-level={2} className={cn(dialogTitleClassName, className)}>
104
+ {children}
105
+ </Text>
106
+ );
107
+ }
108
+
109
+ export function DialogDescription({
110
+ className,
111
+ children,
112
+ }: {
113
+ className?: string;
114
+ children: string;
115
+ }) {
116
+ return <Text className={cn(dialogDescriptionClassName, className)}>{children}</Text>;
117
+ }
118
+
119
+ export function DialogContent({
120
+ className,
121
+ children,
122
+ }: {
123
+ className?: string;
124
+ children: ReactNode;
125
+ }) {
126
+ return <View className={className}>{children}</View>;
127
+ }
128
+
129
+ export function DialogFooter({
130
+ className,
131
+ children,
132
+ }: {
133
+ className?: string;
134
+ children: ReactNode;
135
+ }) {
136
+ return <View className={cn(dialogFooterClassName, className)}>{children}</View>;
137
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ formatDate,
3
+ formatDateRange,
4
+ formatRelativeDate,
5
+ type DateStyle,
6
+ } from "../../lib/format/date";
7
+ import { useLocale } from "../../lib/locale";
8
+ import { Text, type TextProps } from "../text/text";
9
+
10
+ export interface DateLabelProps extends Omit<TextProps, "children"> {
11
+ value: string;
12
+ dateStyle?: DateStyle;
13
+ relative?: boolean;
14
+ }
15
+
16
+ export function DateLabel({ value, dateStyle = "medium", relative, ...textProps }: DateLabelProps) {
17
+ const { locale } = useLocale();
18
+
19
+ return (
20
+ <Text {...textProps}>
21
+ {relative === true ? formatRelativeDate(value, locale) : formatDate(value, locale, dateStyle)}
22
+ </Text>
23
+ );
24
+ }
25
+
26
+ export interface DateRangeLabelProps extends Omit<TextProps, "children"> {
27
+ start: string;
28
+ end: string;
29
+ dateStyle?: DateStyle;
30
+ }
31
+
32
+ export function DateRangeLabel({
33
+ start,
34
+ end,
35
+ dateStyle = "medium",
36
+ ...textProps
37
+ }: DateRangeLabelProps) {
38
+ const { locale } = useLocale();
39
+
40
+ return <Text {...textProps}>{formatDateRange(start, end, locale, dateStyle)}</Text>;
41
+ }
@@ -0,0 +1,13 @@
1
+ import { formatDistance } from "../../lib/format/distance";
2
+ import { useLocale } from "../../lib/locale";
3
+ import { Text, type TextProps } from "../text/text";
4
+
5
+ export interface DistanceLabelProps extends Omit<TextProps, "children"> {
6
+ meters: number;
7
+ }
8
+
9
+ export function DistanceLabel({ meters, ...textProps }: DistanceLabelProps) {
10
+ const { locale, measurementSystem } = useLocale();
11
+
12
+ return <Text {...textProps}>{formatDistance(meters, locale, measurementSystem)}</Text>;
13
+ }
@@ -0,0 +1,13 @@
1
+ import { formatDuration } from "../../lib/format/duration";
2
+ import { useLocale } from "../../lib/locale";
3
+ import { Text, type TextProps } from "../text/text";
4
+
5
+ export interface DurationLabelProps extends Omit<TextProps, "children"> {
6
+ minutes: number;
7
+ }
8
+
9
+ export function DurationLabel({ minutes, ...textProps }: DurationLabelProps) {
10
+ const { locale } = useLocale();
11
+
12
+ return <Text {...textProps}>{formatDuration(minutes, locale)}</Text>;
13
+ }
@@ -0,0 +1,20 @@
1
+ import type { Money } from "@atlure/types";
2
+
3
+ import { formatMoney, formatMoneyRate, type RateUnit } from "../../lib/format/money";
4
+ import { useLocale } from "../../lib/locale";
5
+ import { Text, type TextProps } from "../text/text";
6
+
7
+ export interface MoneyLabelProps extends Omit<TextProps, "children"> {
8
+ value: Money;
9
+ per?: RateUnit;
10
+ }
11
+
12
+ export function MoneyLabel({ value, per, ...textProps }: MoneyLabelProps) {
13
+ const { locale } = useLocale();
14
+
15
+ return (
16
+ <Text {...textProps}>
17
+ {per === undefined ? formatMoney(value, locale) : formatMoneyRate(value, locale, per)}
18
+ </Text>
19
+ );
20
+ }
@@ -0,0 +1,104 @@
1
+ import { Pressable, ScrollView, View } from "react-native";
2
+
3
+ import { cn } from "../../lib/cn";
4
+ import { touchTargetHitSlop } from "../../lib/touch-target";
5
+ import { useSheet } from "../../lib/use-sheet";
6
+ import { inputVariants, type InputVariantProps } from "../../variants/input-variants";
7
+ import {
8
+ selectOptionListClassName,
9
+ selectTriggerClassName,
10
+ selectValueVariants,
11
+ } from "../../variants/select-variants";
12
+ import { SelectItem, type SelectOption, type SelectValue } from "../select/select";
13
+ import { Sheet } from "../sheet/sheet";
14
+ import { Text } from "../text/text";
15
+
16
+ export interface PickerProps<TValue extends SelectValue> {
17
+ options: readonly SelectOption<TValue>[];
18
+ values: readonly NoInfer<TValue>[];
19
+ onValuesChange: (values: NoInfer<TValue>[]) => void;
20
+ placeholder: string;
21
+ accessibilityLabel: string;
22
+ backdropAccessibilityLabel: string;
23
+ size?: NonNullable<InputVariantProps["size"]>;
24
+ isDisabled?: boolean;
25
+ isInvalid?: boolean;
26
+ snapPoints?: readonly number[];
27
+ bottomInset?: number;
28
+ className?: string;
29
+ }
30
+
31
+ export function Picker<TValue extends SelectValue>({
32
+ options,
33
+ values,
34
+ onValuesChange,
35
+ placeholder,
36
+ accessibilityLabel,
37
+ backdropAccessibilityLabel,
38
+ size = "md",
39
+ isDisabled = false,
40
+ isInvalid = false,
41
+ snapPoints,
42
+ bottomInset,
43
+ className,
44
+ }: PickerProps<TValue>) {
45
+ const sheet = useSheet();
46
+ const selectedLabels = options
47
+ .filter((option) => values.includes(option.value))
48
+ .map((option) => option.label);
49
+
50
+ return (
51
+ <>
52
+ <Pressable
53
+ accessibilityRole="combobox"
54
+ accessibilityLabel={accessibilityLabel}
55
+ accessibilityState={{ disabled: isDisabled, expanded: sheet.isOpen }}
56
+ accessibilityValue={{ text: selectedLabels.join(", ") || placeholder }}
57
+ aria-disabled={isDisabled}
58
+ aria-expanded={sheet.isOpen}
59
+ aria-invalid={isInvalid}
60
+ aria-multiselectable
61
+ disabled={isDisabled}
62
+ hitSlop={touchTargetHitSlop(size)}
63
+ onPress={sheet.open}
64
+ className={cn(
65
+ inputVariants({ size, isInvalid, isDisabled }),
66
+ selectTriggerClassName,
67
+ className,
68
+ )}
69
+ >
70
+ <Text className={selectValueVariants({ isPlaceholder: selectedLabels.length === 0 })}>
71
+ {selectedLabels.join(", ") || placeholder}
72
+ </Text>
73
+ </Pressable>
74
+ <Sheet
75
+ isOpen={sheet.isOpen}
76
+ onClose={sheet.close}
77
+ accessibilityLabel={accessibilityLabel}
78
+ backdropAccessibilityLabel={backdropAccessibilityLabel}
79
+ snapPoints={snapPoints}
80
+ bottomInset={bottomInset}
81
+ >
82
+ <ScrollView className={selectOptionListClassName}>
83
+ <View accessibilityRole="menu">
84
+ {options.map((option) => (
85
+ <SelectItem
86
+ key={option.value}
87
+ label={option.label}
88
+ isSelected={values.includes(option.value)}
89
+ isDisabled={option.isDisabled}
90
+ onPress={() =>
91
+ onValuesChange(
92
+ values.includes(option.value)
93
+ ? values.filter((selected) => selected !== option.value)
94
+ : [...values, option.value],
95
+ )
96
+ }
97
+ />
98
+ ))}
99
+ </View>
100
+ </ScrollView>
101
+ </Sheet>
102
+ </>
103
+ );
104
+ }
@@ -0,0 +1,95 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { Animated, Easing, View, type ViewProps } from "react-native";
3
+
4
+ import { cn } from "../../lib/cn";
5
+ import {
6
+ progressFillClassName,
7
+ progressTrackVariants,
8
+ type ProgressVariantProps,
9
+ } from "../../variants/progress-variants";
10
+ import { useReducedMotion } from "../skeleton/hooks/use-reduced-motion";
11
+ import {
12
+ clampProgress,
13
+ PROGRESS_INDETERMINATE_DURATION,
14
+ PROGRESS_INDETERMINATE_WIDTH_RATIO,
15
+ PROGRESS_MAX,
16
+ PROGRESS_MIN,
17
+ } from "./utils";
18
+
19
+ export interface ProgressProps extends Omit<ViewProps, "children"> {
20
+ value?: number;
21
+ isIndeterminate?: boolean;
22
+ size?: NonNullable<ProgressVariantProps["size"]>;
23
+ accessibilityLabel: string;
24
+ }
25
+
26
+ export function Progress({
27
+ value = 0,
28
+ isIndeterminate = false,
29
+ size = "md",
30
+ accessibilityLabel,
31
+ className,
32
+ ...viewProps
33
+ }: ProgressProps) {
34
+ const isReducedMotion = useReducedMotion();
35
+ const slide = useRef(new Animated.Value(0)).current;
36
+ const isAnimated = isIndeterminate && !isReducedMotion;
37
+
38
+ useEffect(() => {
39
+ if (!isAnimated) return;
40
+
41
+ slide.setValue(0);
42
+ const animation = Animated.loop(
43
+ Animated.timing(slide, {
44
+ toValue: 1,
45
+ duration: PROGRESS_INDETERMINATE_DURATION,
46
+ easing: Easing.linear,
47
+ useNativeDriver: true,
48
+ }),
49
+ );
50
+
51
+ animation.start();
52
+
53
+ return () => animation.stop();
54
+ }, [isAnimated, slide]);
55
+
56
+ const clampedValue = clampProgress(value);
57
+
58
+ return (
59
+ <View
60
+ accessibilityRole="progressbar"
61
+ accessibilityLabel={accessibilityLabel}
62
+ accessibilityValue={
63
+ isIndeterminate
64
+ ? undefined
65
+ : { min: PROGRESS_MIN, max: PROGRESS_MAX, now: clampedValue }
66
+ }
67
+ accessibilityState={{ busy: isIndeterminate }}
68
+ aria-busy={isIndeterminate}
69
+ aria-valuemin={isIndeterminate ? undefined : PROGRESS_MIN}
70
+ aria-valuemax={isIndeterminate ? undefined : PROGRESS_MAX}
71
+ aria-valuenow={isIndeterminate ? undefined : clampedValue}
72
+ className={cn(progressTrackVariants({ size }), className)}
73
+ {...viewProps}
74
+ >
75
+ {isIndeterminate ? (
76
+ <Animated.View
77
+ className={progressFillClassName}
78
+ style={{
79
+ width: `${PROGRESS_INDETERMINATE_WIDTH_RATIO * PROGRESS_MAX}%`,
80
+ transform: [
81
+ {
82
+ translateX: slide.interpolate({
83
+ inputRange: [0, 1],
84
+ outputRange: ["0%", `${PROGRESS_MAX / PROGRESS_INDETERMINATE_WIDTH_RATIO}%`],
85
+ }),
86
+ },
87
+ ],
88
+ }}
89
+ />
90
+ ) : (
91
+ <View className={progressFillClassName} style={{ width: `${clampedValue}%` }} />
92
+ )}
93
+ </View>
94
+ );
95
+ }
@@ -0,0 +1,10 @@
1
+ export const PROGRESS_MIN = 0;
2
+ export const PROGRESS_MAX = 100;
3
+ export const PROGRESS_INDETERMINATE_DURATION = 1200;
4
+ export const PROGRESS_INDETERMINATE_WIDTH_RATIO = 0.4;
5
+
6
+ export function clampProgress(value: number): number {
7
+ if (Number.isNaN(value)) return PROGRESS_MIN;
8
+
9
+ return Math.min(Math.max(value, PROGRESS_MIN), PROGRESS_MAX);
10
+ }
@@ -0,0 +1,27 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ export interface RadioGroupControl {
4
+ selectedValue: string | undefined;
5
+ isDisabled: boolean;
6
+ select: (value: string) => void;
7
+ registerValue: (value: string) => () => void;
8
+ selectAdjacent: (fromValue: string, direction: 1 | -1) => void;
9
+ }
10
+
11
+ const RadioGroupContext = createContext<RadioGroupControl | undefined>(undefined);
12
+
13
+ export function RadioGroupProvider({
14
+ control,
15
+ children,
16
+ }: {
17
+ control: RadioGroupControl;
18
+ children: React.ReactNode;
19
+ }) {
20
+ return <RadioGroupContext.Provider value={control}>{children}</RadioGroupContext.Provider>;
21
+ }
22
+
23
+ export function useRadioGroupControl(): RadioGroupControl | undefined {
24
+ return useContext(RadioGroupContext);
25
+ }
26
+
27
+ export { RadioGroupContext };