@vellira-ui/react-native 2.21.0 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +151 -25
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -55,6 +55,20 @@ export function Example() {
55
55
  }
56
56
  ```
57
57
 
58
+ ### Button Notes
59
+
60
+ Use `accessibilityLabel` for icon-only native buttons:
61
+
62
+ ```tsx
63
+ import { Search } from '@vellira-ui/icons';
64
+
65
+ <Button accessibilityLabel='Search' iconOnly leftIcon={<Search />} />;
66
+ ```
67
+
68
+ Button icons are React elements from `@vellira-ui/icons`; the component injects
69
+ the current icon color and size. `loading` disables interaction and can replace
70
+ the visible label with `loadingText`.
71
+
58
72
  ## Testing
59
73
 
60
74
  Run only native tests:
package/dist/index.js CHANGED
@@ -1831,6 +1831,13 @@ Tooltip.displayName = "Tooltip";
1831
1831
  import { cloneElement as cloneElement4, useState as useState7 } from "react";
1832
1832
  import { ActivityIndicator, Pressable as Pressable11, Text as Text12 } from "react-native";
1833
1833
 
1834
+ // src/utils/devWarning.ts
1835
+ var devWarning = (condition, message) => {
1836
+ if ((typeof __DEV__ === "undefined" || __DEV__) && !condition) {
1837
+ console.warn(message);
1838
+ }
1839
+ };
1840
+
1834
1841
  // src/primitives/Button/Button.styles.ts
1835
1842
  import { StyleSheet as StyleSheet21 } from "react-native";
1836
1843
  var fontWeight3 = (value) => value;
@@ -1861,6 +1868,16 @@ var createStyles21 = (theme) => StyleSheet21.create({
1861
1868
  disabled: {
1862
1869
  borderColor: theme.components.button.disabled.border
1863
1870
  },
1871
+ focused: {
1872
+ borderColor: theme.semantic.focus.ring,
1873
+ shadowColor: theme.semantic.focus.ring,
1874
+ shadowOffset: {
1875
+ width: 0,
1876
+ height: 0
1877
+ },
1878
+ shadowOpacity: 0.35,
1879
+ shadowRadius: 4
1880
+ },
1864
1881
  pressed: {
1865
1882
  transform: [{ scale: 0.98 }]
1866
1883
  }
@@ -1912,15 +1929,16 @@ function Button({
1912
1929
  const config = sizeMap[size];
1913
1930
  const variantTheme = theme.components.button[color][variant];
1914
1931
  const [isPressed, setIsPressed] = useState7(false);
1932
+ const [isHovered, setIsHovered] = useState7(false);
1933
+ const [isFocused, setIsFocused] = useState7(false);
1915
1934
  const isDisabled = disabled || loading;
1916
1935
  const iconOnly = iconOnlyProp || !children && Boolean(leftIcon || rightIcon);
1917
1936
  const content = loading && loadingText ? loadingText : children;
1918
- if (iconOnly && !accessibilityLabel) {
1919
- console.warn(
1920
- "Vellira Button: icon-only buttons must provide an accessibilityLabel."
1921
- );
1922
- }
1923
- const stateTheme = isDisabled ? theme.components.button.disabled : isPressed ? variantTheme.pressed : variantTheme.default;
1937
+ devWarning(
1938
+ !iconOnly || Boolean(accessibilityLabel),
1939
+ "Button: icon-only buttons must provide an accessibilityLabel."
1940
+ );
1941
+ const stateTheme = isDisabled ? theme.components.button.disabled : isPressed ? variantTheme.pressed : isHovered ? variantTheme.hover : variantTheme.default;
1924
1942
  const contentColor = stateTheme.fg;
1925
1943
  const resolvedIconSize = iconSize ?? config.iconSize;
1926
1944
  const renderIcon = (icon, iconColor) => {
@@ -1938,10 +1956,14 @@ function Button({
1938
1956
  accessibilityRole: "button",
1939
1957
  accessibilityState: { disabled: isDisabled, busy: loading },
1940
1958
  accessibilityLabel: accessibilityLabel ?? (typeof content === "string" ? content : void 0),
1959
+ onBlur: () => setIsFocused(false),
1960
+ onFocus: () => setIsFocused(true),
1961
+ onHoverIn: () => setIsHovered(true),
1962
+ onHoverOut: () => setIsHovered(false),
1941
1963
  onPressIn: () => setIsPressed(true),
1942
1964
  onPressOut: () => setIsPressed(false),
1943
1965
  style: ({ pressed }) => {
1944
- const pressedTheme = !isDisabled && pressed ? variantTheme.pressed : stateTheme;
1966
+ const pressedTheme = !isDisabled && pressed ? variantTheme.pressed : !isDisabled && isHovered ? variantTheme.hover : stateTheme;
1945
1967
  return [
1946
1968
  styles.button,
1947
1969
  {
@@ -1953,6 +1975,7 @@ function Button({
1953
1975
  fullWidth && styles.fullWidth,
1954
1976
  isDisabled && styles.disabled,
1955
1977
  pressed && !isDisabled && styles.pressed,
1978
+ isFocused && !isDisabled && styles.focused,
1956
1979
  style
1957
1980
  ];
1958
1981
  },
@@ -2103,7 +2126,7 @@ Checkbox.displayName = "Checkbox";
2103
2126
 
2104
2127
  // src/primitives/Input/Input.tsx
2105
2128
  import { cloneElement as cloneElement5, forwardRef as forwardRef2, useState as useState8 } from "react";
2106
- import { TextInput, View as View20 } from "react-native";
2129
+ import { Pressable as Pressable13, Text as Text14, TextInput, View as View20 } from "react-native";
2107
2130
 
2108
2131
  // src/primitives/Input/Input.styles.ts
2109
2132
  import { StyleSheet as StyleSheet23 } from "react-native";
@@ -2128,18 +2151,51 @@ var createStyles23 = (theme) => StyleSheet23.create({
2128
2151
  borderRadius: theme.tokens.radius.md,
2129
2152
  borderWidth: 1
2130
2153
  },
2131
- inputWithLeftIcon: {
2154
+ inputWithLeftAdornment: {
2132
2155
  paddingLeft: theme.tokens.spacing[5] + 20
2133
2156
  },
2134
- leftIcon: {
2157
+ inputWithRightAdornment: {
2158
+ paddingRight: theme.tokens.spacing[5] + 20
2159
+ },
2160
+ rightAdornment: {
2161
+ position: "absolute",
2162
+ right: theme.tokens.spacing[4],
2163
+ top: "50%",
2164
+ zIndex: 1,
2165
+ width: 20,
2166
+ height: 20,
2167
+ marginTop: -10,
2168
+ alignItems: "center",
2169
+ justifyContent: "center"
2170
+ },
2171
+ leftAdornment: {
2135
2172
  position: "absolute",
2136
2173
  left: theme.tokens.spacing[4],
2137
- top: 0,
2138
- bottom: 0,
2174
+ top: "50%",
2139
2175
  zIndex: 1,
2176
+ width: 20,
2177
+ height: 20,
2178
+ marginTop: -10,
2140
2179
  alignItems: "center",
2141
2180
  justifyContent: "center"
2142
2181
  },
2182
+ clearButton: {
2183
+ position: "absolute",
2184
+ right: theme.tokens.spacing[4],
2185
+ top: "50%",
2186
+ zIndex: 1,
2187
+ width: 20,
2188
+ height: 20,
2189
+ marginTop: -10,
2190
+ borderRadius: 999,
2191
+ alignItems: "center",
2192
+ justifyContent: "center"
2193
+ },
2194
+ clearButtonText: {
2195
+ color: theme.components.input.icon.danger,
2196
+ fontSize: 16,
2197
+ lineHeight: 20
2198
+ },
2143
2199
  sm: {
2144
2200
  minHeight: 36,
2145
2201
  paddingHorizontal: theme.tokens.spacing[3],
@@ -2169,6 +2225,11 @@ var createStyles23 = (theme) => StyleSheet23.create({
2169
2225
  errorFocused: {
2170
2226
  borderColor: theme.components.input.error.border
2171
2227
  },
2228
+ readOnly: {
2229
+ color: theme.components.input.readOnly.fg,
2230
+ borderColor: theme.components.input.readOnly.border,
2231
+ backgroundColor: theme.components.input.readOnly.bg
2232
+ },
2172
2233
  disabled: {
2173
2234
  opacity: 1,
2174
2235
  color: theme.components.input.disabled.fg,
@@ -2210,14 +2271,20 @@ var Input = forwardRef2(
2210
2271
  ({
2211
2272
  label,
2212
2273
  value,
2274
+ defaultValue,
2213
2275
  onChange,
2214
2276
  placeholder,
2215
2277
  size = "md",
2216
2278
  error,
2217
2279
  disabled = false,
2218
2280
  required = false,
2281
+ readOnly = false,
2219
2282
  type = "text",
2220
- leftIcon,
2283
+ leftAdornment,
2284
+ rightAdornment,
2285
+ leftAdornmentTone = "default",
2286
+ rightAdornmentTone = "default",
2287
+ clearIcon,
2221
2288
  iconSize,
2222
2289
  containerStyle,
2223
2290
  inputStyle,
@@ -2229,15 +2296,25 @@ var Input = forwardRef2(
2229
2296
  onFocus,
2230
2297
  accessibilityLabel,
2231
2298
  accessibilityHint,
2299
+ testID,
2300
+ autoFocus,
2301
+ maxLength,
2302
+ clearable,
2303
+ onClear,
2232
2304
  ...props
2233
2305
  }, ref) => {
2234
2306
  const { theme } = useTheme();
2235
2307
  const styles = useThemeStyles(createStyles23);
2236
2308
  const [isFocused, setIsFocused] = useState8(false);
2237
- const placeholderTextColor = disabled ? getDisabledPlaceholderTextColor(theme) : getPlaceholderTextColor(theme);
2309
+ const [uncontrolledValue, setUncontrolledValue] = useState8(
2310
+ defaultValue ?? ""
2311
+ );
2312
+ const isControlled = value !== void 0;
2313
+ const currentValue = isControlled ? value : uncontrolledValue;
2314
+ const hasValue = currentValue !== "";
2315
+ const placeholderTextColor = disabled ? getDisabledPlaceholderTextColor(theme) : readOnly ? theme.components.input.readOnly.placeholder : getPlaceholderTextColor(theme);
2238
2316
  const isPassword = type === "password";
2239
2317
  const resolvedIconSize = iconSize ?? 16;
2240
- const iconColor = disabled ? theme.components.input.disabled.fg : isFocused ? theme.components.input.focus.fg : theme.components.input.default.placeholder;
2241
2318
  const handleFocus = (event) => {
2242
2319
  setIsFocused(true);
2243
2320
  onFocus?.(event);
@@ -2246,6 +2323,24 @@ var Input = forwardRef2(
2246
2323
  setIsFocused(false);
2247
2324
  onBlur?.(event);
2248
2325
  };
2326
+ const handleChangeText = (nextValue) => {
2327
+ if (!isControlled) {
2328
+ setUncontrolledValue(nextValue);
2329
+ }
2330
+ onChange?.(nextValue);
2331
+ };
2332
+ const handleClear = () => {
2333
+ if (!isControlled) {
2334
+ setUncontrolledValue("");
2335
+ }
2336
+ onChange?.("");
2337
+ onClear?.();
2338
+ };
2339
+ const showClearButton = clearable && hasValue && !disabled && !readOnly;
2340
+ const showRightIcon = !showClearButton && Boolean(rightAdornment);
2341
+ const leftIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon[leftAdornmentTone];
2342
+ const rightIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon[rightAdornmentTone];
2343
+ const clearIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon.danger;
2249
2344
  return /* @__PURE__ */ jsx25(
2250
2345
  FormField,
2251
2346
  {
@@ -2255,15 +2350,15 @@ var Input = forwardRef2(
2255
2350
  disabled,
2256
2351
  style: containerStyle,
2257
2352
  children: /* @__PURE__ */ jsxs15(View20, { style: styles.inputWrapper, children: [
2258
- leftIcon && /* @__PURE__ */ jsx25(
2353
+ leftAdornment && /* @__PURE__ */ jsx25(
2259
2354
  View20,
2260
2355
  {
2261
2356
  pointerEvents: "none",
2262
- style: styles.leftIcon,
2357
+ style: styles.leftAdornment,
2263
2358
  accessibilityElementsHidden: true,
2264
2359
  importantForAccessibility: "no",
2265
- children: cloneElement5(leftIcon, {
2266
- color: iconColor,
2360
+ children: cloneElement5(leftAdornment, {
2361
+ color: leftIconColor,
2267
2362
  size: resolvedIconSize
2268
2363
  })
2269
2364
  }
@@ -2273,16 +2368,19 @@ var Input = forwardRef2(
2273
2368
  {
2274
2369
  ...props,
2275
2370
  ref,
2276
- value,
2277
- editable: !disabled,
2371
+ value: currentValue,
2372
+ onChangeText: handleChangeText,
2373
+ editable: !disabled && !readOnly,
2278
2374
  placeholder,
2279
2375
  keyboardType: keyboardType ?? keyboardTypeByInputType[type],
2280
2376
  secureTextEntry: secureTextEntry ?? isPassword,
2281
2377
  autoCapitalize: autoCapitalize ?? autoCapitalizeByInputType[type],
2282
2378
  autoCorrect: autoCorrect ?? autoCorrectByInputType[type],
2283
- onChangeText: onChange,
2284
2379
  onBlur: handleBlur,
2285
2380
  onFocus: handleFocus,
2381
+ autoFocus,
2382
+ maxLength,
2383
+ testID,
2286
2384
  placeholderTextColor,
2287
2385
  accessibilityLabel: accessibilityLabel ?? label,
2288
2386
  accessibilityHint,
@@ -2291,13 +2389,41 @@ var Input = forwardRef2(
2291
2389
  styles.input,
2292
2390
  styles[size],
2293
2391
  inputStyle,
2294
- leftIcon && styles.inputWithLeftIcon,
2295
- isFocused && !disabled && styles.focused,
2392
+ leftAdornment && styles.inputWithLeftAdornment,
2393
+ (showRightIcon || showClearButton) && styles.inputWithRightAdornment,
2394
+ isFocused && !disabled && !readOnly && styles.focused,
2296
2395
  error && styles.error,
2297
- isFocused && error && !disabled && styles.errorFocused,
2396
+ isFocused && error && !disabled && !readOnly && styles.errorFocused,
2397
+ readOnly && !disabled && styles.readOnly,
2298
2398
  disabled && styles.disabled
2299
2399
  ]
2300
2400
  }
2401
+ ),
2402
+ showClearButton ? /* @__PURE__ */ jsx25(
2403
+ Pressable13,
2404
+ {
2405
+ accessibilityRole: "button",
2406
+ accessibilityLabel: "Clear input",
2407
+ hitSlop: 8,
2408
+ onPress: handleClear,
2409
+ style: styles.clearButton,
2410
+ children: clearIcon ? cloneElement5(clearIcon, {
2411
+ color: clearIconColor,
2412
+ size: resolvedIconSize
2413
+ }) : /* @__PURE__ */ jsx25(Text14, { style: styles.clearButtonText, children: "\xD7" })
2414
+ }
2415
+ ) : showRightIcon && rightAdornment && /* @__PURE__ */ jsx25(
2416
+ View20,
2417
+ {
2418
+ pointerEvents: "none",
2419
+ style: styles.rightAdornment,
2420
+ accessibilityElementsHidden: true,
2421
+ importantForAccessibility: "no",
2422
+ children: cloneElement5(rightAdornment, {
2423
+ color: rightIconColor,
2424
+ size: resolvedIconSize
2425
+ })
2426
+ }
2301
2427
  )
2302
2428
  ] })
2303
2429
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.21.0",
3
+ "version": "2.22.0",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",
@@ -35,10 +35,10 @@
35
35
  "react-native": ">=0.81"
36
36
  },
37
37
  "dependencies": {
38
- "@vellira-ui/core": "2.21.0",
39
- "@vellira-ui/icons": "2.21.0",
40
- "@vellira-ui/tokens": "2.21.0",
41
- "@vellira-ui/types": "2.21.0"
38
+ "@vellira-ui/core": "2.22.0",
39
+ "@vellira-ui/icons": "2.22.0",
40
+ "@vellira-ui/types": "2.22.0",
41
+ "@vellira-ui/tokens": "2.22.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "^19.2.3",