@vellira-ui/react-native 2.21.0 → 2.22.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 +14 -0
  2. package/dist/index.js +216 -56
  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,29 +1868,42 @@ 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
  }
1867
1884
  });
1868
1885
 
1869
1886
  // src/primitives/Button/Button.tsx
1870
- import { jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1887
+ import { Fragment, jsx as jsx23, jsxs as jsxs13 } from "react/jsx-runtime";
1871
1888
  var sizeMap = {
1872
1889
  sm: {
1873
1890
  px: 12,
1874
1891
  py: 8,
1892
+ height: 36,
1875
1893
  fontSize: 12,
1876
1894
  iconSize: 16
1877
1895
  },
1878
1896
  md: {
1879
1897
  px: 16,
1880
1898
  py: 12,
1899
+ height: 44,
1881
1900
  fontSize: 14,
1882
1901
  iconSize: 20
1883
1902
  },
1884
1903
  lg: {
1885
1904
  px: 20,
1886
1905
  py: 16,
1906
+ height: 52,
1887
1907
  fontSize: 16,
1888
1908
  iconSize: 24
1889
1909
  }
@@ -1896,6 +1916,10 @@ function Button({
1896
1916
  loading = false,
1897
1917
  loadingText,
1898
1918
  onPress,
1919
+ onFocus,
1920
+ onBlur,
1921
+ onHoverIn,
1922
+ onHoverOut,
1899
1923
  size = "md",
1900
1924
  leftIcon,
1901
1925
  rightIcon,
@@ -1905,76 +1929,100 @@ function Button({
1905
1929
  textStyle,
1906
1930
  accessibilityLabel,
1907
1931
  iconSize,
1908
- testID
1932
+ testID,
1933
+ ...props
1909
1934
  }) {
1910
1935
  const { theme } = useTheme();
1911
1936
  const styles = useThemeStyles(createStyles21);
1912
1937
  const config = sizeMap[size];
1913
1938
  const variantTheme = theme.components.button[color][variant];
1914
- const [isPressed, setIsPressed] = useState7(false);
1939
+ const [isHovered, setIsHovered] = useState7(false);
1940
+ const [isFocused, setIsFocused] = useState7(false);
1915
1941
  const isDisabled = disabled || loading;
1916
1942
  const iconOnly = iconOnlyProp || !children && Boolean(leftIcon || rightIcon);
1917
1943
  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;
1924
- const contentColor = stateTheme.fg;
1944
+ devWarning(
1945
+ !iconOnly || Boolean(accessibilityLabel),
1946
+ "Button: icon-only buttons must provide an accessibilityLabel."
1947
+ );
1925
1948
  const resolvedIconSize = iconSize ?? config.iconSize;
1926
- const renderIcon = (icon, iconColor) => {
1927
- return cloneElement4(icon, {
1928
- color: iconColor,
1929
- size: resolvedIconSize
1930
- });
1949
+ const handleFocus = (event) => {
1950
+ setIsFocused(true);
1951
+ onFocus?.(event);
1952
+ };
1953
+ const handleBlur = (event) => {
1954
+ setIsFocused(false);
1955
+ onBlur?.(event);
1956
+ };
1957
+ const handleHoverIn = (event) => {
1958
+ setIsHovered(true);
1959
+ onHoverIn?.(event);
1960
+ };
1961
+ const handleHoverOut = (event) => {
1962
+ setIsHovered(false);
1963
+ onHoverOut?.(event);
1931
1964
  };
1932
- return /* @__PURE__ */ jsxs13(
1965
+ const renderIcon = (icon, iconColor) => cloneElement4(icon, {
1966
+ color: iconColor,
1967
+ size: resolvedIconSize
1968
+ });
1969
+ const getInteractionTheme = (pressed) => isDisabled ? theme.components.button.disabled : pressed ? variantTheme.pressed : isHovered ? variantTheme.hover : variantTheme.default;
1970
+ return /* @__PURE__ */ jsx23(
1933
1971
  Pressable11,
1934
1972
  {
1973
+ ...props,
1935
1974
  testID,
1936
1975
  disabled: isDisabled,
1937
1976
  onPress: isDisabled ? void 0 : onPress,
1938
1977
  accessibilityRole: "button",
1939
1978
  accessibilityState: { disabled: isDisabled, busy: loading },
1940
1979
  accessibilityLabel: accessibilityLabel ?? (typeof content === "string" ? content : void 0),
1941
- onPressIn: () => setIsPressed(true),
1942
- onPressOut: () => setIsPressed(false),
1980
+ onBlur: handleBlur,
1981
+ onFocus: handleFocus,
1982
+ onHoverIn: handleHoverIn,
1983
+ onHoverOut: handleHoverOut,
1943
1984
  style: ({ pressed }) => {
1944
- const pressedTheme = !isDisabled && pressed ? variantTheme.pressed : stateTheme;
1985
+ const interactionTheme = getInteractionTheme(pressed);
1945
1986
  return [
1946
1987
  styles.button,
1947
1988
  {
1948
- backgroundColor: pressedTheme.bg,
1949
- borderColor: pressedTheme.border,
1950
- paddingHorizontal: iconOnly ? config.py : config.px,
1951
- paddingVertical: config.py
1989
+ backgroundColor: interactionTheme.bg,
1990
+ borderColor: interactionTheme.border,
1991
+ paddingHorizontal: iconOnly ? 0 : config.px,
1992
+ paddingVertical: iconOnly ? 0 : config.py,
1993
+ width: iconOnly ? config.height : void 0,
1994
+ height: iconOnly ? config.height : void 0
1952
1995
  },
1953
- fullWidth && styles.fullWidth,
1996
+ fullWidth && !iconOnly && styles.fullWidth,
1954
1997
  isDisabled && styles.disabled,
1955
1998
  pressed && !isDisabled && styles.pressed,
1999
+ isFocused && !isDisabled && styles.focused,
1956
2000
  style
1957
2001
  ];
1958
2002
  },
1959
- children: [
1960
- loading && /* @__PURE__ */ jsx23(ActivityIndicator, { size: "small", color: contentColor }),
1961
- !loading && leftIcon && renderIcon(leftIcon, contentColor),
1962
- content && !iconOnly && /* @__PURE__ */ jsx23(
1963
- Text12,
1964
- {
1965
- style: [
1966
- styles.text,
1967
- {
1968
- fontSize: config.fontSize,
1969
- color: contentColor
1970
- },
1971
- textStyle
1972
- ],
1973
- children: content
1974
- }
1975
- ),
1976
- !loading && rightIcon && renderIcon(rightIcon, contentColor)
1977
- ]
2003
+ children: ({ pressed }) => {
2004
+ const interactionTheme = getInteractionTheme(pressed);
2005
+ const contentColor = interactionTheme.fg;
2006
+ return /* @__PURE__ */ jsxs13(Fragment, { children: [
2007
+ loading && /* @__PURE__ */ jsx23(ActivityIndicator, { size: "small", color: contentColor }),
2008
+ !loading && leftIcon && renderIcon(leftIcon, contentColor),
2009
+ content && !iconOnly && /* @__PURE__ */ jsx23(
2010
+ Text12,
2011
+ {
2012
+ style: [
2013
+ styles.text,
2014
+ {
2015
+ fontSize: config.fontSize,
2016
+ color: contentColor
2017
+ },
2018
+ textStyle
2019
+ ],
2020
+ children: content
2021
+ }
2022
+ ),
2023
+ !loading && rightIcon && renderIcon(rightIcon, contentColor)
2024
+ ] });
2025
+ }
1978
2026
  }
1979
2027
  );
1980
2028
  }
@@ -2103,7 +2151,7 @@ Checkbox.displayName = "Checkbox";
2103
2151
 
2104
2152
  // src/primitives/Input/Input.tsx
2105
2153
  import { cloneElement as cloneElement5, forwardRef as forwardRef2, useState as useState8 } from "react";
2106
- import { TextInput, View as View20 } from "react-native";
2154
+ import { Pressable as Pressable13, Text as Text14, TextInput, View as View20 } from "react-native";
2107
2155
 
2108
2156
  // src/primitives/Input/Input.styles.ts
2109
2157
  import { StyleSheet as StyleSheet23 } from "react-native";
@@ -2128,18 +2176,51 @@ var createStyles23 = (theme) => StyleSheet23.create({
2128
2176
  borderRadius: theme.tokens.radius.md,
2129
2177
  borderWidth: 1
2130
2178
  },
2131
- inputWithLeftIcon: {
2179
+ inputWithLeftAdornment: {
2132
2180
  paddingLeft: theme.tokens.spacing[5] + 20
2133
2181
  },
2182
+ inputWithRightAdornment: {
2183
+ paddingRight: theme.tokens.spacing[5] + 20
2184
+ },
2185
+ rightIcon: {
2186
+ position: "absolute",
2187
+ right: theme.tokens.spacing[4],
2188
+ top: "50%",
2189
+ zIndex: 1,
2190
+ width: 20,
2191
+ height: 20,
2192
+ marginTop: -10,
2193
+ alignItems: "center",
2194
+ justifyContent: "center"
2195
+ },
2134
2196
  leftIcon: {
2135
2197
  position: "absolute",
2136
2198
  left: theme.tokens.spacing[4],
2137
- top: 0,
2138
- bottom: 0,
2199
+ top: "50%",
2139
2200
  zIndex: 1,
2201
+ width: 20,
2202
+ height: 20,
2203
+ marginTop: -10,
2140
2204
  alignItems: "center",
2141
2205
  justifyContent: "center"
2142
2206
  },
2207
+ clearButton: {
2208
+ position: "absolute",
2209
+ right: theme.tokens.spacing[4],
2210
+ top: "50%",
2211
+ zIndex: 1,
2212
+ width: 20,
2213
+ height: 20,
2214
+ marginTop: -10,
2215
+ borderRadius: 999,
2216
+ alignItems: "center",
2217
+ justifyContent: "center"
2218
+ },
2219
+ clearButtonText: {
2220
+ color: theme.components.input.icon.danger,
2221
+ fontSize: 16,
2222
+ lineHeight: 20
2223
+ },
2143
2224
  sm: {
2144
2225
  minHeight: 36,
2145
2226
  paddingHorizontal: theme.tokens.spacing[3],
@@ -2169,6 +2250,11 @@ var createStyles23 = (theme) => StyleSheet23.create({
2169
2250
  errorFocused: {
2170
2251
  borderColor: theme.components.input.error.border
2171
2252
  },
2253
+ readOnly: {
2254
+ color: theme.components.input.readOnly.fg,
2255
+ borderColor: theme.components.input.readOnly.border,
2256
+ backgroundColor: theme.components.input.readOnly.bg
2257
+ },
2172
2258
  disabled: {
2173
2259
  opacity: 1,
2174
2260
  color: theme.components.input.disabled.fg,
@@ -2209,15 +2295,23 @@ var autoCorrectByInputType = {
2209
2295
  var Input = forwardRef2(
2210
2296
  ({
2211
2297
  label,
2298
+ description,
2212
2299
  value,
2300
+ defaultValue,
2213
2301
  onChange,
2214
2302
  placeholder,
2215
2303
  size = "md",
2216
2304
  error,
2217
2305
  disabled = false,
2218
2306
  required = false,
2307
+ readOnly = false,
2219
2308
  type = "text",
2220
2309
  leftIcon,
2310
+ rightIcon,
2311
+ leftIconTone = "default",
2312
+ rightIconTone = "default",
2313
+ clearIcon,
2314
+ clearIconTone = "danger",
2221
2315
  iconSize,
2222
2316
  containerStyle,
2223
2317
  inputStyle,
@@ -2229,15 +2323,25 @@ var Input = forwardRef2(
2229
2323
  onFocus,
2230
2324
  accessibilityLabel,
2231
2325
  accessibilityHint,
2326
+ testID,
2327
+ autoFocus,
2328
+ maxLength,
2329
+ clearable,
2330
+ onClear,
2232
2331
  ...props
2233
2332
  }, ref) => {
2234
2333
  const { theme } = useTheme();
2235
2334
  const styles = useThemeStyles(createStyles23);
2236
2335
  const [isFocused, setIsFocused] = useState8(false);
2237
- const placeholderTextColor = disabled ? getDisabledPlaceholderTextColor(theme) : getPlaceholderTextColor(theme);
2336
+ const [uncontrolledValue, setUncontrolledValue] = useState8(
2337
+ defaultValue ?? ""
2338
+ );
2339
+ const isControlled = value !== void 0;
2340
+ const currentValue = isControlled ? value : uncontrolledValue;
2341
+ const hasValue = currentValue !== "";
2342
+ const placeholderTextColor = disabled ? getDisabledPlaceholderTextColor(theme) : readOnly ? theme.components.input.readOnly.placeholder : getPlaceholderTextColor(theme);
2238
2343
  const isPassword = type === "password";
2239
2344
  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
2345
  const handleFocus = (event) => {
2242
2346
  setIsFocused(true);
2243
2347
  onFocus?.(event);
@@ -2246,10 +2350,29 @@ var Input = forwardRef2(
2246
2350
  setIsFocused(false);
2247
2351
  onBlur?.(event);
2248
2352
  };
2353
+ const handleChangeText = (nextValue) => {
2354
+ if (!isControlled) {
2355
+ setUncontrolledValue(nextValue);
2356
+ }
2357
+ onChange?.(nextValue);
2358
+ };
2359
+ const handleClear = () => {
2360
+ if (!isControlled) {
2361
+ setUncontrolledValue("");
2362
+ }
2363
+ onChange?.("");
2364
+ onClear?.();
2365
+ };
2366
+ const showClearButton = clearable && hasValue && !disabled && !readOnly;
2367
+ const showRightIcon = !showClearButton && Boolean(rightIcon);
2368
+ const leftIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon[leftIconTone];
2369
+ const rightIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon[rightIconTone];
2370
+ const clearIconColor = disabled ? theme.components.input.disabled.icon : theme.components.input.icon[clearIconTone];
2249
2371
  return /* @__PURE__ */ jsx25(
2250
2372
  FormField,
2251
2373
  {
2252
2374
  label,
2375
+ description,
2253
2376
  error,
2254
2377
  required,
2255
2378
  disabled,
@@ -2263,7 +2386,7 @@ var Input = forwardRef2(
2263
2386
  accessibilityElementsHidden: true,
2264
2387
  importantForAccessibility: "no",
2265
2388
  children: cloneElement5(leftIcon, {
2266
- color: iconColor,
2389
+ color: leftIconColor,
2267
2390
  size: resolvedIconSize
2268
2391
  })
2269
2392
  }
@@ -2273,16 +2396,19 @@ var Input = forwardRef2(
2273
2396
  {
2274
2397
  ...props,
2275
2398
  ref,
2276
- value,
2277
- editable: !disabled,
2399
+ value: currentValue,
2400
+ onChangeText: handleChangeText,
2401
+ editable: !disabled && !readOnly,
2278
2402
  placeholder,
2279
2403
  keyboardType: keyboardType ?? keyboardTypeByInputType[type],
2280
2404
  secureTextEntry: secureTextEntry ?? isPassword,
2281
2405
  autoCapitalize: autoCapitalize ?? autoCapitalizeByInputType[type],
2282
2406
  autoCorrect: autoCorrect ?? autoCorrectByInputType[type],
2283
- onChangeText: onChange,
2284
2407
  onBlur: handleBlur,
2285
2408
  onFocus: handleFocus,
2409
+ autoFocus,
2410
+ maxLength,
2411
+ testID,
2286
2412
  placeholderTextColor,
2287
2413
  accessibilityLabel: accessibilityLabel ?? label,
2288
2414
  accessibilityHint,
@@ -2291,13 +2417,47 @@ var Input = forwardRef2(
2291
2417
  styles.input,
2292
2418
  styles[size],
2293
2419
  inputStyle,
2294
- leftIcon && styles.inputWithLeftIcon,
2295
- isFocused && !disabled && styles.focused,
2420
+ leftIcon && styles.inputWithLeftAdornment,
2421
+ (showRightIcon || showClearButton) && styles.inputWithRightAdornment,
2422
+ isFocused && !disabled && !readOnly && styles.focused,
2296
2423
  error && styles.error,
2297
- isFocused && error && !disabled && styles.errorFocused,
2424
+ isFocused && error && !disabled && !readOnly && styles.errorFocused,
2425
+ readOnly && !disabled && styles.readOnly,
2298
2426
  disabled && styles.disabled
2299
2427
  ]
2300
2428
  }
2429
+ ),
2430
+ showClearButton ? /* @__PURE__ */ jsx25(
2431
+ Pressable13,
2432
+ {
2433
+ accessibilityRole: "button",
2434
+ accessibilityLabel: "Clear input",
2435
+ hitSlop: 8,
2436
+ onPress: handleClear,
2437
+ style: styles.clearButton,
2438
+ children: clearIcon ? cloneElement5(clearIcon, {
2439
+ color: clearIconColor,
2440
+ size: resolvedIconSize
2441
+ }) : /* @__PURE__ */ jsx25(
2442
+ Text14,
2443
+ {
2444
+ style: [styles.clearButtonText, { color: clearIconColor }],
2445
+ children: "\xD7"
2446
+ }
2447
+ )
2448
+ }
2449
+ ) : showRightIcon && rightIcon && /* @__PURE__ */ jsx25(
2450
+ View20,
2451
+ {
2452
+ pointerEvents: "none",
2453
+ style: styles.rightIcon,
2454
+ accessibilityElementsHidden: true,
2455
+ importantForAccessibility: "no",
2456
+ children: cloneElement5(rightIcon, {
2457
+ color: rightIconColor,
2458
+ size: resolvedIconSize
2459
+ })
2460
+ }
2301
2461
  )
2302
2462
  ] })
2303
2463
  }
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.1",
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/icons": "2.22.1",
39
+ "@vellira-ui/core": "2.22.1",
40
+ "@vellira-ui/types": "2.22.1",
41
+ "@vellira-ui/tokens": "2.22.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/react": "^19.2.3",