@vellira-ui/react-native 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,7 @@ This package contains iOS-inspired native components built with React Native `St
10
10
  - Checkbox
11
11
  - Input
12
12
  - FormField
13
+ - Radio
13
14
  - RadioGroup
14
15
  - Select
15
16
  - Dropdown
@@ -81,6 +82,44 @@ Use `description` for settings-style helper text when the checkbox is not
81
82
  wrapped in `FormField`. For checkbox rows without a visible label, provide
82
83
  `accessibilityLabel`.
83
84
 
85
+ ### Select Notes
86
+
87
+ Use `Select` for one form value from a compact list, `RadioGroup` for a few
88
+ visible choices, and `Dropdown` for action menus. Native `Select` commits the
89
+ picker draft only when the user presses `Done`; `Cancel` and the backdrop close
90
+ without changing the selected value.
91
+
92
+ ```tsx
93
+ import { Select } from '@vellira-ui/react-native';
94
+ import { useState } from 'react';
95
+
96
+ export function RoleSelect() {
97
+ const [role, setRole] = useState('editor');
98
+
99
+ return (
100
+ <Select
101
+ label='Role'
102
+ value={role}
103
+ onChange={setRole}
104
+ options={[
105
+ { label: 'Admin', value: 'admin' },
106
+ { label: 'Editor', value: 'editor' },
107
+ { label: 'Viewer', value: 'viewer' },
108
+ ]}
109
+ />
110
+ );
111
+ }
112
+ ```
113
+
114
+ ### FormField Notes
115
+
116
+ `FormField` is a presentational wrapper for custom native controls. Do not wrap
117
+ components that already render their own field chrome, such as `Input`, `Select`
118
+ or `RadioGroup`. The wrapped control should provide its own
119
+ `accessibilityLabel`, role, disabled/editable state and interaction behavior.
120
+ `FormField` only provides layout, text styling, required mark, disabled root
121
+ state and polite error announcement.
122
+
84
123
  ## Testing
85
124
 
86
125
  Run only native tests:
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export type { DropdownProps } from './components/Dropdown';
2
2
  export { Dropdown } from './components/Dropdown';
3
3
  export type { ModalProps } from './components/Modal';
4
4
  export { Modal } from './components/Modal';
5
- export type { RadioGroupProps, RadioOption } from './components/RadioGroup';
5
+ export type { RadioGroupProps } from './components/RadioGroup';
6
6
  export { RadioGroup } from './components/RadioGroup';
7
7
  export type { SelectOption, SelectProps } from './components/Select';
8
8
  export { Select } from './components/Select';
@@ -18,6 +18,8 @@ export type { CheckboxProps } from './primitives/Checkbox';
18
18
  export { Checkbox } from './primitives/Checkbox';
19
19
  export type { InputProps } from './primitives/Input';
20
20
  export { Input } from './primitives/Input';
21
+ export type { RadioProps } from './primitives/Radio';
22
+ export { Radio } from './primitives/Radio';
21
23
  export type { NativeThemeName, ThemeProviderProps } from './theme';
22
24
  export { nativeThemes, ThemeProvider, useTheme } from './theme';
23
25
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -60,9 +60,9 @@ function useTheme() {
60
60
 
61
61
  // src/theme/useThemeStyles.ts
62
62
  import { useMemo as useMemo2 } from "react";
63
- function useThemeStyles(createStyles24) {
63
+ function useThemeStyles(createStyles25) {
64
64
  const { theme } = useTheme();
65
- return useMemo2(() => createStyles24(theme), [createStyles24, theme]);
65
+ return useMemo2(() => createStyles25(theme), [createStyles25, theme]);
66
66
  }
67
67
 
68
68
  // src/components/Dropdown/Content/DropdownContent.styles.ts
@@ -763,8 +763,9 @@ var Modal2 = Object.assign(ModalRoot, {
763
763
  });
764
764
 
765
765
  // src/components/RadioGroup/RadioGroup.tsx
766
+ import { forwardRef } from "react";
766
767
  import { useControllableState as useControllableState2 } from "@vellira-ui/core";
767
- import { Pressable as Pressable6, Text as Text7, View as View11 } from "react-native";
768
+ import { View as View11 } from "react-native";
768
769
 
769
770
  // src/patterns/FormField/FormField.tsx
770
771
  import { Text as Text6, View as View10 } from "react-native";
@@ -802,6 +803,11 @@ var createStyles12 = (theme) => StyleSheet12.create({
802
803
  descriptionDisabled: {
803
804
  color: theme.components.formField.disabled.descriptionFg
804
805
  },
806
+ customLabel: {
807
+ flexDirection: "row",
808
+ alignItems: "center",
809
+ gap: theme.tokens.spacing[1]
810
+ },
805
811
  control: {
806
812
  width: "100%",
807
813
  minWidth: 0,
@@ -828,28 +834,47 @@ function FormField({
828
834
  controlStyle,
829
835
  labelStyle,
830
836
  descriptionStyle,
831
- errorStyle
837
+ errorStyle,
838
+ ...rest
832
839
  }) {
833
840
  const styles = useThemeStyles(createStyles12);
834
841
  return /* @__PURE__ */ jsxs7(
835
842
  View10,
836
843
  {
844
+ ...rest,
845
+ accessibilityState: disabled ? { disabled: true } : void 0,
837
846
  style: [styles.root, style],
838
- accessibilityState: {
839
- disabled
840
- },
841
847
  children: [
842
- label && /* @__PURE__ */ jsxs7(
848
+ label && (typeof label === "string" || typeof label === "number" ? /* @__PURE__ */ jsxs7(
843
849
  Text6,
844
850
  {
845
851
  style: [styles.label, disabled && styles.labelDisabled, labelStyle],
846
852
  children: [
847
853
  label,
848
- required && /* @__PURE__ */ jsx14(Text6, { style: styles.required, children: " *" })
854
+ required && /* @__PURE__ */ jsx14(
855
+ Text6,
856
+ {
857
+ style: styles.required,
858
+ accessible: false,
859
+ importantForAccessibility: "no",
860
+ children: " *"
861
+ }
862
+ )
849
863
  ]
850
864
  }
851
- ),
852
- description && /* @__PURE__ */ jsx14(
865
+ ) : /* @__PURE__ */ jsxs7(View10, { style: styles.customLabel, children: [
866
+ label,
867
+ required && /* @__PURE__ */ jsx14(
868
+ Text6,
869
+ {
870
+ style: styles.required,
871
+ accessible: false,
872
+ importantForAccessibility: "no",
873
+ children: "*"
874
+ }
875
+ )
876
+ ] })),
877
+ description && (typeof description === "string" || typeof description === "number" ? /* @__PURE__ */ jsx14(
853
878
  Text6,
854
879
  {
855
880
  style: [
@@ -859,17 +884,16 @@ function FormField({
859
884
  ],
860
885
  children: description
861
886
  }
862
- ),
887
+ ) : description),
863
888
  /* @__PURE__ */ jsx14(View10, { style: [styles.control, controlStyle], children }),
864
- error && /* @__PURE__ */ jsx14(
889
+ error && (typeof error === "string" || typeof error === "number" ? /* @__PURE__ */ jsx14(
865
890
  Text6,
866
891
  {
867
- style: [styles.error, errorStyle],
868
- accessibilityRole: "alert",
869
892
  accessibilityLiveRegion: "polite",
893
+ style: [styles.error, errorStyle],
870
894
  children: error
871
895
  }
872
- )
896
+ ) : /* @__PURE__ */ jsx14(View10, { accessibilityLiveRegion: "polite", children: error }))
873
897
  ]
874
898
  }
875
899
  );
@@ -878,180 +902,125 @@ function FormField({
878
902
  // src/components/RadioGroup/RadioGroup.styles.ts
879
903
  import { StyleSheet as StyleSheet13 } from "react-native";
880
904
  var createStyles13 = (theme) => StyleSheet13.create({
881
- group: {
905
+ items: {
882
906
  width: "100%",
883
907
  minWidth: 0,
884
908
  alignSelf: "stretch",
885
- gap: theme.tokens.spacing[2]
909
+ gap: theme.tokens.spacing[1]
886
910
  },
887
911
  horizontal: {
888
912
  flexDirection: "row",
889
913
  flexWrap: "wrap",
890
- gap: theme.tokens.spacing[4]
891
- },
892
- option: {
893
- minWidth: 0,
894
- alignItems: "center",
895
- flexDirection: "row",
896
- gap: theme.tokens.spacing[2]
897
- },
898
- optionDisabled: {
899
- opacity: 1
900
- },
901
- radio: {
902
- width: 16,
903
- height: 16,
904
- alignItems: "center",
905
- justifyContent: "center",
906
- borderColor: theme.components.radio.default.border,
907
- borderRadius: 999,
908
- borderWidth: 1,
909
- backgroundColor: theme.components.radio.default.bg
910
- },
911
- radioSelected: {
912
- borderColor: theme.components.radio.checked.default.border
913
- },
914
- radioDisabled: {
915
- borderColor: theme.components.radio.disabled.border,
916
- backgroundColor: theme.components.radio.disabled.bg
917
- },
918
- dot: {
919
- width: 12,
920
- height: 12,
921
- backgroundColor: theme.components.radio.checked.default.bg,
922
- borderRadius: 999
923
- },
924
- dotDisabled: {
925
- backgroundColor: theme.components.radio.disabled.fg
926
- },
927
- label: {
928
- minWidth: 0,
929
- color: theme.components.radio.default.fg,
930
- fontFamily: theme.tokens.typography.family.regular,
931
- fontSize: theme.tokens.typography.size.md,
932
- lineHeight: theme.tokens.typography.lineHeight.md
933
- },
934
- labelDisabled: {
935
- color: theme.components.radio.disabled.fg
936
- },
937
- labelSelected: {
938
- color: theme.components.radio.checked.default.fg
914
+ alignItems: "flex-start",
915
+ columnGap: theme.tokens.spacing[6],
916
+ rowGap: theme.tokens.spacing[4]
939
917
  }
940
918
  });
941
919
 
920
+ // src/components/RadioGroup/RadioGroupContext.tsx
921
+ import { createContext as createContext3, useContext as useContext3 } from "react";
922
+ var RadioGroupContext = createContext3(null);
923
+ var RadioGroupProvider = RadioGroupContext.Provider;
924
+ function useRadioGroupContext() {
925
+ return useContext3(RadioGroupContext);
926
+ }
927
+
942
928
  // src/components/RadioGroup/RadioGroup.tsx
943
- import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
944
- function RadioGroup({
945
- label,
946
- description,
947
- value,
948
- defaultValue = "",
949
- onChange,
950
- options,
951
- required = false,
952
- disabled = false,
953
- error,
954
- orientation = "vertical",
955
- style,
956
- optionStyle,
957
- labelStyle
958
- }) {
959
- const styles = useThemeStyles(createStyles13);
960
- const [selectedValue, setSelectedValue] = useControllableState2({
929
+ import { jsx as jsx15 } from "react/jsx-runtime";
930
+ var RadioGroup = forwardRef(
931
+ ({
961
932
  value,
962
- defaultValue,
963
- onChange
964
- });
965
- return /* @__PURE__ */ jsx15(
966
- FormField,
967
- {
968
- label,
969
- description,
970
- error,
971
- required,
972
- disabled,
973
- children: /* @__PURE__ */ jsx15(
974
- View11,
975
- {
976
- accessibilityRole: "radiogroup",
977
- accessibilityState: { disabled },
978
- style: [
979
- styles.group,
980
- orientation === "horizontal" && styles.horizontal,
981
- style
982
- ],
983
- children: options.map((option) => {
984
- const isSelected = selectedValue === option.value;
985
- const isDisabled = disabled || !!option.disabled;
986
- return /* @__PURE__ */ jsxs8(
987
- Pressable6,
933
+ defaultValue = "",
934
+ onValueChange,
935
+ disabled = false,
936
+ required = false,
937
+ size = "md",
938
+ orientation = "vertical",
939
+ label,
940
+ description,
941
+ error,
942
+ children,
943
+ style,
944
+ itemsStyle,
945
+ labelStyle,
946
+ descriptionStyle,
947
+ errorStyle,
948
+ accessibilityLabel,
949
+ accessibilityHint,
950
+ ...rest
951
+ }, ref) => {
952
+ const styles = useThemeStyles(createStyles13);
953
+ const [selectedValue, setSelectedValue] = useControllableState2({
954
+ value,
955
+ defaultValue,
956
+ onChange: onValueChange
957
+ });
958
+ const invalid = Boolean(error);
959
+ const resolvedAccessibilityLabel = accessibilityLabel ?? (typeof label === "string" ? label : void 0);
960
+ const resolvedAccessibilityHint = (accessibilityHint ?? [
961
+ typeof description === "string" ? description : void 0,
962
+ required ? "Required." : void 0,
963
+ typeof error === "string" ? error : void 0
964
+ ].filter(Boolean).join(" ")) || void 0;
965
+ return /* @__PURE__ */ jsx15(
966
+ FormField,
967
+ {
968
+ label,
969
+ description,
970
+ error,
971
+ required,
972
+ disabled,
973
+ labelStyle,
974
+ descriptionStyle,
975
+ errorStyle,
976
+ style,
977
+ children: /* @__PURE__ */ jsx15(
978
+ RadioGroupProvider,
979
+ {
980
+ value: {
981
+ value: selectedValue,
982
+ disabled,
983
+ required,
984
+ invalid,
985
+ size,
986
+ onValueChange: setSelectedValue
987
+ },
988
+ children: /* @__PURE__ */ jsx15(
989
+ View11,
988
990
  {
989
- disabled: isDisabled,
990
- accessibilityRole: "radio",
991
- accessibilityLabel: option.label,
991
+ ...rest,
992
+ ref,
993
+ accessibilityRole: "radiogroup",
994
+ accessibilityLabel: resolvedAccessibilityLabel,
995
+ accessibilityHint: resolvedAccessibilityHint,
992
996
  accessibilityState: {
993
- checked: isSelected,
994
- disabled: isDisabled
995
- },
996
- onPress: () => {
997
- if (isDisabled) return;
998
- setSelectedValue(option.value);
997
+ disabled
999
998
  },
1000
999
  style: [
1001
- styles.option,
1002
- isDisabled && styles.optionDisabled,
1003
- optionStyle
1000
+ styles.items,
1001
+ orientation === "horizontal" && styles.horizontal,
1002
+ itemsStyle
1004
1003
  ],
1005
- children: [
1006
- /* @__PURE__ */ jsx15(
1007
- View11,
1008
- {
1009
- style: [
1010
- styles.radio,
1011
- isSelected && styles.radioSelected,
1012
- isDisabled && styles.radioDisabled
1013
- ],
1014
- children: isSelected && /* @__PURE__ */ jsx15(
1015
- View11,
1016
- {
1017
- style: [styles.dot, isDisabled && styles.dotDisabled]
1018
- }
1019
- )
1020
- }
1021
- ),
1022
- /* @__PURE__ */ jsx15(
1023
- Text7,
1024
- {
1025
- style: [
1026
- styles.label,
1027
- isSelected && styles.labelSelected,
1028
- isDisabled && styles.labelDisabled,
1029
- labelStyle
1030
- ],
1031
- children: option.label
1032
- }
1033
- )
1034
- ]
1035
- },
1036
- option.value
1037
- );
1038
- })
1039
- }
1040
- )
1041
- }
1042
- );
1043
- }
1004
+ children
1005
+ }
1006
+ )
1007
+ }
1008
+ )
1009
+ }
1010
+ );
1011
+ }
1012
+ );
1044
1013
  RadioGroup.displayName = "RadioGroup";
1045
1014
 
1046
1015
  // src/components/Select/Select.tsx
1047
- import { useState as useState3 } from "react";
1016
+ import { useEffect as useEffect2, useState as useState3 } from "react";
1048
1017
  import { Picker } from "@react-native-picker/picker";
1049
1018
  import { useControllableState as useControllableState3 } from "@vellira-ui/core";
1050
- import { Modal as Modal3, Pressable as Pressable8, Text as Text9, View as View13 } from "react-native";
1019
+ import { Modal as Modal3, Pressable as Pressable7, Text as Text8, View as View13 } from "react-native";
1051
1020
 
1052
1021
  // src/components/Select/SelectTrigger/SelectTrigger.tsx
1053
1022
  import { ChevronDown as ChevronDown2 } from "@vellira-ui/icons";
1054
- import { Pressable as Pressable7, Text as Text8, View as View12 } from "react-native";
1023
+ import { Pressable as Pressable6, Text as Text7, View as View12 } from "react-native";
1055
1024
 
1056
1025
  // src/components/Select/SelectTrigger/SelectTrigger.styles.ts
1057
1026
  import { StyleSheet as StyleSheet14 } from "react-native";
@@ -1069,6 +1038,33 @@ var createStyles14 = (theme) => StyleSheet14.create({
1069
1038
  borderRadius: theme.tokens.radius.md,
1070
1039
  borderWidth: 1
1071
1040
  },
1041
+ sm: {
1042
+ minHeight: 36,
1043
+ paddingHorizontal: theme.tokens.spacing[3],
1044
+ paddingVertical: theme.tokens.spacing[2]
1045
+ },
1046
+ md: {
1047
+ minHeight: 44,
1048
+ paddingHorizontal: theme.tokens.spacing[4],
1049
+ paddingVertical: theme.tokens.spacing[3]
1050
+ },
1051
+ lg: {
1052
+ minHeight: 52,
1053
+ paddingHorizontal: theme.tokens.spacing[5],
1054
+ paddingVertical: theme.tokens.spacing[4]
1055
+ },
1056
+ textSm: {
1057
+ fontSize: theme.tokens.typography.size.sm,
1058
+ lineHeight: theme.tokens.typography.lineHeight.sm
1059
+ },
1060
+ textMd: {
1061
+ fontSize: theme.tokens.typography.size.md,
1062
+ lineHeight: theme.tokens.typography.lineHeight.md
1063
+ },
1064
+ textLg: {
1065
+ fontSize: theme.tokens.typography.size.lg,
1066
+ lineHeight: theme.tokens.typography.lineHeight.lg
1067
+ },
1072
1068
  triggerOpen: {
1073
1069
  borderColor: theme.components.select.trigger.focus.border
1074
1070
  },
@@ -1083,9 +1079,7 @@ var createStyles14 = (theme) => StyleSheet14.create({
1083
1079
  flex: 1,
1084
1080
  minWidth: 0,
1085
1081
  color: theme.components.select.trigger.default.fg,
1086
- fontFamily: theme.tokens.typography.family.regular,
1087
- fontSize: theme.tokens.typography.size.md,
1088
- lineHeight: theme.tokens.typography.lineHeight.md
1082
+ fontFamily: theme.tokens.typography.family.regular
1089
1083
  },
1090
1084
  textDisabled: {
1091
1085
  color: theme.components.select.trigger.disabled.fg
@@ -1106,27 +1100,36 @@ var createStyles14 = (theme) => StyleSheet14.create({
1106
1100
  });
1107
1101
 
1108
1102
  // src/components/Select/SelectTrigger/SelectTrigger.tsx
1109
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1103
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1110
1104
  function SelectTrigger({
1111
1105
  displayText,
1112
1106
  isPlaceholder,
1113
1107
  isOpen,
1108
+ size = "md",
1114
1109
  disabled = false,
1110
+ required = false,
1115
1111
  hasError = false,
1116
1112
  accessibilityLabel,
1113
+ accessibilityHint,
1117
1114
  triggerStyle,
1118
1115
  textStyle,
1119
1116
  onPress
1120
1117
  }) {
1121
1118
  const { theme } = useTheme();
1122
1119
  const styles = useThemeStyles(createStyles14);
1123
- return /* @__PURE__ */ jsxs9(
1124
- Pressable7,
1120
+ const textSizeStyle = {
1121
+ sm: styles.textSm,
1122
+ md: styles.textMd,
1123
+ lg: styles.textLg
1124
+ }[size];
1125
+ const resolvedAccessibilityHint = accessibilityHint ?? (hasError ? "Invalid selection. Opens a picker" : required ? "Required. Opens a picker" : "Opens a picker");
1126
+ return /* @__PURE__ */ jsxs8(
1127
+ Pressable6,
1125
1128
  {
1126
1129
  disabled,
1127
1130
  accessibilityRole: "button",
1128
1131
  accessibilityLabel,
1129
- accessibilityHint: "Opens a picker",
1132
+ accessibilityHint: resolvedAccessibilityHint,
1130
1133
  accessibilityState: {
1131
1134
  expanded: isOpen,
1132
1135
  disabled
@@ -1134,6 +1137,7 @@ function SelectTrigger({
1134
1137
  onPress,
1135
1138
  style: [
1136
1139
  styles.trigger,
1140
+ styles[size],
1137
1141
  isOpen && styles.triggerOpen,
1138
1142
  hasError && styles.triggerError,
1139
1143
  disabled && styles.triggerDisabled,
@@ -1141,11 +1145,12 @@ function SelectTrigger({
1141
1145
  ],
1142
1146
  children: [
1143
1147
  /* @__PURE__ */ jsx16(
1144
- Text8,
1148
+ Text7,
1145
1149
  {
1146
1150
  numberOfLines: 1,
1147
1151
  style: [
1148
1152
  styles.text,
1153
+ textSizeStyle,
1149
1154
  isPlaceholder && styles.placeholder,
1150
1155
  disabled && styles.textDisabled,
1151
1156
  textStyle
@@ -1194,6 +1199,7 @@ var createStyles15 = (theme) => StyleSheet15.create({
1194
1199
  borderTopLeftRadius: theme.tokens.radius.lg,
1195
1200
  borderTopRightRadius: theme.tokens.radius.lg,
1196
1201
  borderWidth: 1,
1202
+ borderBottomWidth: 0,
1197
1203
  overflow: "hidden"
1198
1204
  },
1199
1205
  toolbar: {
@@ -1206,19 +1212,31 @@ var createStyles15 = (theme) => StyleSheet15.create({
1206
1212
  borderBottomWidth: 1
1207
1213
  },
1208
1214
  title: {
1215
+ flex: 1,
1216
+ marginHorizontal: theme.tokens.spacing[3],
1209
1217
  color: theme.components.select.dropdown.fg,
1210
1218
  fontFamily: theme.tokens.typography.family.medium,
1211
- fontSize: theme.tokens.typography.size.md
1219
+ fontSize: theme.tokens.typography.size.md,
1220
+ lineHeight: theme.tokens.typography.lineHeight.md,
1221
+ textAlign: "center"
1222
+ },
1223
+ toolbarAction: {
1224
+ minWidth: 64,
1225
+ minHeight: 44,
1226
+ alignItems: "center",
1227
+ justifyContent: "center"
1212
1228
  },
1213
1229
  cancelText: {
1214
1230
  color: theme.components.select.trigger.placeholder.fg,
1215
1231
  fontFamily: theme.tokens.typography.family.medium,
1216
- fontSize: theme.tokens.typography.size.md
1232
+ fontSize: theme.tokens.typography.size.md,
1233
+ lineHeight: theme.tokens.typography.lineHeight.md
1217
1234
  },
1218
1235
  doneText: {
1219
1236
  color: theme.components.select.option.selected.bg,
1220
1237
  fontFamily: theme.tokens.typography.family.medium,
1221
- fontSize: theme.tokens.typography.size.md
1238
+ fontSize: theme.tokens.typography.size.md,
1239
+ lineHeight: theme.tokens.typography.lineHeight.md
1222
1240
  },
1223
1241
  picker: {
1224
1242
  width: "100%"
@@ -1226,7 +1244,7 @@ var createStyles15 = (theme) => StyleSheet15.create({
1226
1244
  });
1227
1245
 
1228
1246
  // src/components/Select/Select.tsx
1229
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1247
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1230
1248
  function Select({
1231
1249
  label,
1232
1250
  description,
@@ -1235,6 +1253,7 @@ function Select({
1235
1253
  onChange,
1236
1254
  options,
1237
1255
  placeholder = "Select...",
1256
+ size = "md",
1238
1257
  required = false,
1239
1258
  disabled = false,
1240
1259
  error,
@@ -1242,7 +1261,8 @@ function Select({
1242
1261
  triggerStyle,
1243
1262
  textStyle,
1244
1263
  pickerStyle,
1245
- accessibilityLabel
1264
+ accessibilityLabel,
1265
+ accessibilityHint
1246
1266
  }) {
1247
1267
  const { theme } = useTheme();
1248
1268
  const styles = useThemeStyles(createStyles15);
@@ -1254,6 +1274,12 @@ function Select({
1254
1274
  });
1255
1275
  const [draftValue, setDraftValue] = useState3(selectedValue);
1256
1276
  const selectedOption = options.find((o) => o.value === selectedValue);
1277
+ const hasError = !!error;
1278
+ useEffect2(() => {
1279
+ if (isOpen) {
1280
+ setDraftValue(selectedValue);
1281
+ }
1282
+ }, [isOpen, selectedValue]);
1257
1283
  const resolvedLabel = accessibilityLabel ?? label ?? selectedOption?.label ?? placeholder ?? "Select";
1258
1284
  const openPicker = () => {
1259
1285
  if (disabled) return;
@@ -1271,10 +1297,15 @@ function Select({
1271
1297
  setDraftValue(nextValue);
1272
1298
  };
1273
1299
  const confirmPicker = () => {
1300
+ const nextOption = options.find((option) => option.value === draftValue);
1301
+ if (!nextOption || nextOption.disabled) {
1302
+ setIsOpen(false);
1303
+ return;
1304
+ }
1274
1305
  setSelectedValue(draftValue);
1275
1306
  setIsOpen(false);
1276
1307
  };
1277
- return /* @__PURE__ */ jsxs10(
1308
+ return /* @__PURE__ */ jsxs9(
1278
1309
  FormField,
1279
1310
  {
1280
1311
  label,
@@ -1290,9 +1321,12 @@ function Select({
1290
1321
  displayText: selectedOption?.label ?? placeholder,
1291
1322
  isPlaceholder: !selectedOption,
1292
1323
  isOpen,
1324
+ size,
1293
1325
  disabled,
1294
- hasError: !!error,
1326
+ required,
1327
+ hasError,
1295
1328
  accessibilityLabel: resolvedLabel,
1329
+ accessibilityHint,
1296
1330
  triggerStyle,
1297
1331
  textStyle,
1298
1332
  onPress: openPicker
@@ -1305,15 +1339,54 @@ function Select({
1305
1339
  visible: isOpen,
1306
1340
  animationType: "slide",
1307
1341
  onRequestClose: closePicker,
1308
- children: /* @__PURE__ */ jsxs10(View13, { style: styles.modalRoot, children: [
1309
- /* @__PURE__ */ jsx17(Pressable8, { style: styles.backdrop, onPress: closePicker }),
1310
- /* @__PURE__ */ jsxs10(View13, { style: styles.sheet, children: [
1311
- /* @__PURE__ */ jsxs10(View13, { style: styles.toolbar, children: [
1312
- /* @__PURE__ */ jsx17(Pressable8, { onPress: closePicker, hitSlop: 8, children: /* @__PURE__ */ jsx17(Text9, { style: styles.cancelText, children: "Cancel" }) }),
1313
- /* @__PURE__ */ jsx17(Text9, { style: styles.title, children: resolvedLabel }),
1314
- /* @__PURE__ */ jsx17(Pressable8, { onPress: confirmPicker, hitSlop: 8, children: /* @__PURE__ */ jsx17(Text9, { style: styles.doneText, children: "Done" }) })
1315
- ] }),
1316
- /* @__PURE__ */ jsxs10(
1342
+ children: /* @__PURE__ */ jsxs9(View13, { style: styles.modalRoot, children: [
1343
+ /* @__PURE__ */ jsx17(Pressable7, { style: styles.backdrop, onPress: closePicker }),
1344
+ /* @__PURE__ */ jsxs9(View13, { style: styles.sheet, children: [
1345
+ /* @__PURE__ */ jsxs9(
1346
+ View13,
1347
+ {
1348
+ style: styles.toolbar,
1349
+ accessible: true,
1350
+ accessibilityRole: "toolbar",
1351
+ accessibilityLabel: `${resolvedLabel} picker actions`,
1352
+ children: [
1353
+ /* @__PURE__ */ jsx17(
1354
+ Pressable7,
1355
+ {
1356
+ onPress: closePicker,
1357
+ hitSlop: 8,
1358
+ style: styles.toolbarAction,
1359
+ accessibilityRole: "button",
1360
+ accessibilityLabel: "Cancel selection",
1361
+ accessibilityHint: "Closes the picker without changing the selected value",
1362
+ children: /* @__PURE__ */ jsx17(Text8, { style: styles.cancelText, children: "Cancel" })
1363
+ }
1364
+ ),
1365
+ /* @__PURE__ */ jsx17(
1366
+ Text8,
1367
+ {
1368
+ style: styles.title,
1369
+ numberOfLines: 1,
1370
+ accessibilityRole: "header",
1371
+ children: resolvedLabel
1372
+ }
1373
+ ),
1374
+ /* @__PURE__ */ jsx17(
1375
+ Pressable7,
1376
+ {
1377
+ onPress: confirmPicker,
1378
+ hitSlop: 8,
1379
+ style: styles.toolbarAction,
1380
+ accessibilityRole: "button",
1381
+ accessibilityLabel: "Confirm selection",
1382
+ accessibilityHint: "Applies the highlighted picker value",
1383
+ children: /* @__PURE__ */ jsx17(Text8, { style: styles.doneText, children: "Done" })
1384
+ }
1385
+ )
1386
+ ]
1387
+ }
1388
+ ),
1389
+ /* @__PURE__ */ jsxs9(
1317
1390
  Picker,
1318
1391
  {
1319
1392
  selectedValue: draftValue,
@@ -1360,11 +1433,11 @@ Select.displayName = "Select";
1360
1433
  import { View as View14 } from "react-native";
1361
1434
 
1362
1435
  // src/components/Tabs/TabsContext.tsx
1363
- import { createContext as createContext3, useContext as useContext3 } from "react";
1364
- var TabsContext = createContext3(null);
1436
+ import { createContext as createContext4, useContext as useContext4 } from "react";
1437
+ var TabsContext = createContext4(null);
1365
1438
  var TabsProvider = TabsContext.Provider;
1366
1439
  var useTabs = () => {
1367
- const context = useContext3(TabsContext);
1440
+ const context = useContext4(TabsContext);
1368
1441
  if (!context) {
1369
1442
  throw new Error("Tabs components must be used inside <Tabs>.");
1370
1443
  }
@@ -1441,7 +1514,7 @@ TabsPanel.displayName = "TabsPanel";
1441
1514
 
1442
1515
  // src/components/Tabs/Tab/Tab.tsx
1443
1516
  import { cloneElement as cloneElement3, isValidElement as isValidElement3 } from "react";
1444
- import { Pressable as Pressable9, Text as Text10, View as View16 } from "react-native";
1517
+ import { Pressable as Pressable8, Text as Text9, View as View16 } from "react-native";
1445
1518
 
1446
1519
  // src/components/Tabs/Tab/Tab.styles.ts
1447
1520
  import { StyleSheet as StyleSheet18 } from "react-native";
@@ -1511,7 +1584,7 @@ var createStyles18 = (theme) => StyleSheet18.create({
1511
1584
  });
1512
1585
 
1513
1586
  // src/components/Tabs/Tab/Tab.tsx
1514
- import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
1587
+ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1515
1588
  var Tab = ({
1516
1589
  index,
1517
1590
  children,
@@ -1531,8 +1604,8 @@ var Tab = ({
1531
1604
  const renderedIcon = isValidElement3(icon) ? cloneElement3(icon, {
1532
1605
  color: iconColor
1533
1606
  }) : icon;
1534
- return /* @__PURE__ */ jsxs11(
1535
- Pressable9,
1607
+ return /* @__PURE__ */ jsxs10(
1608
+ Pressable8,
1536
1609
  {
1537
1610
  disabled,
1538
1611
  accessibilityRole: "tab",
@@ -1551,7 +1624,7 @@ var Tab = ({
1551
1624
  children: [
1552
1625
  icon != null && /* @__PURE__ */ jsx20(View16, { style: styles.tabIcon, children: renderedIcon }),
1553
1626
  children != null && /* @__PURE__ */ jsx20(
1554
- Text10,
1627
+ Text9,
1555
1628
  {
1556
1629
  style: [
1557
1630
  styles.tabText,
@@ -1651,8 +1724,8 @@ var Tabs = Object.assign(TabsRoot, {
1651
1724
  });
1652
1725
 
1653
1726
  // src/components/Tooltip/Tooltip.tsx
1654
- import { useEffect as useEffect2, useRef as useRef3, useState as useState6 } from "react";
1655
- import { Modal as Modal4, Pressable as Pressable10, Text as Text11, View as View18 } from "react-native";
1727
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
1728
+ import { Modal as Modal4, Pressable as Pressable9, Text as Text10, View as View18 } from "react-native";
1656
1729
 
1657
1730
  // src/hooks/useNativeFloatingPosition.ts
1658
1731
  import { useCallback as useCallback2, useRef as useRef2, useState as useState5 } from "react";
@@ -1757,7 +1830,7 @@ var createStyles20 = (theme) => StyleSheet20.create({
1757
1830
  });
1758
1831
 
1759
1832
  // src/components/Tooltip/Tooltip.tsx
1760
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
1833
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
1761
1834
  function Tooltip({
1762
1835
  children,
1763
1836
  content,
@@ -1791,13 +1864,13 @@ function Tooltip({
1791
1864
  closeTimerRef.current = null;
1792
1865
  }, hideDelay);
1793
1866
  };
1794
- useEffect2(() => {
1867
+ useEffect3(() => {
1795
1868
  return clearCloseTimer;
1796
1869
  }, []);
1797
- return /* @__PURE__ */ jsxs12(View18, { style: [styles.root, style], children: [
1798
- /* @__PURE__ */ jsx22(Pressable10, { ref: triggerRef, onLongPress: showTooltip, children }),
1870
+ return /* @__PURE__ */ jsxs11(View18, { style: [styles.root, style], children: [
1871
+ /* @__PURE__ */ jsx22(Pressable9, { ref: triggerRef, onLongPress: showTooltip, children }),
1799
1872
  /* @__PURE__ */ jsx22(Modal4, { visible: visible && !disabled, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx22(
1800
- Pressable10,
1873
+ Pressable9,
1801
1874
  {
1802
1875
  style: styles.overlay,
1803
1876
  onPress: () => {
@@ -1818,7 +1891,7 @@ function Tooltip({
1818
1891
  contentStyle
1819
1892
  ],
1820
1893
  onLayout: onFloatingLayout,
1821
- children: typeof content === "string" ? /* @__PURE__ */ jsx22(Text11, { style: [styles.text, textStyle], children: content }) : content
1894
+ children: typeof content === "string" ? /* @__PURE__ */ jsx22(Text10, { style: [styles.text, textStyle], children: content }) : content
1822
1895
  }
1823
1896
  )
1824
1897
  }
@@ -1829,7 +1902,7 @@ Tooltip.displayName = "Tooltip";
1829
1902
 
1830
1903
  // src/primitives/Button/Button.tsx
1831
1904
  import { cloneElement as cloneElement4, useState as useState7 } from "react";
1832
- import { ActivityIndicator, Pressable as Pressable11, Text as Text12 } from "react-native";
1905
+ import { ActivityIndicator, Pressable as Pressable10, Text as Text11 } from "react-native";
1833
1906
 
1834
1907
  // src/utils/devWarning.ts
1835
1908
  var devWarning = (condition, message) => {
@@ -1884,7 +1957,7 @@ var createStyles21 = (theme) => StyleSheet21.create({
1884
1957
  });
1885
1958
 
1886
1959
  // src/primitives/Button/Button.tsx
1887
- import { Fragment, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1960
+ import { Fragment, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
1888
1961
  var sizeMap = {
1889
1962
  sm: {
1890
1963
  px: 12,
@@ -1968,7 +2041,7 @@ function Button({
1968
2041
  });
1969
2042
  const getInteractionTheme = (pressed) => isDisabled ? theme.components.button.disabled : pressed ? variantTheme.pressed : isHovered ? variantTheme.hover : variantTheme.default;
1970
2043
  return /* @__PURE__ */ jsx23(
1971
- Pressable11,
2044
+ Pressable10,
1972
2045
  {
1973
2046
  ...props,
1974
2047
  testID,
@@ -2003,11 +2076,11 @@ function Button({
2003
2076
  children: ({ pressed }) => {
2004
2077
  const interactionTheme = getInteractionTheme(pressed);
2005
2078
  const contentColor = interactionTheme.fg;
2006
- return /* @__PURE__ */ jsxs13(Fragment, { children: [
2079
+ return /* @__PURE__ */ jsxs12(Fragment, { children: [
2007
2080
  loading && /* @__PURE__ */ jsx23(ActivityIndicator, { size: "small", color: contentColor }),
2008
2081
  !loading && leftIcon && renderIcon(leftIcon, contentColor),
2009
2082
  content && !iconOnly && /* @__PURE__ */ jsx23(
2010
- Text12,
2083
+ Text11,
2011
2084
  {
2012
2085
  style: [
2013
2086
  styles.text,
@@ -2028,10 +2101,10 @@ function Button({
2028
2101
  }
2029
2102
 
2030
2103
  // src/primitives/Checkbox/Checkbox.tsx
2031
- import { forwardRef } from "react";
2104
+ import { forwardRef as forwardRef2 } from "react";
2032
2105
  import { useControllableState as useControllableState4 } from "@vellira-ui/core";
2033
2106
  import { Check } from "@vellira-ui/icons";
2034
- import { Pressable as Pressable12, Text as Text13, View as View19 } from "react-native";
2107
+ import { Pressable as Pressable11, Text as Text12, View as View19 } from "react-native";
2035
2108
 
2036
2109
  // src/primitives/Checkbox/Checkbox.styles.ts
2037
2110
  import { StyleSheet as StyleSheet22 } from "react-native";
@@ -2143,13 +2216,13 @@ var createStyles22 = (theme) => StyleSheet22.create({
2143
2216
  });
2144
2217
 
2145
2218
  // src/primitives/Checkbox/Checkbox.tsx
2146
- import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
2219
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
2147
2220
  var iconSizeBySize = {
2148
2221
  sm: 10,
2149
2222
  md: 12,
2150
2223
  lg: 14
2151
2224
  };
2152
- var Checkbox = forwardRef(
2225
+ var Checkbox = forwardRef2(
2153
2226
  ({
2154
2227
  label,
2155
2228
  description,
@@ -2201,9 +2274,9 @@ var Checkbox = forwardRef(
2201
2274
  Boolean(resolvedAccessibilityLabel),
2202
2275
  "Checkbox: an accessible label must be provided through label or accessibilityLabel."
2203
2276
  );
2204
- return /* @__PURE__ */ jsxs14(View19, { style: styles.container, children: [
2205
- /* @__PURE__ */ jsxs14(
2206
- Pressable12,
2277
+ return /* @__PURE__ */ jsxs13(View19, { style: styles.container, children: [
2278
+ /* @__PURE__ */ jsxs13(
2279
+ Pressable11,
2207
2280
  {
2208
2281
  ...PressableProps,
2209
2282
  ref,
@@ -2236,8 +2309,8 @@ var Checkbox = forwardRef(
2236
2309
  children: indeterminate ? /* @__PURE__ */ jsx24(View19, { style: styles.indeterminateMark }) : isChecked && /* @__PURE__ */ jsx24(Check, { size: iconSizeBySize[size], color: checkColor })
2237
2310
  }
2238
2311
  ),
2239
- label && /* @__PURE__ */ jsxs14(
2240
- Text13,
2312
+ label && /* @__PURE__ */ jsxs13(
2313
+ Text12,
2241
2314
  {
2242
2315
  style: [
2243
2316
  styles.label,
@@ -2246,7 +2319,7 @@ var Checkbox = forwardRef(
2246
2319
  ],
2247
2320
  children: [
2248
2321
  label,
2249
- required && /* @__PURE__ */ jsx24(Text13, { style: styles.requiredMark, children: " *" })
2322
+ required && /* @__PURE__ */ jsx24(Text12, { style: styles.requiredMark, children: " *" })
2250
2323
  ]
2251
2324
  }
2252
2325
  )
@@ -2254,7 +2327,7 @@ var Checkbox = forwardRef(
2254
2327
  }
2255
2328
  ),
2256
2329
  description && /* @__PURE__ */ jsx24(
2257
- Text13,
2330
+ Text12,
2258
2331
  {
2259
2332
  style: [
2260
2333
  styles.descriptionText,
@@ -2264,15 +2337,15 @@ var Checkbox = forwardRef(
2264
2337
  children: description
2265
2338
  }
2266
2339
  ),
2267
- hasError && /* @__PURE__ */ jsx24(Text13, { style: [styles.errorText, helperTextSizeStyle[size]], children: error })
2340
+ hasError && /* @__PURE__ */ jsx24(Text12, { style: [styles.errorText, helperTextSizeStyle[size]], children: error })
2268
2341
  ] });
2269
2342
  }
2270
2343
  );
2271
2344
  Checkbox.displayName = "Checkbox";
2272
2345
 
2273
2346
  // src/primitives/Input/Input.tsx
2274
- import { cloneElement as cloneElement5, forwardRef as forwardRef2, useState as useState8 } from "react";
2275
- import { Pressable as Pressable13, Text as Text14, TextInput, View as View20 } from "react-native";
2347
+ import { cloneElement as cloneElement5, forwardRef as forwardRef3, useState as useState8 } from "react";
2348
+ import { Pressable as Pressable12, Text as Text13, TextInput, View as View20 } from "react-native";
2276
2349
 
2277
2350
  // src/primitives/Input/Input.styles.ts
2278
2351
  import { StyleSheet as StyleSheet23 } from "react-native";
@@ -2385,7 +2458,7 @@ var createStyles23 = (theme) => StyleSheet23.create({
2385
2458
  });
2386
2459
 
2387
2460
  // src/primitives/Input/Input.tsx
2388
- import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2461
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
2389
2462
  var keyboardTypeByInputType = {
2390
2463
  text: "default",
2391
2464
  email: "email-address",
@@ -2413,7 +2486,7 @@ var autoCorrectByInputType = {
2413
2486
  url: false,
2414
2487
  search: false
2415
2488
  };
2416
- var Input = forwardRef2(
2489
+ var Input = forwardRef3(
2417
2490
  ({
2418
2491
  label,
2419
2492
  description,
@@ -2497,7 +2570,7 @@ var Input = forwardRef2(
2497
2570
  required,
2498
2571
  disabled,
2499
2572
  style: containerStyle,
2500
- children: /* @__PURE__ */ jsxs15(View20, { style: styles.inputWrapper, children: [
2573
+ children: /* @__PURE__ */ jsxs14(View20, { style: styles.inputWrapper, children: [
2501
2574
  leftIcon && /* @__PURE__ */ jsx25(
2502
2575
  View20,
2503
2576
  {
@@ -2548,7 +2621,7 @@ var Input = forwardRef2(
2548
2621
  }
2549
2622
  ),
2550
2623
  showClearButton ? /* @__PURE__ */ jsx25(
2551
- Pressable13,
2624
+ Pressable12,
2552
2625
  {
2553
2626
  accessibilityRole: "button",
2554
2627
  accessibilityLabel: "Clear input",
@@ -2559,7 +2632,7 @@ var Input = forwardRef2(
2559
2632
  color: clearIconColor,
2560
2633
  size: resolvedIconSize
2561
2634
  }) : /* @__PURE__ */ jsx25(
2562
- Text14,
2635
+ Text13,
2563
2636
  {
2564
2637
  style: [styles.clearButtonText, { color: clearIconColor }],
2565
2638
  children: "\xD7"
@@ -2585,6 +2658,292 @@ var Input = forwardRef2(
2585
2658
  }
2586
2659
  );
2587
2660
  Input.displayName = "Input";
2661
+
2662
+ // src/primitives/Radio/Radio.tsx
2663
+ import { forwardRef as forwardRef4, useEffect as useEffect4 } from "react";
2664
+ import { useControllableState as useControllableState5 } from "@vellira-ui/core";
2665
+ import {
2666
+ Pressable as Pressable13,
2667
+ Text as Text14,
2668
+ View as View21
2669
+ } from "react-native";
2670
+
2671
+ // src/primitives/Radio/Radio.styles.ts
2672
+ import { StyleSheet as StyleSheet24 } from "react-native";
2673
+ var createStyles24 = (theme) => StyleSheet24.create({
2674
+ root: {
2675
+ alignSelf: "flex-start"
2676
+ },
2677
+ pressable: {
2678
+ minWidth: 32,
2679
+ minHeight: 32,
2680
+ flexDirection: "row",
2681
+ alignItems: "flex-start",
2682
+ gap: theme.tokens.spacing[2]
2683
+ },
2684
+ pressablePressed: {
2685
+ opacity: 0.8
2686
+ },
2687
+ pressableDisabled: {
2688
+ opacity: 1
2689
+ },
2690
+ control: {
2691
+ alignItems: "center",
2692
+ justifyContent: "center",
2693
+ borderWidth: 1,
2694
+ borderRadius: theme.tokens.radius.full,
2695
+ borderColor: theme.components.radio.default.border,
2696
+ backgroundColor: theme.components.radio.default.bg
2697
+ },
2698
+ controlChecked: {
2699
+ borderColor: theme.components.radio.checked.default.border,
2700
+ backgroundColor: theme.components.radio.default.bg
2701
+ },
2702
+ controlInvalid: {
2703
+ borderColor: theme.components.radio.invalid.border
2704
+ },
2705
+ controlDisabled: {
2706
+ borderColor: theme.components.radio.disabled.border,
2707
+ backgroundColor: theme.components.radio.disabled.bg
2708
+ },
2709
+ indicator: {
2710
+ borderRadius: theme.tokens.radius.full,
2711
+ backgroundColor: theme.components.radio.checked.default.bg
2712
+ },
2713
+ indicatorDisabled: {
2714
+ backgroundColor: theme.components.radio.checked.disabled.bg
2715
+ },
2716
+ content: {
2717
+ flexShrink: 1,
2718
+ gap: theme.tokens.spacing[1]
2719
+ },
2720
+ label: {
2721
+ color: theme.components.radio.default.fg,
2722
+ fontFamily: theme.tokens.typography.family.regular
2723
+ },
2724
+ labelChecked: {
2725
+ color: theme.components.radio.checked.default.fg
2726
+ },
2727
+ labelInvalid: {
2728
+ color: theme.components.radio.invalid.fg
2729
+ },
2730
+ description: {
2731
+ color: theme.components.formField.description.fg,
2732
+ fontFamily: theme.tokens.typography.family.regular
2733
+ },
2734
+ textDisabled: {
2735
+ color: theme.components.radio.disabled.fg
2736
+ },
2737
+ error: {
2738
+ color: theme.components.formField.helperText.error.fg,
2739
+ fontFamily: theme.tokens.typography.family.regular
2740
+ }
2741
+ });
2742
+
2743
+ // src/primitives/Radio/Radio.tsx
2744
+ import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
2745
+ var controlSizeBySize = {
2746
+ sm: 14,
2747
+ md: 16,
2748
+ lg: 20
2749
+ };
2750
+ var indicatorSizeBySize = {
2751
+ sm: 6,
2752
+ md: 8,
2753
+ lg: 10
2754
+ };
2755
+ var Radio = forwardRef4(
2756
+ ({
2757
+ value,
2758
+ checked,
2759
+ defaultChecked = false,
2760
+ disabled: disabledProp = false,
2761
+ required: requiredProp = false,
2762
+ size: sizeProp,
2763
+ onCheckedChange,
2764
+ label,
2765
+ description,
2766
+ error,
2767
+ accessibilityLabel,
2768
+ accessibilityHint,
2769
+ containerStyle,
2770
+ labelStyle,
2771
+ descriptionStyle,
2772
+ errorStyle,
2773
+ style,
2774
+ ...rest
2775
+ }, ref) => {
2776
+ const { theme } = useTheme();
2777
+ const styles = createStyles24(theme);
2778
+ const group = useRadioGroupContext();
2779
+ const isInsideGroup = group !== null;
2780
+ const [standaloneChecked, setStandaloneChecked] = useControllableState5({
2781
+ value: checked,
2782
+ defaultValue: defaultChecked,
2783
+ onChange: onCheckedChange
2784
+ });
2785
+ const resolvedChecked = isInsideGroup ? group.value === value : standaloneChecked;
2786
+ const resolvedDisabled = Boolean(group?.disabled) || disabledProp;
2787
+ const resolvedRequired = Boolean(group?.required) || requiredProp;
2788
+ const resolvedInvalid = Boolean(group?.invalid) || Boolean(error);
2789
+ const resolvedSize = sizeProp ?? group?.size ?? "md";
2790
+ const controlSize = controlSizeBySize[resolvedSize];
2791
+ const indicatorSize = indicatorSizeBySize[resolvedSize];
2792
+ const typographySizeByRadioSize = {
2793
+ sm: {
2794
+ labelSize: theme.tokens.typography.size.sm,
2795
+ labelLineHeight: theme.tokens.typography.lineHeight.sm,
2796
+ descriptionSize: theme.tokens.typography.size.xs,
2797
+ descriptionLineHeight: theme.tokens.typography.lineHeight.xs
2798
+ },
2799
+ md: {
2800
+ labelSize: theme.tokens.typography.size.md,
2801
+ labelLineHeight: theme.tokens.typography.lineHeight.md,
2802
+ descriptionSize: theme.tokens.typography.size.sm,
2803
+ descriptionLineHeight: theme.tokens.typography.lineHeight.sm
2804
+ },
2805
+ lg: {
2806
+ labelSize: theme.tokens.typography.size.lg,
2807
+ labelLineHeight: theme.tokens.typography.lineHeight.lg,
2808
+ descriptionSize: theme.tokens.typography.size.md,
2809
+ descriptionLineHeight: theme.tokens.typography.lineHeight.md
2810
+ }
2811
+ };
2812
+ const typographySize = typographySizeByRadioSize[resolvedSize];
2813
+ const controlMarginTop = (typographySize.labelLineHeight - controlSize) / 2;
2814
+ useEffect4(() => {
2815
+ if (typeof __DEV__ !== "undefined" && __DEV__ && !label && !accessibilityLabel) {
2816
+ console.warn(
2817
+ "Radio requires either a visible label or accessibilityLabel."
2818
+ );
2819
+ }
2820
+ }, [accessibilityLabel, label]);
2821
+ const resolvedAccessibilityLabel = accessibilityLabel ?? (typeof label === "string" ? label : void 0);
2822
+ const resolvedAccessibilityHint = (accessibilityHint ?? [
2823
+ typeof description === "string" ? description : void 0,
2824
+ resolvedRequired ? "Required." : void 0,
2825
+ error
2826
+ ].filter((item) => Boolean(item)).join(" ")) || void 0;
2827
+ const handlePress = () => {
2828
+ if (resolvedDisabled || resolvedChecked) {
2829
+ return;
2830
+ }
2831
+ if (isInsideGroup) {
2832
+ group.onValueChange(value);
2833
+ return;
2834
+ }
2835
+ setStandaloneChecked(true);
2836
+ };
2837
+ const resolvePressableStyle = (state) => [
2838
+ styles.pressable,
2839
+ resolvedDisabled && styles.pressableDisabled,
2840
+ state.pressed && !resolvedDisabled && styles.pressablePressed,
2841
+ typeof style === "function" ? style(state) : style
2842
+ ];
2843
+ return /* @__PURE__ */ jsxs15(View21, { style: [styles.root, containerStyle], children: [
2844
+ /* @__PURE__ */ jsxs15(
2845
+ Pressable13,
2846
+ {
2847
+ ...rest,
2848
+ ref,
2849
+ accessibilityRole: "radio",
2850
+ accessibilityLabel: resolvedAccessibilityLabel,
2851
+ accessibilityHint: resolvedAccessibilityHint,
2852
+ accessibilityState: {
2853
+ checked: resolvedChecked,
2854
+ disabled: resolvedDisabled
2855
+ },
2856
+ disabled: resolvedDisabled,
2857
+ onPress: handlePress,
2858
+ style: resolvePressableStyle,
2859
+ children: [
2860
+ /* @__PURE__ */ jsx26(
2861
+ View21,
2862
+ {
2863
+ pointerEvents: "none",
2864
+ style: [
2865
+ styles.control,
2866
+ {
2867
+ width: controlSize,
2868
+ height: controlSize,
2869
+ marginTop: controlMarginTop
2870
+ },
2871
+ resolvedChecked && styles.controlChecked,
2872
+ resolvedInvalid && styles.controlInvalid,
2873
+ resolvedDisabled && styles.controlDisabled
2874
+ ],
2875
+ children: resolvedChecked && /* @__PURE__ */ jsx26(
2876
+ View21,
2877
+ {
2878
+ style: [
2879
+ styles.indicator,
2880
+ {
2881
+ width: indicatorSize,
2882
+ height: indicatorSize
2883
+ },
2884
+ resolvedDisabled && styles.indicatorDisabled
2885
+ ]
2886
+ }
2887
+ )
2888
+ }
2889
+ ),
2890
+ (label || description) && /* @__PURE__ */ jsxs15(View21, { pointerEvents: "none", style: styles.content, children: [
2891
+ label && (typeof label === "string" ? /* @__PURE__ */ jsx26(
2892
+ Text14,
2893
+ {
2894
+ style: [
2895
+ styles.label,
2896
+ {
2897
+ fontSize: typographySize.labelSize,
2898
+ lineHeight: typographySize.labelLineHeight
2899
+ },
2900
+ resolvedChecked && styles.labelChecked,
2901
+ resolvedInvalid && styles.labelInvalid,
2902
+ resolvedDisabled && styles.textDisabled,
2903
+ labelStyle
2904
+ ],
2905
+ children: label
2906
+ }
2907
+ ) : label),
2908
+ description && (typeof description === "string" ? /* @__PURE__ */ jsx26(
2909
+ Text14,
2910
+ {
2911
+ style: [
2912
+ styles.description,
2913
+ {
2914
+ fontSize: typographySize.descriptionSize,
2915
+ lineHeight: typographySize.descriptionLineHeight
2916
+ },
2917
+ resolvedDisabled && styles.textDisabled,
2918
+ descriptionStyle
2919
+ ],
2920
+ children: description
2921
+ }
2922
+ ) : description)
2923
+ ] })
2924
+ ]
2925
+ }
2926
+ ),
2927
+ error && /* @__PURE__ */ jsx26(
2928
+ Text14,
2929
+ {
2930
+ accessibilityLiveRegion: "polite",
2931
+ style: [
2932
+ styles.error,
2933
+ {
2934
+ marginLeft: controlSize + theme.tokens.spacing[2],
2935
+ fontSize: typographySize.descriptionSize,
2936
+ lineHeight: typographySize.descriptionLineHeight
2937
+ },
2938
+ errorStyle
2939
+ ],
2940
+ children: error
2941
+ }
2942
+ )
2943
+ ] });
2944
+ }
2945
+ );
2946
+ Radio.displayName = "Radio";
2588
2947
  export {
2589
2948
  Button,
2590
2949
  Checkbox,
@@ -2592,6 +2951,7 @@ export {
2592
2951
  FormField,
2593
2952
  Input,
2594
2953
  Modal2 as Modal,
2954
+ Radio,
2595
2955
  RadioGroup,
2596
2956
  Select,
2597
2957
  Tabs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.23.0",
3
+ "version": "2.25.0",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",