@vellira-ui/react-native 2.24.0 → 2.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +29 -0
  2. package/dist/index.js +123 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -82,6 +82,35 @@ Use `description` for settings-style helper text when the checkbox is not
82
82
  wrapped in `FormField`. For checkbox rows without a visible label, provide
83
83
  `accessibilityLabel`.
84
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
+
85
114
  ### FormField Notes
86
115
 
87
116
  `FormField` is a presentational wrapper for custom native controls. Do not wrap
package/dist/index.js CHANGED
@@ -1013,7 +1013,7 @@ var RadioGroup = forwardRef(
1013
1013
  RadioGroup.displayName = "RadioGroup";
1014
1014
 
1015
1015
  // src/components/Select/Select.tsx
1016
- import { useState as useState3 } from "react";
1016
+ import { useEffect as useEffect2, useState as useState3 } from "react";
1017
1017
  import { Picker } from "@react-native-picker/picker";
1018
1018
  import { useControllableState as useControllableState3 } from "@vellira-ui/core";
1019
1019
  import { Modal as Modal3, Pressable as Pressable7, Text as Text8, View as View13 } from "react-native";
@@ -1038,6 +1038,33 @@ var createStyles14 = (theme) => StyleSheet14.create({
1038
1038
  borderRadius: theme.tokens.radius.md,
1039
1039
  borderWidth: 1
1040
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
+ },
1041
1068
  triggerOpen: {
1042
1069
  borderColor: theme.components.select.trigger.focus.border
1043
1070
  },
@@ -1052,9 +1079,7 @@ var createStyles14 = (theme) => StyleSheet14.create({
1052
1079
  flex: 1,
1053
1080
  minWidth: 0,
1054
1081
  color: theme.components.select.trigger.default.fg,
1055
- fontFamily: theme.tokens.typography.family.regular,
1056
- fontSize: theme.tokens.typography.size.md,
1057
- lineHeight: theme.tokens.typography.lineHeight.md
1082
+ fontFamily: theme.tokens.typography.family.regular
1058
1083
  },
1059
1084
  textDisabled: {
1060
1085
  color: theme.components.select.trigger.disabled.fg
@@ -1080,22 +1105,31 @@ function SelectTrigger({
1080
1105
  displayText,
1081
1106
  isPlaceholder,
1082
1107
  isOpen,
1108
+ size = "md",
1083
1109
  disabled = false,
1110
+ required = false,
1084
1111
  hasError = false,
1085
1112
  accessibilityLabel,
1113
+ accessibilityHint,
1086
1114
  triggerStyle,
1087
1115
  textStyle,
1088
1116
  onPress
1089
1117
  }) {
1090
1118
  const { theme } = useTheme();
1091
1119
  const styles = useThemeStyles(createStyles14);
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");
1092
1126
  return /* @__PURE__ */ jsxs8(
1093
1127
  Pressable6,
1094
1128
  {
1095
1129
  disabled,
1096
1130
  accessibilityRole: "button",
1097
1131
  accessibilityLabel,
1098
- accessibilityHint: "Opens a picker",
1132
+ accessibilityHint: resolvedAccessibilityHint,
1099
1133
  accessibilityState: {
1100
1134
  expanded: isOpen,
1101
1135
  disabled
@@ -1103,6 +1137,7 @@ function SelectTrigger({
1103
1137
  onPress,
1104
1138
  style: [
1105
1139
  styles.trigger,
1140
+ styles[size],
1106
1141
  isOpen && styles.triggerOpen,
1107
1142
  hasError && styles.triggerError,
1108
1143
  disabled && styles.triggerDisabled,
@@ -1115,6 +1150,7 @@ function SelectTrigger({
1115
1150
  numberOfLines: 1,
1116
1151
  style: [
1117
1152
  styles.text,
1153
+ textSizeStyle,
1118
1154
  isPlaceholder && styles.placeholder,
1119
1155
  disabled && styles.textDisabled,
1120
1156
  textStyle
@@ -1163,6 +1199,7 @@ var createStyles15 = (theme) => StyleSheet15.create({
1163
1199
  borderTopLeftRadius: theme.tokens.radius.lg,
1164
1200
  borderTopRightRadius: theme.tokens.radius.lg,
1165
1201
  borderWidth: 1,
1202
+ borderBottomWidth: 0,
1166
1203
  overflow: "hidden"
1167
1204
  },
1168
1205
  toolbar: {
@@ -1175,19 +1212,31 @@ var createStyles15 = (theme) => StyleSheet15.create({
1175
1212
  borderBottomWidth: 1
1176
1213
  },
1177
1214
  title: {
1215
+ flex: 1,
1216
+ marginHorizontal: theme.tokens.spacing[3],
1178
1217
  color: theme.components.select.dropdown.fg,
1179
1218
  fontFamily: theme.tokens.typography.family.medium,
1180
- 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"
1181
1228
  },
1182
1229
  cancelText: {
1183
1230
  color: theme.components.select.trigger.placeholder.fg,
1184
1231
  fontFamily: theme.tokens.typography.family.medium,
1185
- fontSize: theme.tokens.typography.size.md
1232
+ fontSize: theme.tokens.typography.size.md,
1233
+ lineHeight: theme.tokens.typography.lineHeight.md
1186
1234
  },
1187
1235
  doneText: {
1188
1236
  color: theme.components.select.option.selected.bg,
1189
1237
  fontFamily: theme.tokens.typography.family.medium,
1190
- fontSize: theme.tokens.typography.size.md
1238
+ fontSize: theme.tokens.typography.size.md,
1239
+ lineHeight: theme.tokens.typography.lineHeight.md
1191
1240
  },
1192
1241
  picker: {
1193
1242
  width: "100%"
@@ -1204,6 +1253,7 @@ function Select({
1204
1253
  onChange,
1205
1254
  options,
1206
1255
  placeholder = "Select...",
1256
+ size = "md",
1207
1257
  required = false,
1208
1258
  disabled = false,
1209
1259
  error,
@@ -1211,7 +1261,8 @@ function Select({
1211
1261
  triggerStyle,
1212
1262
  textStyle,
1213
1263
  pickerStyle,
1214
- accessibilityLabel
1264
+ accessibilityLabel,
1265
+ accessibilityHint
1215
1266
  }) {
1216
1267
  const { theme } = useTheme();
1217
1268
  const styles = useThemeStyles(createStyles15);
@@ -1223,6 +1274,12 @@ function Select({
1223
1274
  });
1224
1275
  const [draftValue, setDraftValue] = useState3(selectedValue);
1225
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]);
1226
1283
  const resolvedLabel = accessibilityLabel ?? label ?? selectedOption?.label ?? placeholder ?? "Select";
1227
1284
  const openPicker = () => {
1228
1285
  if (disabled) return;
@@ -1240,6 +1297,11 @@ function Select({
1240
1297
  setDraftValue(nextValue);
1241
1298
  };
1242
1299
  const confirmPicker = () => {
1300
+ const nextOption = options.find((option) => option.value === draftValue);
1301
+ if (!nextOption || nextOption.disabled) {
1302
+ setIsOpen(false);
1303
+ return;
1304
+ }
1243
1305
  setSelectedValue(draftValue);
1244
1306
  setIsOpen(false);
1245
1307
  };
@@ -1259,9 +1321,12 @@ function Select({
1259
1321
  displayText: selectedOption?.label ?? placeholder,
1260
1322
  isPlaceholder: !selectedOption,
1261
1323
  isOpen,
1324
+ size,
1262
1325
  disabled,
1263
- hasError: !!error,
1326
+ required,
1327
+ hasError,
1264
1328
  accessibilityLabel: resolvedLabel,
1329
+ accessibilityHint,
1265
1330
  triggerStyle,
1266
1331
  textStyle,
1267
1332
  onPress: openPicker
@@ -1277,11 +1342,50 @@ function Select({
1277
1342
  children: /* @__PURE__ */ jsxs9(View13, { style: styles.modalRoot, children: [
1278
1343
  /* @__PURE__ */ jsx17(Pressable7, { style: styles.backdrop, onPress: closePicker }),
1279
1344
  /* @__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" }) })
1284
- ] }),
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
+ ),
1285
1389
  /* @__PURE__ */ jsxs9(
1286
1390
  Picker,
1287
1391
  {
@@ -1620,7 +1724,7 @@ var Tabs = Object.assign(TabsRoot, {
1620
1724
  });
1621
1725
 
1622
1726
  // src/components/Tooltip/Tooltip.tsx
1623
- import { useEffect as useEffect2, useRef as useRef3, useState as useState6 } from "react";
1727
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
1624
1728
  import { Modal as Modal4, Pressable as Pressable9, Text as Text10, View as View18 } from "react-native";
1625
1729
 
1626
1730
  // src/hooks/useNativeFloatingPosition.ts
@@ -1760,7 +1864,7 @@ function Tooltip({
1760
1864
  closeTimerRef.current = null;
1761
1865
  }, hideDelay);
1762
1866
  };
1763
- useEffect2(() => {
1867
+ useEffect3(() => {
1764
1868
  return clearCloseTimer;
1765
1869
  }, []);
1766
1870
  return /* @__PURE__ */ jsxs11(View18, { style: [styles.root, style], children: [
@@ -2556,7 +2660,7 @@ var Input = forwardRef3(
2556
2660
  Input.displayName = "Input";
2557
2661
 
2558
2662
  // src/primitives/Radio/Radio.tsx
2559
- import { forwardRef as forwardRef4, useEffect as useEffect3 } from "react";
2663
+ import { forwardRef as forwardRef4, useEffect as useEffect4 } from "react";
2560
2664
  import { useControllableState as useControllableState5 } from "@vellira-ui/core";
2561
2665
  import {
2562
2666
  Pressable as Pressable13,
@@ -2707,7 +2811,7 @@ var Radio = forwardRef4(
2707
2811
  };
2708
2812
  const typographySize = typographySizeByRadioSize[resolvedSize];
2709
2813
  const controlMarginTop = (typographySize.labelLineHeight - controlSize) / 2;
2710
- useEffect3(() => {
2814
+ useEffect4(() => {
2711
2815
  if (typeof __DEV__ !== "undefined" && __DEV__ && !label && !accessibilityLabel) {
2712
2816
  console.warn(
2713
2817
  "Radio requires either a visible label or accessibilityLabel."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.24.0",
3
+ "version": "2.25.1",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",