react-native-molecules 0.5.0-beta.23 → 0.5.0-beta.25

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 (68) hide show
  1. package/components/Accordion/Accordion.tsx +1 -1
  2. package/components/Accordion/AccordionItem.tsx +1 -1
  3. package/components/Checkbox/Checkbox.tsx +2 -1
  4. package/components/DateField/useDateFieldState.ts +2 -2
  5. package/components/DatePicker/DatePickerProvider.tsx +1 -1
  6. package/components/DatePickerInline/DatePickerInline.tsx +1 -1
  7. package/components/DatePickerInline/DatePickerInlineBase.tsx +1 -1
  8. package/components/DatePickerInline/Day.tsx +1 -1
  9. package/components/DatePickerInline/Swiper.tsx +1 -1
  10. package/components/DatePickerInline/SwiperUtils.ts +1 -1
  11. package/components/DatePickerInline/dateUtils.tsx +1 -1
  12. package/components/DatePickerInline/store.tsx +2 -1
  13. package/components/Divider/index.tsx +2 -3
  14. package/components/ElementGroup/ElementGroup.tsx +1 -1
  15. package/components/FilePicker/FilePicker.tsx +1 -1
  16. package/components/Icon/iconFactory.tsx +2 -1
  17. package/components/IconButton/IconButton.tsx +39 -13
  18. package/components/IconButton/index.tsx +1 -0
  19. package/components/IconButton/types.ts +2 -0
  20. package/components/List/List.tsx +2 -1
  21. package/components/List/context.tsx +2 -1
  22. package/components/Portal/Portal.tsx +1 -2
  23. package/components/RadioButton/RadioButtonGroup.tsx +1 -2
  24. package/components/Rating/Rating.tsx +1 -1
  25. package/components/Select/Select.tsx +103 -34
  26. package/components/Select/context.tsx +3 -1
  27. package/components/Select/index.ts +20 -2
  28. package/components/Select/types.ts +2 -0
  29. package/components/Select/utils.ts +11 -4
  30. package/components/Switch/Switch.ios.tsx +1 -1
  31. package/components/Switch/Switch.tsx +2 -1
  32. package/components/Tabs/Tabs.tsx +2 -2
  33. package/components/TextInput/TextInput.tsx +4 -3
  34. package/components/TimePicker/AnalogClock.tsx +1 -1
  35. package/components/TimePicker/TimeInputs.tsx +1 -1
  36. package/components/TimePicker/TimePicker.tsx +1 -1
  37. package/components/TimePicker/TimePickerModal.tsx +1 -1
  38. package/components/Tooltip/Tooltip.tsx +1 -1
  39. package/components/TouchableRipple/TouchableRipple.tsx +1 -1
  40. package/hocs/index.tsx +1 -1
  41. package/hocs/withKeyboardAccessibility.tsx +2 -3
  42. package/hooks/index.tsx +2 -6
  43. package/hooks/useContrastColor.ts +1 -2
  44. package/hooks/useFilePicker.tsx +1 -1
  45. package/hooks/useHandleNumberFormat.tsx +2 -2
  46. package/hooks/useMediaQuery.tsx +1 -2
  47. package/package.json +95 -118
  48. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +1 -1
  49. package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
  50. package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
  51. package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
  52. package/utils/extractTextStyles.ts +1 -2
  53. package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
  54. package/utils/index.ts +0 -3
  55. package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
  56. package/context-bridge/index.tsx +0 -87
  57. package/fast-context/index.tsx +0 -190
  58. package/hocs/typedMemo.tsx +0 -5
  59. package/hooks/useControlledValue.tsx +0 -84
  60. package/hooks/useLatest.tsx +0 -9
  61. package/hooks/useMergedRefs.ts +0 -14
  62. package/hooks/usePrevious.ts +0 -13
  63. package/hooks/useToggle.tsx +0 -24
  64. package/hooks/useWhatHasUpdated.tsx +0 -48
  65. package/utils/color.ts +0 -22
  66. package/utils/compare/index.ts +0 -54
  67. package/utils/lodash.ts +0 -121
  68. package/utils/repository.ts +0 -53
