@vellira-ui/react-native 2.22.5 → 2.24.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
@@ -42,11 +43,17 @@ import { View } from 'react-native';
42
43
 
43
44
  export function Example() {
44
45
  const [email, setEmail] = useState('');
46
+ const [accepted, setAccepted] = useState(false);
45
47
 
46
48
  return (
47
49
  <View style={{ gap: 16 }}>
48
50
  <Input label='Email' value={email} onChange={setEmail} />
49
- <Checkbox label='Accept terms' />
51
+ <Checkbox
52
+ label='Accept terms'
53
+ description='Required to create an account.'
54
+ checked={accepted}
55
+ onCheckedChange={setAccepted}
56
+ />
50
57
  <Button color='primary' variant='solid'>
51
58
  Continue
52
59
  </Button>
@@ -69,6 +76,21 @@ Button icons are React elements from `@vellira-ui/icons`; the component injects
69
76
  the current icon color and size. `loading` disables interaction and can replace
70
77
  the visible label with `loadingText`.
71
78
 
79
+ ### Checkbox Notes
80
+
81
+ Use `description` for settings-style helper text when the checkbox is not
82
+ wrapped in `FormField`. For checkbox rows without a visible label, provide
83
+ `accessibilityLabel`.
84
+
85
+ ### FormField Notes
86
+
87
+ `FormField` is a presentational wrapper for custom native controls. Do not wrap
88
+ components that already render their own field chrome, such as `Input`, `Select`
89
+ or `RadioGroup`. The wrapped control should provide its own
90
+ `accessibilityLabel`, role, disabled/editable state and interaction behavior.
91
+ `FormField` only provides layout, text styling, required mark, disabled root
92
+ state and polite error announcement.
93
+
72
94
  ## Testing
73
95
 
74
96
  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
1016
  import { 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";
@@ -1106,7 +1075,7 @@ var createStyles14 = (theme) => StyleSheet14.create({
1106
1075
  });
1107
1076
 
1108
1077
  // src/components/Select/SelectTrigger/SelectTrigger.tsx
1109
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1078
+ import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1110
1079
  function SelectTrigger({
1111
1080
  displayText,
1112
1081
  isPlaceholder,
@@ -1120,8 +1089,8 @@ function SelectTrigger({
1120
1089
  }) {
1121
1090
  const { theme } = useTheme();
1122
1091
  const styles = useThemeStyles(createStyles14);
1123
- return /* @__PURE__ */ jsxs9(
1124
- Pressable7,
1092
+ return /* @__PURE__ */ jsxs8(
1093
+ Pressable6,
1125
1094
  {
1126
1095
  disabled,
1127
1096
  accessibilityRole: "button",
@@ -1141,7 +1110,7 @@ function SelectTrigger({
1141
1110
  ],
1142
1111
  children: [
1143
1112
  /* @__PURE__ */ jsx16(
1144
- Text8,
1113
+ Text7,
1145
1114
  {
1146
1115
  numberOfLines: 1,
1147
1116
  style: [
@@ -1226,7 +1195,7 @@ var createStyles15 = (theme) => StyleSheet15.create({
1226
1195
  });
1227
1196
 
1228
1197
  // src/components/Select/Select.tsx
1229
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1198
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1230
1199
  function Select({
1231
1200
  label,
1232
1201
  description,
@@ -1274,7 +1243,7 @@ function Select({
1274
1243
  setSelectedValue(draftValue);
1275
1244
  setIsOpen(false);
1276
1245
  };
1277
- return /* @__PURE__ */ jsxs10(
1246
+ return /* @__PURE__ */ jsxs9(
1278
1247
  FormField,
1279
1248
  {
1280
1249
  label,
@@ -1305,15 +1274,15 @@ function Select({
1305
1274
  visible: isOpen,
1306
1275
  animationType: "slide",
1307
1276
  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" }) })
1277
+ children: /* @__PURE__ */ jsxs9(View13, { style: styles.modalRoot, children: [
1278
+ /* @__PURE__ */ jsx17(Pressable7, { style: styles.backdrop, onPress: closePicker }),
1279
+ /* @__PURE__ */ jsxs9(View13, { style: styles.sheet, children: [
1280
+ /* @__PURE__ */ jsxs9(View13, { style: styles.toolbar, children: [
1281
+ /* @__PURE__ */ jsx17(Pressable7, { onPress: closePicker, hitSlop: 8, children: /* @__PURE__ */ jsx17(Text8, { style: styles.cancelText, children: "Cancel" }) }),
1282
+ /* @__PURE__ */ jsx17(Text8, { style: styles.title, children: resolvedLabel }),
1283
+ /* @__PURE__ */ jsx17(Pressable7, { onPress: confirmPicker, hitSlop: 8, children: /* @__PURE__ */ jsx17(Text8, { style: styles.doneText, children: "Done" }) })
1315
1284
  ] }),
1316
- /* @__PURE__ */ jsxs10(
1285
+ /* @__PURE__ */ jsxs9(
1317
1286
  Picker,
1318
1287
  {
1319
1288
  selectedValue: draftValue,
@@ -1360,11 +1329,11 @@ Select.displayName = "Select";
1360
1329
  import { View as View14 } from "react-native";
1361
1330
 
1362
1331
  // src/components/Tabs/TabsContext.tsx
1363
- import { createContext as createContext3, useContext as useContext3 } from "react";
1364
- var TabsContext = createContext3(null);
1332
+ import { createContext as createContext4, useContext as useContext4 } from "react";
1333
+ var TabsContext = createContext4(null);
1365
1334
  var TabsProvider = TabsContext.Provider;
1366
1335
  var useTabs = () => {
1367
- const context = useContext3(TabsContext);
1336
+ const context = useContext4(TabsContext);
1368
1337
  if (!context) {
1369
1338
  throw new Error("Tabs components must be used inside <Tabs>.");
1370
1339
  }
@@ -1441,7 +1410,7 @@ TabsPanel.displayName = "TabsPanel";
1441
1410
 
1442
1411
  // src/components/Tabs/Tab/Tab.tsx
1443
1412
  import { cloneElement as cloneElement3, isValidElement as isValidElement3 } from "react";
1444
- import { Pressable as Pressable9, Text as Text10, View as View16 } from "react-native";
1413
+ import { Pressable as Pressable8, Text as Text9, View as View16 } from "react-native";
1445
1414
 
1446
1415
  // src/components/Tabs/Tab/Tab.styles.ts
1447
1416
  import { StyleSheet as StyleSheet18 } from "react-native";
@@ -1511,7 +1480,7 @@ var createStyles18 = (theme) => StyleSheet18.create({
1511
1480
  });
1512
1481
 
1513
1482
  // src/components/Tabs/Tab/Tab.tsx
1514
- import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
1483
+ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1515
1484
  var Tab = ({
1516
1485
  index,
1517
1486
  children,
@@ -1531,8 +1500,8 @@ var Tab = ({
1531
1500
  const renderedIcon = isValidElement3(icon) ? cloneElement3(icon, {
1532
1501
  color: iconColor
1533
1502
  }) : icon;
1534
- return /* @__PURE__ */ jsxs11(
1535
- Pressable9,
1503
+ return /* @__PURE__ */ jsxs10(
1504
+ Pressable8,
1536
1505
  {
1537
1506
  disabled,
1538
1507
  accessibilityRole: "tab",
@@ -1551,7 +1520,7 @@ var Tab = ({
1551
1520
  children: [
1552
1521
  icon != null && /* @__PURE__ */ jsx20(View16, { style: styles.tabIcon, children: renderedIcon }),
1553
1522
  children != null && /* @__PURE__ */ jsx20(
1554
- Text10,
1523
+ Text9,
1555
1524
  {
1556
1525
  style: [
1557
1526
  styles.tabText,
@@ -1652,7 +1621,7 @@ var Tabs = Object.assign(TabsRoot, {
1652
1621
 
1653
1622
  // src/components/Tooltip/Tooltip.tsx
1654
1623
  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";
1624
+ import { Modal as Modal4, Pressable as Pressable9, Text as Text10, View as View18 } from "react-native";
1656
1625
 
1657
1626
  // src/hooks/useNativeFloatingPosition.ts
1658
1627
  import { useCallback as useCallback2, useRef as useRef2, useState as useState5 } from "react";
@@ -1757,7 +1726,7 @@ var createStyles20 = (theme) => StyleSheet20.create({
1757
1726
  });
1758
1727
 
1759
1728
  // src/components/Tooltip/Tooltip.tsx
1760
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
1729
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
1761
1730
  function Tooltip({
1762
1731
  children,
1763
1732
  content,
@@ -1794,10 +1763,10 @@ function Tooltip({
1794
1763
  useEffect2(() => {
1795
1764
  return clearCloseTimer;
1796
1765
  }, []);
1797
- return /* @__PURE__ */ jsxs12(View18, { style: [styles.root, style], children: [
1798
- /* @__PURE__ */ jsx22(Pressable10, { ref: triggerRef, onLongPress: showTooltip, children }),
1766
+ return /* @__PURE__ */ jsxs11(View18, { style: [styles.root, style], children: [
1767
+ /* @__PURE__ */ jsx22(Pressable9, { ref: triggerRef, onLongPress: showTooltip, children }),
1799
1768
  /* @__PURE__ */ jsx22(Modal4, { visible: visible && !disabled, transparent: true, animationType: "fade", children: /* @__PURE__ */ jsx22(
1800
- Pressable10,
1769
+ Pressable9,
1801
1770
  {
1802
1771
  style: styles.overlay,
1803
1772
  onPress: () => {
@@ -1818,7 +1787,7 @@ function Tooltip({
1818
1787
  contentStyle
1819
1788
  ],
1820
1789
  onLayout: onFloatingLayout,
1821
- children: typeof content === "string" ? /* @__PURE__ */ jsx22(Text11, { style: [styles.text, textStyle], children: content }) : content
1790
+ children: typeof content === "string" ? /* @__PURE__ */ jsx22(Text10, { style: [styles.text, textStyle], children: content }) : content
1822
1791
  }
1823
1792
  )
1824
1793
  }
@@ -1829,7 +1798,7 @@ Tooltip.displayName = "Tooltip";
1829
1798
 
1830
1799
  // src/primitives/Button/Button.tsx
1831
1800
  import { cloneElement as cloneElement4, useState as useState7 } from "react";
1832
- import { ActivityIndicator, Pressable as Pressable11, Text as Text12 } from "react-native";
1801
+ import { ActivityIndicator, Pressable as Pressable10, Text as Text11 } from "react-native";
1833
1802
 
1834
1803
  // src/utils/devWarning.ts
1835
1804
  var devWarning = (condition, message) => {
@@ -1884,7 +1853,7 @@ var createStyles21 = (theme) => StyleSheet21.create({
1884
1853
  });
1885
1854
 
1886
1855
  // src/primitives/Button/Button.tsx
1887
- import { Fragment, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1856
+ import { Fragment, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
1888
1857
  var sizeMap = {
1889
1858
  sm: {
1890
1859
  px: 12,
@@ -1968,7 +1937,7 @@ function Button({
1968
1937
  });
1969
1938
  const getInteractionTheme = (pressed) => isDisabled ? theme.components.button.disabled : pressed ? variantTheme.pressed : isHovered ? variantTheme.hover : variantTheme.default;
1970
1939
  return /* @__PURE__ */ jsx23(
1971
- Pressable11,
1940
+ Pressable10,
1972
1941
  {
1973
1942
  ...props,
1974
1943
  testID,
@@ -2003,11 +1972,11 @@ function Button({
2003
1972
  children: ({ pressed }) => {
2004
1973
  const interactionTheme = getInteractionTheme(pressed);
2005
1974
  const contentColor = interactionTheme.fg;
2006
- return /* @__PURE__ */ jsxs13(Fragment, { children: [
1975
+ return /* @__PURE__ */ jsxs12(Fragment, { children: [
2007
1976
  loading && /* @__PURE__ */ jsx23(ActivityIndicator, { size: "small", color: contentColor }),
2008
1977
  !loading && leftIcon && renderIcon(leftIcon, contentColor),
2009
1978
  content && !iconOnly && /* @__PURE__ */ jsx23(
2010
- Text12,
1979
+ Text11,
2011
1980
  {
2012
1981
  style: [
2013
1982
  styles.text,
@@ -2028,31 +1997,53 @@ function Button({
2028
1997
  }
2029
1998
 
2030
1999
  // src/primitives/Checkbox/Checkbox.tsx
2031
- import { forwardRef } from "react";
2000
+ import { forwardRef as forwardRef2 } from "react";
2032
2001
  import { useControllableState as useControllableState4 } from "@vellira-ui/core";
2033
2002
  import { Check } from "@vellira-ui/icons";
2034
- import { Pressable as Pressable12, Text as Text13, View as View19 } from "react-native";
2003
+ import { Pressable as Pressable11, Text as Text12, View as View19 } from "react-native";
2035
2004
 
2036
2005
  // src/primitives/Checkbox/Checkbox.styles.ts
2037
2006
  import { StyleSheet as StyleSheet22 } from "react-native";
2038
2007
  var createStyles22 = (theme) => StyleSheet22.create({
2008
+ container: {
2009
+ gap: theme.tokens.spacing[2]
2010
+ },
2039
2011
  wrapper: {
2040
2012
  flexDirection: "row",
2041
2013
  alignItems: "center",
2042
2014
  gap: theme.tokens.spacing[3]
2043
2015
  },
2016
+ iconOnly: {
2017
+ justifyContent: "center",
2018
+ minWidth: 44,
2019
+ minHeight: 44
2020
+ },
2044
2021
  disabled: {
2045
2022
  opacity: 1
2046
2023
  },
2047
2024
  box: {
2048
- width: 22,
2049
- height: 22,
2050
- borderWidth: 2,
2051
- borderColor: theme.components.checkbox.default.border,
2052
- borderRadius: theme.tokens.radius.md,
2025
+ flexShrink: 0,
2053
2026
  alignItems: "center",
2054
2027
  justifyContent: "center",
2055
- backgroundColor: theme.components.checkbox.default.bg
2028
+ backgroundColor: theme.components.checkbox.default.bg,
2029
+ borderWidth: 2,
2030
+ borderColor: theme.components.checkbox.default.border,
2031
+ borderRadius: theme.tokens.radius.xs
2032
+ },
2033
+ boxSm: {
2034
+ width: 16,
2035
+ height: 16,
2036
+ borderWidth: 1
2037
+ },
2038
+ boxMd: {
2039
+ width: 20,
2040
+ height: 20,
2041
+ borderWidth: 2
2042
+ },
2043
+ boxLg: {
2044
+ width: 24,
2045
+ height: 24,
2046
+ borderWidth: 2
2056
2047
  },
2057
2048
  boxChecked: {
2058
2049
  backgroundColor: theme.components.checkbox.checked.default.bg,
@@ -2065,40 +2056,102 @@ var createStyles22 = (theme) => StyleSheet22.create({
2065
2056
  boxError: {
2066
2057
  borderColor: theme.components.checkbox.error.border
2067
2058
  },
2059
+ indeterminateMark: {
2060
+ width: "60%",
2061
+ height: 2,
2062
+ backgroundColor: theme.components.checkbox.checked.default.fg,
2063
+ borderRadius: theme.tokens.radius.full
2064
+ },
2068
2065
  label: {
2066
+ flexShrink: 1,
2069
2067
  fontFamily: theme.tokens.typography.family.regular,
2070
- fontSize: theme.tokens.typography.size.md,
2071
2068
  color: theme.components.checkbox.default.fg
2072
2069
  },
2070
+ labelSm: {
2071
+ fontSize: theme.tokens.typography.size.sm,
2072
+ lineHeight: theme.tokens.typography.lineHeight.sm
2073
+ },
2074
+ labelMd: {
2075
+ fontSize: theme.tokens.typography.size.md,
2076
+ lineHeight: theme.tokens.typography.lineHeight.md
2077
+ },
2078
+ labelLg: {
2079
+ fontSize: theme.tokens.typography.size.lg,
2080
+ lineHeight: theme.tokens.typography.lineHeight.lg
2081
+ },
2073
2082
  labelDisabled: {
2074
2083
  color: theme.components.checkbox.disabled.fg
2075
2084
  },
2076
- container: {
2077
- gap: theme.tokens.spacing[2]
2085
+ requiredMark: {
2086
+ color: theme.components.checkbox.error.fg
2087
+ },
2088
+ descriptionText: {
2089
+ color: theme.components.formField.description.fg,
2090
+ fontFamily: theme.tokens.typography.family.regular,
2091
+ fontSize: theme.tokens.typography.size.sm,
2092
+ lineHeight: theme.tokens.typography.lineHeight.sm
2093
+ },
2094
+ descriptionTextDisabled: {
2095
+ color: theme.components.formField.disabled.descriptionFg
2078
2096
  },
2079
2097
  errorText: {
2080
2098
  color: theme.components.checkbox.error.fg,
2099
+ fontFamily: theme.tokens.typography.family.regular,
2081
2100
  fontSize: theme.tokens.typography.size.sm,
2082
- lineHeight: theme.tokens.typography.lineHeight.sm,
2083
- marginLeft: 22 + theme.tokens.spacing[3]
2101
+ lineHeight: theme.tokens.typography.lineHeight.sm
2102
+ },
2103
+ errorTextSm: {
2104
+ marginLeft: 16 + theme.tokens.spacing[3]
2105
+ },
2106
+ errorTextMd: {
2107
+ marginLeft: 20 + theme.tokens.spacing[3]
2108
+ },
2109
+ errorTextLg: {
2110
+ marginLeft: 24 + theme.tokens.spacing[3]
2084
2111
  }
2085
2112
  });
2086
2113
 
2087
2114
  // src/primitives/Checkbox/Checkbox.tsx
2088
- import { jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
2089
- var Checkbox = forwardRef(
2115
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
2116
+ var iconSizeBySize = {
2117
+ sm: 10,
2118
+ md: 12,
2119
+ lg: 14
2120
+ };
2121
+ var Checkbox = forwardRef2(
2090
2122
  ({
2091
2123
  label,
2124
+ description,
2092
2125
  checked,
2093
2126
  defaultChecked = false,
2094
2127
  disabled = false,
2128
+ required = false,
2129
+ indeterminate = false,
2130
+ size = "md",
2095
2131
  onCheckedChange,
2096
2132
  error,
2097
2133
  style,
2098
- ...props
2134
+ accessibilityLabel,
2135
+ accessibilityHint,
2136
+ ...PressableProps
2099
2137
  }, ref) => {
2100
2138
  const { theme } = useTheme();
2101
2139
  const styles = useThemeStyles(createStyles22);
2140
+ const boxSizeStyle = {
2141
+ sm: styles.boxSm,
2142
+ md: styles.boxMd,
2143
+ lg: styles.boxLg
2144
+ };
2145
+ const labelSizeStyle = {
2146
+ sm: styles.labelSm,
2147
+ md: styles.labelMd,
2148
+ lg: styles.labelLg
2149
+ };
2150
+ const helperTextSizeStyle = {
2151
+ sm: styles.errorTextSm,
2152
+ md: styles.errorTextMd,
2153
+ lg: styles.errorTextLg
2154
+ };
2102
2155
  const hasError = Boolean(error);
2103
2156
  const [isChecked, setIsChecked] = useControllableState4({
2104
2157
  value: checked,
@@ -2107,51 +2160,88 @@ var Checkbox = forwardRef(
2107
2160
  });
2108
2161
  const handlePress = () => {
2109
2162
  if (disabled) return;
2110
- setIsChecked(!isChecked);
2163
+ setIsChecked(indeterminate ? true : !isChecked);
2111
2164
  };
2165
+ const resolvedAccessibilityLabel = accessibilityLabel ?? label;
2166
+ const resolvedAccessibilityHint = [accessibilityHint, description, error].filter(Boolean).join(" ");
2167
+ const accessibilityChecked = indeterminate ? "mixed" : isChecked;
2112
2168
  const checkColor = disabled ? theme.components.checkbox.disabled.fg : theme.components.checkbox.checked.default.fg;
2113
- return /* @__PURE__ */ jsxs14(View19, { style: styles.container, children: [
2114
- /* @__PURE__ */ jsxs14(
2115
- Pressable12,
2169
+ devWarning(
2170
+ Boolean(resolvedAccessibilityLabel),
2171
+ "Checkbox: an accessible label must be provided through label or accessibilityLabel."
2172
+ );
2173
+ return /* @__PURE__ */ jsxs13(View19, { style: styles.container, children: [
2174
+ /* @__PURE__ */ jsxs13(
2175
+ Pressable11,
2116
2176
  {
2177
+ ...PressableProps,
2117
2178
  ref,
2118
2179
  onPress: handlePress,
2119
2180
  disabled,
2120
2181
  accessibilityRole: "checkbox",
2121
2182
  accessibilityState: {
2122
- checked: isChecked,
2183
+ checked: accessibilityChecked,
2123
2184
  disabled
2124
2185
  },
2125
- accessibilityHint: hasError ? error : void 0,
2126
- accessibilityLabel: label ?? "Checkbox",
2127
- style: [styles.wrapper, disabled && styles.disabled, style],
2128
- ...props,
2186
+ accessibilityHint: resolvedAccessibilityHint || void 0,
2187
+ accessibilityLabel: resolvedAccessibilityLabel,
2188
+ style: [
2189
+ styles.wrapper,
2190
+ !label && styles.iconOnly,
2191
+ disabled && styles.disabled,
2192
+ style
2193
+ ],
2129
2194
  children: [
2130
2195
  /* @__PURE__ */ jsx24(
2131
2196
  View19,
2132
2197
  {
2133
2198
  style: [
2134
2199
  styles.box,
2135
- isChecked && styles.boxChecked,
2200
+ boxSizeStyle[size],
2201
+ (isChecked || indeterminate) && styles.boxChecked,
2136
2202
  hasError && styles.boxError,
2137
2203
  disabled && styles.boxDisabled
2138
2204
  ],
2139
- children: isChecked && /* @__PURE__ */ jsx24(Check, { size: 12, color: checkColor })
2205
+ children: indeterminate ? /* @__PURE__ */ jsx24(View19, { style: styles.indeterminateMark }) : isChecked && /* @__PURE__ */ jsx24(Check, { size: iconSizeBySize[size], color: checkColor })
2140
2206
  }
2141
2207
  ),
2142
- label && /* @__PURE__ */ jsx24(Text13, { style: [styles.label, disabled && styles.labelDisabled], children: label })
2208
+ label && /* @__PURE__ */ jsxs13(
2209
+ Text12,
2210
+ {
2211
+ style: [
2212
+ styles.label,
2213
+ labelSizeStyle[size],
2214
+ disabled && styles.labelDisabled
2215
+ ],
2216
+ children: [
2217
+ label,
2218
+ required && /* @__PURE__ */ jsx24(Text12, { style: styles.requiredMark, children: " *" })
2219
+ ]
2220
+ }
2221
+ )
2143
2222
  ]
2144
2223
  }
2145
2224
  ),
2146
- hasError && /* @__PURE__ */ jsx24(Text13, { style: styles.errorText, children: error })
2225
+ description && /* @__PURE__ */ jsx24(
2226
+ Text12,
2227
+ {
2228
+ style: [
2229
+ styles.descriptionText,
2230
+ helperTextSizeStyle[size],
2231
+ disabled && styles.descriptionTextDisabled
2232
+ ],
2233
+ children: description
2234
+ }
2235
+ ),
2236
+ hasError && /* @__PURE__ */ jsx24(Text12, { style: [styles.errorText, helperTextSizeStyle[size]], children: error })
2147
2237
  ] });
2148
2238
  }
2149
2239
  );
2150
2240
  Checkbox.displayName = "Checkbox";
2151
2241
 
2152
2242
  // src/primitives/Input/Input.tsx
2153
- import { cloneElement as cloneElement5, forwardRef as forwardRef2, useState as useState8 } from "react";
2154
- import { Pressable as Pressable13, Text as Text14, TextInput, View as View20 } from "react-native";
2243
+ import { cloneElement as cloneElement5, forwardRef as forwardRef3, useState as useState8 } from "react";
2244
+ import { Pressable as Pressable12, Text as Text13, TextInput, View as View20 } from "react-native";
2155
2245
 
2156
2246
  // src/primitives/Input/Input.styles.ts
2157
2247
  import { StyleSheet as StyleSheet23 } from "react-native";
@@ -2264,7 +2354,7 @@ var createStyles23 = (theme) => StyleSheet23.create({
2264
2354
  });
2265
2355
 
2266
2356
  // src/primitives/Input/Input.tsx
2267
- import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2357
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
2268
2358
  var keyboardTypeByInputType = {
2269
2359
  text: "default",
2270
2360
  email: "email-address",
@@ -2292,7 +2382,7 @@ var autoCorrectByInputType = {
2292
2382
  url: false,
2293
2383
  search: false
2294
2384
  };
2295
- var Input = forwardRef2(
2385
+ var Input = forwardRef3(
2296
2386
  ({
2297
2387
  label,
2298
2388
  description,
@@ -2376,7 +2466,7 @@ var Input = forwardRef2(
2376
2466
  required,
2377
2467
  disabled,
2378
2468
  style: containerStyle,
2379
- children: /* @__PURE__ */ jsxs15(View20, { style: styles.inputWrapper, children: [
2469
+ children: /* @__PURE__ */ jsxs14(View20, { style: styles.inputWrapper, children: [
2380
2470
  leftIcon && /* @__PURE__ */ jsx25(
2381
2471
  View20,
2382
2472
  {
@@ -2427,7 +2517,7 @@ var Input = forwardRef2(
2427
2517
  }
2428
2518
  ),
2429
2519
  showClearButton ? /* @__PURE__ */ jsx25(
2430
- Pressable13,
2520
+ Pressable12,
2431
2521
  {
2432
2522
  accessibilityRole: "button",
2433
2523
  accessibilityLabel: "Clear input",
@@ -2438,7 +2528,7 @@ var Input = forwardRef2(
2438
2528
  color: clearIconColor,
2439
2529
  size: resolvedIconSize
2440
2530
  }) : /* @__PURE__ */ jsx25(
2441
- Text14,
2531
+ Text13,
2442
2532
  {
2443
2533
  style: [styles.clearButtonText, { color: clearIconColor }],
2444
2534
  children: "\xD7"
@@ -2464,6 +2554,292 @@ var Input = forwardRef2(
2464
2554
  }
2465
2555
  );
2466
2556
  Input.displayName = "Input";
2557
+
2558
+ // src/primitives/Radio/Radio.tsx
2559
+ import { forwardRef as forwardRef4, useEffect as useEffect3 } from "react";
2560
+ import { useControllableState as useControllableState5 } from "@vellira-ui/core";
2561
+ import {
2562
+ Pressable as Pressable13,
2563
+ Text as Text14,
2564
+ View as View21
2565
+ } from "react-native";
2566
+
2567
+ // src/primitives/Radio/Radio.styles.ts
2568
+ import { StyleSheet as StyleSheet24 } from "react-native";
2569
+ var createStyles24 = (theme) => StyleSheet24.create({
2570
+ root: {
2571
+ alignSelf: "flex-start"
2572
+ },
2573
+ pressable: {
2574
+ minWidth: 32,
2575
+ minHeight: 32,
2576
+ flexDirection: "row",
2577
+ alignItems: "flex-start",
2578
+ gap: theme.tokens.spacing[2]
2579
+ },
2580
+ pressablePressed: {
2581
+ opacity: 0.8
2582
+ },
2583
+ pressableDisabled: {
2584
+ opacity: 1
2585
+ },
2586
+ control: {
2587
+ alignItems: "center",
2588
+ justifyContent: "center",
2589
+ borderWidth: 1,
2590
+ borderRadius: theme.tokens.radius.full,
2591
+ borderColor: theme.components.radio.default.border,
2592
+ backgroundColor: theme.components.radio.default.bg
2593
+ },
2594
+ controlChecked: {
2595
+ borderColor: theme.components.radio.checked.default.border,
2596
+ backgroundColor: theme.components.radio.default.bg
2597
+ },
2598
+ controlInvalid: {
2599
+ borderColor: theme.components.radio.invalid.border
2600
+ },
2601
+ controlDisabled: {
2602
+ borderColor: theme.components.radio.disabled.border,
2603
+ backgroundColor: theme.components.radio.disabled.bg
2604
+ },
2605
+ indicator: {
2606
+ borderRadius: theme.tokens.radius.full,
2607
+ backgroundColor: theme.components.radio.checked.default.bg
2608
+ },
2609
+ indicatorDisabled: {
2610
+ backgroundColor: theme.components.radio.checked.disabled.bg
2611
+ },
2612
+ content: {
2613
+ flexShrink: 1,
2614
+ gap: theme.tokens.spacing[1]
2615
+ },
2616
+ label: {
2617
+ color: theme.components.radio.default.fg,
2618
+ fontFamily: theme.tokens.typography.family.regular
2619
+ },
2620
+ labelChecked: {
2621
+ color: theme.components.radio.checked.default.fg
2622
+ },
2623
+ labelInvalid: {
2624
+ color: theme.components.radio.invalid.fg
2625
+ },
2626
+ description: {
2627
+ color: theme.components.formField.description.fg,
2628
+ fontFamily: theme.tokens.typography.family.regular
2629
+ },
2630
+ textDisabled: {
2631
+ color: theme.components.radio.disabled.fg
2632
+ },
2633
+ error: {
2634
+ color: theme.components.formField.helperText.error.fg,
2635
+ fontFamily: theme.tokens.typography.family.regular
2636
+ }
2637
+ });
2638
+
2639
+ // src/primitives/Radio/Radio.tsx
2640
+ import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
2641
+ var controlSizeBySize = {
2642
+ sm: 14,
2643
+ md: 16,
2644
+ lg: 20
2645
+ };
2646
+ var indicatorSizeBySize = {
2647
+ sm: 6,
2648
+ md: 8,
2649
+ lg: 10
2650
+ };
2651
+ var Radio = forwardRef4(
2652
+ ({
2653
+ value,
2654
+ checked,
2655
+ defaultChecked = false,
2656
+ disabled: disabledProp = false,
2657
+ required: requiredProp = false,
2658
+ size: sizeProp,
2659
+ onCheckedChange,
2660
+ label,
2661
+ description,
2662
+ error,
2663
+ accessibilityLabel,
2664
+ accessibilityHint,
2665
+ containerStyle,
2666
+ labelStyle,
2667
+ descriptionStyle,
2668
+ errorStyle,
2669
+ style,
2670
+ ...rest
2671
+ }, ref) => {
2672
+ const { theme } = useTheme();
2673
+ const styles = createStyles24(theme);
2674
+ const group = useRadioGroupContext();
2675
+ const isInsideGroup = group !== null;
2676
+ const [standaloneChecked, setStandaloneChecked] = useControllableState5({
2677
+ value: checked,
2678
+ defaultValue: defaultChecked,
2679
+ onChange: onCheckedChange
2680
+ });
2681
+ const resolvedChecked = isInsideGroup ? group.value === value : standaloneChecked;
2682
+ const resolvedDisabled = Boolean(group?.disabled) || disabledProp;
2683
+ const resolvedRequired = Boolean(group?.required) || requiredProp;
2684
+ const resolvedInvalid = Boolean(group?.invalid) || Boolean(error);
2685
+ const resolvedSize = sizeProp ?? group?.size ?? "md";
2686
+ const controlSize = controlSizeBySize[resolvedSize];
2687
+ const indicatorSize = indicatorSizeBySize[resolvedSize];
2688
+ const typographySizeByRadioSize = {
2689
+ sm: {
2690
+ labelSize: theme.tokens.typography.size.sm,
2691
+ labelLineHeight: theme.tokens.typography.lineHeight.sm,
2692
+ descriptionSize: theme.tokens.typography.size.xs,
2693
+ descriptionLineHeight: theme.tokens.typography.lineHeight.xs
2694
+ },
2695
+ md: {
2696
+ labelSize: theme.tokens.typography.size.md,
2697
+ labelLineHeight: theme.tokens.typography.lineHeight.md,
2698
+ descriptionSize: theme.tokens.typography.size.sm,
2699
+ descriptionLineHeight: theme.tokens.typography.lineHeight.sm
2700
+ },
2701
+ lg: {
2702
+ labelSize: theme.tokens.typography.size.lg,
2703
+ labelLineHeight: theme.tokens.typography.lineHeight.lg,
2704
+ descriptionSize: theme.tokens.typography.size.md,
2705
+ descriptionLineHeight: theme.tokens.typography.lineHeight.md
2706
+ }
2707
+ };
2708
+ const typographySize = typographySizeByRadioSize[resolvedSize];
2709
+ const controlMarginTop = (typographySize.labelLineHeight - controlSize) / 2;
2710
+ useEffect3(() => {
2711
+ if (typeof __DEV__ !== "undefined" && __DEV__ && !label && !accessibilityLabel) {
2712
+ console.warn(
2713
+ "Radio requires either a visible label or accessibilityLabel."
2714
+ );
2715
+ }
2716
+ }, [accessibilityLabel, label]);
2717
+ const resolvedAccessibilityLabel = accessibilityLabel ?? (typeof label === "string" ? label : void 0);
2718
+ const resolvedAccessibilityHint = (accessibilityHint ?? [
2719
+ typeof description === "string" ? description : void 0,
2720
+ resolvedRequired ? "Required." : void 0,
2721
+ error
2722
+ ].filter((item) => Boolean(item)).join(" ")) || void 0;
2723
+ const handlePress = () => {
2724
+ if (resolvedDisabled || resolvedChecked) {
2725
+ return;
2726
+ }
2727
+ if (isInsideGroup) {
2728
+ group.onValueChange(value);
2729
+ return;
2730
+ }
2731
+ setStandaloneChecked(true);
2732
+ };
2733
+ const resolvePressableStyle = (state) => [
2734
+ styles.pressable,
2735
+ resolvedDisabled && styles.pressableDisabled,
2736
+ state.pressed && !resolvedDisabled && styles.pressablePressed,
2737
+ typeof style === "function" ? style(state) : style
2738
+ ];
2739
+ return /* @__PURE__ */ jsxs15(View21, { style: [styles.root, containerStyle], children: [
2740
+ /* @__PURE__ */ jsxs15(
2741
+ Pressable13,
2742
+ {
2743
+ ...rest,
2744
+ ref,
2745
+ accessibilityRole: "radio",
2746
+ accessibilityLabel: resolvedAccessibilityLabel,
2747
+ accessibilityHint: resolvedAccessibilityHint,
2748
+ accessibilityState: {
2749
+ checked: resolvedChecked,
2750
+ disabled: resolvedDisabled
2751
+ },
2752
+ disabled: resolvedDisabled,
2753
+ onPress: handlePress,
2754
+ style: resolvePressableStyle,
2755
+ children: [
2756
+ /* @__PURE__ */ jsx26(
2757
+ View21,
2758
+ {
2759
+ pointerEvents: "none",
2760
+ style: [
2761
+ styles.control,
2762
+ {
2763
+ width: controlSize,
2764
+ height: controlSize,
2765
+ marginTop: controlMarginTop
2766
+ },
2767
+ resolvedChecked && styles.controlChecked,
2768
+ resolvedInvalid && styles.controlInvalid,
2769
+ resolvedDisabled && styles.controlDisabled
2770
+ ],
2771
+ children: resolvedChecked && /* @__PURE__ */ jsx26(
2772
+ View21,
2773
+ {
2774
+ style: [
2775
+ styles.indicator,
2776
+ {
2777
+ width: indicatorSize,
2778
+ height: indicatorSize
2779
+ },
2780
+ resolvedDisabled && styles.indicatorDisabled
2781
+ ]
2782
+ }
2783
+ )
2784
+ }
2785
+ ),
2786
+ (label || description) && /* @__PURE__ */ jsxs15(View21, { pointerEvents: "none", style: styles.content, children: [
2787
+ label && (typeof label === "string" ? /* @__PURE__ */ jsx26(
2788
+ Text14,
2789
+ {
2790
+ style: [
2791
+ styles.label,
2792
+ {
2793
+ fontSize: typographySize.labelSize,
2794
+ lineHeight: typographySize.labelLineHeight
2795
+ },
2796
+ resolvedChecked && styles.labelChecked,
2797
+ resolvedInvalid && styles.labelInvalid,
2798
+ resolvedDisabled && styles.textDisabled,
2799
+ labelStyle
2800
+ ],
2801
+ children: label
2802
+ }
2803
+ ) : label),
2804
+ description && (typeof description === "string" ? /* @__PURE__ */ jsx26(
2805
+ Text14,
2806
+ {
2807
+ style: [
2808
+ styles.description,
2809
+ {
2810
+ fontSize: typographySize.descriptionSize,
2811
+ lineHeight: typographySize.descriptionLineHeight
2812
+ },
2813
+ resolvedDisabled && styles.textDisabled,
2814
+ descriptionStyle
2815
+ ],
2816
+ children: description
2817
+ }
2818
+ ) : description)
2819
+ ] })
2820
+ ]
2821
+ }
2822
+ ),
2823
+ error && /* @__PURE__ */ jsx26(
2824
+ Text14,
2825
+ {
2826
+ accessibilityLiveRegion: "polite",
2827
+ style: [
2828
+ styles.error,
2829
+ {
2830
+ marginLeft: controlSize + theme.tokens.spacing[2],
2831
+ fontSize: typographySize.descriptionSize,
2832
+ lineHeight: typographySize.descriptionLineHeight
2833
+ },
2834
+ errorStyle
2835
+ ],
2836
+ children: error
2837
+ }
2838
+ )
2839
+ ] });
2840
+ }
2841
+ );
2842
+ Radio.displayName = "Radio";
2467
2843
  export {
2468
2844
  Button,
2469
2845
  Checkbox,
@@ -2471,6 +2847,7 @@ export {
2471
2847
  FormField,
2472
2848
  Input,
2473
2849
  Modal2 as Modal,
2850
+ Radio,
2474
2851
  RadioGroup,
2475
2852
  Select,
2476
2853
  Tabs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.22.5",
3
+ "version": "2.24.0",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",
@@ -49,12 +49,12 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/react": "^19.2.3",
52
- "@vitest/coverage-v8": "^4.1.9",
52
+ "@vitest/coverage-v8": "^4.1.10",
53
53
  "jsdom": "^28.1.0",
54
54
  "react": "19.2.7",
55
55
  "tsup": "^8.5.1",
56
56
  "typescript": "6.0.3",
57
- "vitest": "^4.1.9"
57
+ "vitest": "^4.1.10"
58
58
  },
59
59
  "publishConfig": {
60
60
  "access": "public"