@@ -1,3 +1,4 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import {
2
3
  type ComponentPropsWithRef,
3
4
  createContext,
@@ -9,7 +10,6 @@ import {
9
10
  } from 'react';
10
11
  import { View } from 'react-native';
11
12
 
12
- import { useControlledValue } from '../../hooks';
13
13
  import { accordionStyles } from './utils';
14
14
 
15
15
  export type Props = Omit<ComponentPropsWithRef<typeof View>, 'children'> & {
@@ -1,3 +1,4 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import {
2
3
  forwardRef,
3
4
  memo,
@@ -9,7 +10,6 @@ import {
9
10
  } from 'react';
10
11
  import { View, type ViewProps } from 'react-native';
11
12
 
12
- import { useControlledValue } from '../../hooks';
13
13
  import type { WithElements } from '../../types';
14
14
  import { extractSubcomponents } from '../../utils/extractSubcomponents';
15
15
  import { AccordionContext } from './Accordion';
@@ -1,7 +1,7 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import { forwardRef, memo, useCallback, useMemo } from 'react';
2
3
  import { View } from 'react-native';
3
4
 
4
- import { useControlledValue } from '../../hooks';
5
5
  import { resolveStateVariant } from '../../utils';
6
6
  import { Text } from '../Text';
7
7
  import CheckboxBase from './CheckboxBase';
@@ -50,6 +50,7 @@ const Checkbox = (
50
50
  // @ts-ignore // TODO - fix this
51
51
  state: state as States,
52
52
  isLeading,
53
+ // @ts-ignore // TODO - fix this
53
54
  size,
54
55
  });
55
56
  }
@@ -1,9 +1,9 @@
1
+ import { isNil } from '@react-native-molecules/utils/helpers/lodash';
2
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
3
  import { type RefObject, useCallback, useEffect, useState } from 'react';
2
4
  import type { BlurEvent, FocusEvent } from 'react-native';
3
5
 
4
- import { useLatest } from '../../hooks';
5
6
  import { endOfDay, format, isValid, parse } from '../../utils/date-fns';
6
- import { isNil } from '../../utils/lodash';
7
7
  import type { ValidRangeType } from '../DatePickerInline';
8
8
  import { useRangeChecker } from '../DatePickerInline/dateUtils';
9
9
 
@@ -1,8 +1,8 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import type { ReactNode } from 'react';
2
3
  import { memo, useCallback, useMemo, useRef, useState } from 'react';
3
4
 
4
5
  import { getRegisteredComponentWithFallback } from '../../core';
5
- import { useControlledValue } from '../../hooks';
6
6
  import type { ValidRangeType } from '../DatePickerInline';
7
7
  import type {
8
8
  DatePickerContextType,
@@ -1,7 +1,7 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import { memo, useCallback } from 'react';
2
3
  import { View, type ViewStyle } from 'react-native';
3
4
 
4
- import { useControlledValue } from '../../hooks';
5
5
  import DatePickerDockedHeader from './DatePickerDockedHeader';
6
6
  import DatePickerInlineBase from './DatePickerInlineBase';
7
7
  import DatePickerInlineHeader from './DatePickerInlineHeader';
@@ -1,7 +1,7 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import { memo, useCallback, useEffect, useMemo } from 'react';
2
3
  import { StyleSheet, View } from 'react-native';
3
4
 
4
- import { useLatest } from '../../hooks';
5
5
  import { areDatesOnSameDay, dateToUnix, getEndOfDay, getInitialIndex } from './dateUtils';
6
6
  import Month from './Month';
7
7
  import MonthPicker from './MonthPicker';
@@ -1,7 +1,7 @@
1
1
  import { memo, useCallback, useMemo } from 'react';
2
2
  import { type StyleProp, View, type ViewStyle } from 'react-native';
3
3
 
4
- import { useActionState } from '../../hooks/useActionState';
4
+ import { useActionState } from '../../hooks';
5
5
  import { resolveStateVariant } from '../../utils';
6
6
  import { StateLayer } from '../StateLayer';
7
7
  import { Text } from '../Text';
@@ -1,3 +1,4 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import {
2
3
  type CSSProperties,
3
4
  memo,
@@ -10,7 +11,6 @@ import {
10
11
  useState,
11
12
  } from 'react';
12
13
 
13
- import { useLatest } from '../../hooks';
14
14
  import AutoSizer from './AutoSizer';
15
15
  import { beginOffset, estimatedMonthHeight, getInitialIndex, totalMonths } from './dateUtils';
16
16
  import { addMonths, getRealIndex } from './dateUtils';
@@ -1,6 +1,6 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import { type RefObject, useEffect } from 'react';
2
3
 
3
- import { useLatest } from '../../hooks';
4
4
  import { addMonths, differenceInMonths, getRealIndex, startAtIndex } from './dateUtils';
5
5
 
6
6
  export type SwiperProps = {
@@ -1,6 +1,6 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import { useCallback } from 'react';
2
3
 
3
- import { useLatest } from '../../hooks';
4
4
  import type { CalendarDate, CalendarDates, ValidRangeType } from './types';
5
5
 
6
6
  export type DisableWeekDaysType = number[];
@@ -1,4 +1,5 @@
1
- import { createFastContext } from '../../fast-context';
1
+ import { createFastContext } from '@react-native-molecules/utils/fast-context';
2
+
2
3
  import { registerPortalContext } from '../Portal/Portal';
3
4
 
4
5
  export type Store = {
@@ -1,11 +1,10 @@
1
1
  export {
2
2
  Divider,
3
+ type DividerProps,
3
4
  horizontalDividerStyles,
4
5
  horizontalDividerStylesDefault,
6
+ type Props,
5
7
  verticalDividerStyles,
6
8
  verticalDividerStylesDefault,
7
- type DividerProps,
8
- type Props,
9
9
  } from './Divider';
10
-
11
10
  export { default } from './Divider';
@@ -1,8 +1,8 @@
1
+ import { isNil } from '@react-native-molecules/utils/helpers/lodash';
1
2
  import { Children, cloneElement, forwardRef, memo, type ReactElement, useMemo } from 'react';
2
3
  import { View, type ViewProps, type ViewStyle } from 'react-native';
3
4
 
4
5
  import { extractPropertiesFromStyles } from '../../utils/extractPropertiesFromStyles';
5
- import { isNil } from '../../utils/lodash';
6
6
  import { elementGroupStyles } from './utils';
7
7
 
8
8
  export enum Orientation {
@@ -1,6 +1,6 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import { memo, useCallback, useMemo } from 'react';
2
3
 
3
- import useControlledValue from '../../hooks/useControlledValue';
4
4
  import useFilePicker from '../../hooks/useFilePicker';
5
5
  import type { DocumentPickerOptions, DocumentResult } from '../../utils/DocumentPicker';
6
6
  import { IconButton } from '../IconButton';
@@ -1,5 +1,6 @@
1
1
  // import { textFactory } from '../Text/textFactory';
2
- import { memoize } from '../../utils/lodash';
2
+ import { memoize } from '@react-native-molecules/utils/helpers/lodash';
3
+
3
4
  import type { IconType } from './types';
4
5
 
5
6
  const customIcons: any = {};
@@ -6,16 +6,20 @@ import {
6
6
  type ViewProps,
7
7
  } from 'react-native';
8
8
 
9
- import { useActionState } from '../../hooks/useActionState';
9
+ import { useActionState } from '../../hooks';
10
10
  import { resolveStateVariant } from '../../utils';
11
11
  import { Icon, type IconProps, type IconType } from '../Icon';
12
12
  import CrossFadeIcon from '../Icon/CrossFadeIcon';
13
13
  import { StateLayer } from '../StateLayer';
14
14
  import { TouchableRipple, type TouchableRippleProps } from '../TouchableRipple';
15
- import type { IconButtonVariant } from './types';
15
+ import type { IconButtonShape, IconButtonVariant, IconButtonWidth } from './types';
16
16
  import { defaultStyles, iconButtonSizeToIconSizeMap } from './utils';
17
17
 
18
- const whiteSpace = 12;
18
+ const ICON_BUTTON_MIN_CONTAINER_SIZE = 32;
19
+ const ICON_BUTTON_CONTAINER_PADDING = 16;
20
+ const M3_ICON_BUTTON_NARROW_WIDTH_ADJUSTMENT = -8;
21
+ const M3_ICON_BUTTON_WIDE_WIDTH_ADJUSTMENT = 12;
22
+ const M3_ICON_BUTTON_SQUARE_CORNER_RADIUS = 12;
19
23
 
20
24
  export type Props = Omit<TouchableRippleProps, 'children' | 'style'> & {
21
25
  /**
@@ -26,6 +30,14 @@ export type Props = Omit<TouchableRippleProps, 'children' | 'style'> & {
26
30
  * Mode of the icon button. By default there is no specified mode - only pressable icon will be rendered.
27
31
  */
28
32
  variant?: IconButtonVariant;
33
+ /**
34
+ * Container shape. Material 3 supports round and square icon buttons.
35
+ */
36
+ shape?: IconButtonShape;
37
+ /**
38
+ * Container width option. `default` maps to Material 3's uniform width.
39
+ */
40
+ width?: IconButtonWidth;
29
41
  /**
30
42
  * Whether icon button is selected. A selected button receives alternative combination of icon and container colors.
31
43
  */
@@ -85,6 +97,8 @@ const IconButton = (
85
97
  selected = false,
86
98
  animated = false,
87
99
  variant = 'default',
100
+ shape = 'round',
101
+ width = 'default',
88
102
  style,
89
103
  testID,
90
104
  stateLayerProps = emptyObject,
@@ -126,33 +140,45 @@ const IconButton = (
126
140
  } = useMemo(() => {
127
141
  const iconSizeInNum =
128
142
  iconButtonSizeToIconSizeMap[size as keyof typeof iconButtonSizeToIconSizeMap] ??
129
- (typeof size === 'number' && size ? (size as number) : undefined);
143
+ (typeof size === 'number' && size ? (size as number) : 24);
144
+ const containerHeight = Math.max(
145
+ ICON_BUTTON_MIN_CONTAINER_SIZE,
146
+ iconSizeInNum + ICON_BUTTON_CONTAINER_PADDING,
147
+ );
148
+ const widthAdjustment =
149
+ width === 'narrow'
150
+ ? M3_ICON_BUTTON_NARROW_WIDTH_ADJUSTMENT
151
+ : width === 'wide'
152
+ ? M3_ICON_BUTTON_WIDE_WIDTH_ADJUSTMENT
153
+ : 0;
154
+ const containerWidth = Math.max(iconSizeInNum, containerHeight + widthAdjustment);
155
+ const borderRadius =
156
+ shape === 'round' ? containerHeight / 2 : M3_ICON_BUTTON_SQUARE_CORNER_RADIUS;
130
157
 
131
158
  return {
132
159
  iconColor: _iconColor,
133
160
  iconSize: iconSizeInNum,
134
161
  containerStyle: [
135
- iconSizeInNum
136
- ? {
137
- width: iconSizeInNum + whiteSpace,
138
- height: iconSizeInNum + whiteSpace,
139
- }
140
- : {},
141
162
  defaultStyles.root,
163
+ {
164
+ width: containerWidth,
165
+ height: containerHeight,
166
+ borderRadius,
167
+ },
142
168
  style,
143
169
  ],
144
170
  iconStyle: [defaultStyles.icon, iconStyleProp],
145
171
  // accessibilityTraits: disabled ? ['button', 'disabled'] : 'button',
146
172
  accessibilityState: { disabled },
147
- stateLayerStyle: [defaultStyles.stateLayer, stateLayerProps?.style],
173
+ stateLayerStyle: [defaultStyles.stateLayer, { borderRadius }, stateLayerProps?.style],
148
174
  };
149
175
  // eslint-disable-next-line react-hooks/exhaustive-deps
150
- }, [_iconColor, disabled, size, stateLayerProps?.style, style, state, variant]);
176
+ }, [_iconColor, disabled, shape, size, stateLayerProps?.style, style, state, variant, width]);
151
177
 
152
178
  return (
153
179
  <TouchableRipple
154
180
  borderless
155
- centered
181
+ centered={shape === 'round' && width === 'default'}
156
182
  onPress={onPress}
157
183
  rippleAlpha={0.12}
158
184
  accessibilityLabel={accessibilityLabel}
@@ -4,4 +4,5 @@ import IconButtonDefault from './IconButton';
4
4
  export const IconButton = getRegisteredComponentWithFallback('IconButton', IconButtonDefault);
5
5
 
6
6
  export type { Props as IconButtonProps } from './IconButton';
7
+ export type * from './types';
7
8
  export { defaultStyles } from './utils';
@@ -1 +1,3 @@
1
1
  export type IconButtonVariant = 'default' | 'outlined' | 'contained' | 'contained-tonal';
2
+ export type IconButtonShape = 'round' | 'square';
3
+ export type IconButtonWidth = 'narrow' | 'default' | 'wide';
@@ -1,8 +1,9 @@
1
+ import { useControlledValue, useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import { memo, useCallback, useMemo } from 'react';
2
3
  import { ScrollView, type StyleProp, type ViewStyle } from 'react-native';
3
4
 
4
5
  import { typedMemo } from '../../hocs';
5
- import { useActionState, useControlledValue, useLatest } from '../../hooks';
6
+ import { useActionState } from '../../hooks';
6
7
  import { resolveStateVariant } from '../../utils';
7
8
  import { StateLayer } from '../StateLayer';
8
9
  import { TouchableRipple } from '../TouchableRipple';
@@ -1,4 +1,5 @@
1
- import { createFastContext } from '../../fast-context';
1
+ import { createFastContext } from '@react-native-molecules/utils/fast-context';
2
+
2
3
  import { registerPortalContext } from '../Portal';
3
4
  import type { DefaultListItemT, ListContextValue } from './types';
4
5
 
@@ -1,8 +1,7 @@
1
1
  import { Portal as GorhomPortal } from '@gorhom/portal';
2
+ import { createContextBridge } from '@react-native-molecules/utils/context-bridge';
2
3
  import { type ComponentType, type ReactNode } from 'react';
3
4
 
4
- import { createContextBridge } from '../../context-bridge';
5
-
6
5
  const { BridgedComponent: Portal, registerContextToBridge: registerPortalContext } =
7
6
  createContextBridge<Omit<any, 'children'> & { children: ReactNode }>(
8
7
  'portal-context',
@@ -1,8 +1,7 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import { createContext, memo, type ReactNode, useMemo } from 'react';
2
3
  import { View, type ViewProps } from 'react-native';
3
4
 
4
- import { useControlledValue } from '../../hooks';
5
-
6
5
  export type Props = ViewProps & {
7
6
  /**
8
7
  * Function to execute on selection change.
@@ -1,7 +1,7 @@
1
+ import { useControlledValue } from '@react-native-molecules/utils/hooks';
1
2
  import { forwardRef, memo, useMemo } from 'react';
2
3
  import { View, type ViewProps, type ViewStyle } from 'react-native';
3
4
 
4
- import { useControlledValue } from '../../hooks';
5
5
  import type { IconProps, IconType } from '../Icon';
6
6
  import type { TooltipProps } from '../Tooltip';
7
7
  import RatingItem from './RatingItem';
@@ -1,4 +1,14 @@
1
- import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import { useControlledValue, useToggle } from '@react-native-molecules/utils/hooks';
2
+ import {
3
+ Fragment,
4
+ memo,
5
+ type ReactNode,
6
+ useCallback,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from 'react';
2
12
  import {
3
13
  type AccessibilityRole,
4
14
  type GestureResponderEvent,
@@ -9,9 +19,8 @@ import {
9
19
  } from 'react-native';
10
20
 
11
21
  import { typedMemo } from '../../hocs';
12
- import { useActionState, useControlledValue } from '../../hooks';
13
- import { useToggle } from '../../hooks';
14
22
  import { resolveStateVariant } from '../../utils';
23
+ import { extractSubcomponents } from '../../utils/extractSubcomponents';
15
24
  import { Chip } from '../Chip';
16
25
  import { Icon } from '../Icon';
17
26
  import { IconButton } from '../IconButton';
@@ -24,6 +33,7 @@ import {
24
33
  SelectSearchContextProvider,
25
34
  useSelectContextValue,
26
35
  useSelectDropdownContextValue,
36
+ useSelectDropdownStoreRef,
27
37
  useSelectSearchContextValue,
28
38
  } from './context';
29
39
  import type {
@@ -35,21 +45,50 @@ import type {
35
45
  SelectSearchContextValue,
36
46
  SelectSearchInputProps,
37
47
  SelectSearchKey,
48
+ SelectTriggerOutlineProps,
38
49
  SelectTriggerProps,
39
50
  SelectValueProps,
40
51
  } from './types';
41
- import { collectWebSelectKeyboardOptionElements, styles, triggerStyles } from './utils';
52
+ import {
53
+ collectWebSelectKeyboardOptionElements,
54
+ selectOutlineStyles,
55
+ styles,
56
+ triggerStyles,
57
+ } from './utils';
42
58
 
43
59
  const emptyArr: unknown[] = [];
44
60
 
45
- const getDisplayLabel = (item: DefaultItemT, labelKey?: string) => {
61
+ export const getSelectTriggerState = ({
62
+ isOpen,
63
+ hovered,
64
+ disabled,
65
+ error,
66
+ }: {
67
+ isOpen: boolean;
68
+ hovered: boolean;
69
+ disabled: boolean;
70
+ error: boolean;
71
+ }) =>
72
+ resolveStateVariant({
73
+ focused: isOpen,
74
+ hovered,
75
+ disabled,
76
+ error,
77
+ hoveredAndFocused: hovered && isOpen,
78
+ errorFocused: error && isOpen,
79
+ errorHovered: error && hovered,
80
+ errorFocusedAndHovered: error && isOpen && hovered,
81
+ errorDisabled: error && disabled,
82
+ }) as any;
83
+
84
+ export const getDisplayLabel = (item: DefaultItemT, labelKey?: string) => {
46
85
  const itemLabelKey = typeof item.labelKey === 'string' ? item.labelKey : undefined;
47
86
  const key = labelKey ?? itemLabelKey ?? 'label';
48
87
  const value = item[key];
49
88
  return value == null ? String(item.id) : String(value);
50
89
  };
51
90
 
52
- const getNested = (item: unknown, path: string): unknown => {
91
+ export const getNested = (item: unknown, path: string): unknown => {
53
92
  if (item == null || typeof item !== 'object') return undefined;
54
93
  if (!path.includes('.')) return (item as Record<string, unknown>)[path];
55
94
  let val: unknown = item;
@@ -60,12 +99,12 @@ const getNested = (item: unknown, path: string): unknown => {
60
99
  return val;
61
100
  };
62
101
 
63
- const matchesByKey = (item: unknown, key: string, lowerQuery: string): boolean =>
102
+ export const matchesByKey = (item: unknown, key: string, lowerQuery: string): boolean =>
64
103
  String(getNested(item, key) ?? '')
65
104
  .toLowerCase()
66
105
  .includes(lowerQuery);
67
106
 
68
- const applySearch = <T extends object>(
107
+ export const applySearch = <T extends object>(
69
108
  items: T[],
70
109
  searchKey: SelectSearchKey<T> | undefined,
71
110
  query: string,
@@ -79,13 +118,13 @@ const applySearch = <T extends object>(
79
118
  return items.filter(item => keys.some(key => matchesByKey(item, key, lowerQuery)));
80
119
  };
81
120
 
82
- const SelectDropdownProvider = memo(
121
+ export const SelectDropdownProvider = memo(
83
122
  ({
84
123
  children,
85
124
  isOpen: isOpenProp,
86
125
  onClose: onCloseProp,
87
126
  }: {
88
- children: React.ReactNode;
127
+ children: ReactNode;
89
128
  isOpen?: boolean;
90
129
  onClose?: () => void;
91
130
  }) => {
@@ -217,7 +256,7 @@ export const SelectContent = typedMemo(
217
256
  },
218
257
  );
219
258
 
220
- export const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps) => {
259
+ export const SelectTrigger = memo(({ children, style, ...rest }: SelectTriggerProps) => {
221
260
  const { isOpen, onOpen, onClose, triggerRef, setTriggerLayout } = useSelectDropdownContextValue(
222
261
  state => ({
223
262
  isOpen: state.isOpen,
@@ -227,26 +266,28 @@ export const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps)
227
266
  setTriggerLayout: state.setTriggerLayout,
228
267
  }),
229
268
  );
269
+ const setSelectDropdownContext = useSelectDropdownStoreRef().set;
230
270
 
231
271
  const { disabled, error } = useSelectContextValue(state => ({
232
272
  disabled: state.disabled,
233
273
  error: state.error,
234
274
  }));
235
275
 
236
- const { hovered } = useActionState({ ref: triggerRef, actionsToListen: ['hover'] });
276
+ const [hovered, setHovered] = useState(false);
277
+
278
+ const { Select_TriggerOutline, rest: restChildren } = extractSubcomponents({
279
+ children,
280
+ allowedChildren: [{ name: 'Select_TriggerOutline', allowMultiple: false }] as const,
281
+ includeRest: true,
282
+ });
237
283
 
238
284
  triggerStyles.useVariants({
239
- state: resolveStateVariant({
240
- focused: isOpen,
285
+ state: getSelectTriggerState({
286
+ isOpen,
241
287
  hovered,
242
288
  disabled: !!disabled,
243
289
  error: !!error,
244
- hoveredAndFocused: hovered && isOpen,
245
- errorFocused: !!error && isOpen,
246
- errorHovered: !!error && hovered,
247
- errorFocusedAndHovered: !!error && isOpen && hovered,
248
- errorDisabled: !!error && !!disabled,
249
- }) as any,
290
+ }),
250
291
  });
251
292
 
252
293
  const handleLayout = useCallback(
@@ -266,29 +307,67 @@ export const SelectTrigger = ({ children, style, ...rest }: SelectTriggerProps)
266
307
  }
267
308
  }, [isOpen, onOpen, onClose, disabled]);
268
309
 
310
+ const handleHoverIn = useCallback(() => {
311
+ setHovered(true);
312
+ setSelectDropdownContext(() => ({ triggerHovered: true }));
313
+ }, [setSelectDropdownContext]);
314
+
315
+ const handleHoverOut = useCallback(() => {
316
+ setHovered(false);
317
+ setSelectDropdownContext(() => ({ triggerHovered: false }));
318
+ }, [setSelectDropdownContext]);
319
+
320
+ const outlineElement =
321
+ Select_TriggerOutline.length > 0 ? Select_TriggerOutline : <SelectTriggerOutline />;
322
+
269
323
  return (
270
324
  <Pressable
271
325
  ref={triggerRef}
272
326
  onPress={handlePress}
273
327
  onLayout={handleLayout}
328
+ onHoverIn={handleHoverIn}
329
+ onHoverOut={handleHoverOut}
274
330
  style={[triggerStyles.trigger, style]}
275
331
  accessibilityRole="combobox"
276
332
  accessibilityState={{ expanded: isOpen, disabled: !!disabled }}
277
333
  disabled={disabled}
278
334
  {...rest}>
279
- {children}
335
+ {restChildren}
280
336
  <Icon
281
337
  name={isOpen ? 'chevron-up' : 'chevron-down'}
282
338
  size={20}
283
339
  style={triggerStyles.triggerIcon}
284
340
  />
285
- <View style={triggerStyles.outline} />
341
+ {outlineElement}
286
342
  </Pressable>
287
343
  );
288
- };
344
+ });
289
345
 
290
346
  SelectTrigger.displayName = 'Select_Trigger';
291
347
 
348
+ export const SelectTriggerOutline = memo(({ style }: SelectTriggerOutlineProps) => {
349
+ const { isOpen, triggerHovered } = useSelectDropdownContextValue(state => ({
350
+ isOpen: state.isOpen,
351
+ triggerHovered: state.triggerHovered,
352
+ }));
353
+ const { disabled, error } = useSelectContextValue(state => ({
354
+ disabled: state.disabled,
355
+ error: state.error,
356
+ }));
357
+ selectOutlineStyles.useVariants({
358
+ state: getSelectTriggerState({
359
+ isOpen,
360
+ hovered: !!triggerHovered,
361
+ disabled: !!disabled,
362
+ error: !!error,
363
+ }),
364
+ });
365
+
366
+ return <View pointerEvents="none" style={[selectOutlineStyles.outline, style]} />;
367
+ });
368
+
369
+ SelectTriggerOutline.displayName = 'Select_TriggerOutline';
370
+
292
371
  export const SelectValue = memo(
293
372
  ({ placeholder, labelKey, renderValue, style, ...rest }: SelectValueProps) => {
294
373
  const { value, multiple, onRemove } = useSelectContextValue(state => ({
@@ -692,14 +771,4 @@ export const SelectSearchInput = memo(({ children, ...textInputProps }: SelectSe
692
771
 
693
772
  SelectSearchInput.displayName = 'Select_SearchInput';
694
773
 
695
- const SelectWithSubcomponents = Object.assign(SelectRoot, {
696
- Trigger: SelectTrigger,
697
- Value: SelectValue,
698
- Dropdown: SelectDropdown,
699
- Content: SelectContent,
700
- Option: SelectOption,
701
- SearchInput: SelectSearchInput,
702
- });
703
-
704
- export default SelectWithSubcomponents;
705
- export { SelectDropdownProvider };
774
+ export default SelectRoot;
@@ -1,6 +1,6 @@
1
+ import { createFastContext } from '@react-native-molecules/utils/fast-context';
1
2
  import type { View } from 'react-native';
2
3
 
3
- import { createFastContext } from '../../fast-context';
4
4
  import {
5
5
  ListContext,
6
6
  ListContextProvider,
@@ -24,6 +24,7 @@ export type SelectDropdownContextType = SelectDropdownContextValue & {
24
24
  triggerRef: React.RefObject<View> | null;
25
25
  triggerLayout: { width: number; height: number } | null;
26
26
  setTriggerLayout: (layout: { width: number; height: number }) => void;
27
+ triggerHovered?: boolean;
27
28
  };
28
29
 
29
30
  const selectDropdownContextDefaultValue: SelectDropdownContextType = {
@@ -33,6 +34,7 @@ const selectDropdownContextDefaultValue: SelectDropdownContextType = {
33
34
  triggerRef: null,
34
35
  triggerLayout: null,
35
36
  setTriggerLayout: () => {},
37
+ triggerHovered: false,
36
38
  };
37
39
 
38
40
  const {
@@ -1,7 +1,25 @@
1
1
  import { getRegisteredComponentWithFallback } from '../../core';
2
- import SelectDefault from './Select';
2
+ import SelectRoot, {
3
+ SelectContent,
4
+ SelectDropdown,
5
+ SelectOption,
6
+ SelectSearchInput,
7
+ SelectTrigger,
8
+ SelectTriggerOutline,
9
+ SelectValue,
10
+ } from './Select';
3
11
 
4
- export const Select = getRegisteredComponentWithFallback('Select', SelectDefault);
12
+ const SelectWithSubcomponents = Object.assign(SelectRoot, {
13
+ Trigger: SelectTrigger,
14
+ TriggerOutline: SelectTriggerOutline,
15
+ Value: SelectValue,
16
+ Dropdown: SelectDropdown,
17
+ Content: SelectContent,
18
+ Option: SelectOption,
19
+ SearchInput: SelectSearchInput,
20
+ });
21
+
22
+ export const Select = getRegisteredComponentWithFallback('Select', SelectWithSubcomponents);
5
23
 
6
24
  export * from './context';
7
25
  export type * from './types';
@@ -82,6 +82,8 @@ export type SelectTriggerProps = ViewProps & {
82
82
  children?: ReactNode;
83
83
  };
84
84
 
85
+ export type SelectTriggerOutlineProps = ViewProps;
86
+
85
87
  // Select.Value props
86
88
  export type SelectValueProps = ViewProps & {
87
89
  placeholder?: string;