@helpwave/hightide-native 0.0.1 → 0.0.5

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 (89) hide show
  1. package/README.md +56 -0
  2. package/package.json +3 -3
  3. package/src/components/chat/ChatAttachmentCard.tsx +110 -0
  4. package/src/components/chat/ChatConversationList.tsx +72 -0
  5. package/src/components/chat/ChatConversationRow.tsx +153 -0
  6. package/src/components/chat/ChatDateDivider.tsx +55 -0
  7. package/src/components/chat/ChatMessageBubble.tsx +112 -0
  8. package/src/components/chat/ChatMessageCard.tsx +118 -0
  9. package/src/components/chat/ChatMessageComposer.tsx +115 -0
  10. package/src/components/chat/ChatMessageList.tsx +66 -0
  11. package/src/components/chat/ChatQuickReplyChip.tsx +72 -0
  12. package/src/components/chat/ChatSystemLine.tsx +68 -0
  13. package/src/components/chat/ChatThreadHeader.tsx +90 -0
  14. package/src/components/chat/index.ts +11 -0
  15. package/src/components/index.ts +4 -7
  16. package/src/components/menu/Menu.tsx +61 -0
  17. package/src/components/menu/MenuActionItem.tsx +90 -0
  18. package/src/components/menu/MenuItem.tsx +73 -0
  19. package/src/components/menu/MenuNavigationItem.tsx +90 -0
  20. package/src/components/menu/index.ts +4 -0
  21. package/src/components/user-interaction/Button.tsx +101 -0
  22. package/src/components/{Checkbox → user-interaction}/Checkbox.tsx +38 -16
  23. package/src/components/user-interaction/IconButton.tsx +95 -0
  24. package/src/components/{Input → user-interaction}/Input.tsx +35 -14
  25. package/src/components/{MultiSelect → user-interaction}/MultiSelect.tsx +47 -15
  26. package/src/components/{Select → user-interaction}/Select.tsx +44 -12
  27. package/src/components/user-interaction/index.ts +6 -0
  28. package/src/components/{Chip → visualization-and-display}/Chip.tsx +36 -20
  29. package/src/components/visualization-and-display/Icon.tsx +27 -0
  30. package/src/components/{Chip → visualization-and-display}/index.ts +1 -0
  31. package/src/global-contexts/HightideContext.ts +4 -1
  32. package/src/global-contexts/HightideProvider.tsx +22 -10
  33. package/src/global-contexts/hightide-config/HightideConfigUtils.ts +15 -12
  34. package/src/global-contexts/localization/LocalizationProvider.tsx +4 -2
  35. package/src/global-contexts/localization/forward-exports.ts +1 -0
  36. package/src/global-contexts/theme/ThemeContext.ts +11 -6
  37. package/src/global-contexts/theme/ThemeProvider.tsx +35 -38
  38. package/src/hooks/useMultiSelect.ts +1 -0
  39. package/src/hooks/useNativeKeyValueStore.ts +7 -1
  40. package/src/hooks/useSelect.ts +1 -0
  41. package/src/theme/resolvers/button.ts +38 -23
  42. package/src/theme/resolvers/chat.ts +418 -0
  43. package/src/theme/resolvers/checkbox.ts +26 -21
  44. package/src/theme/resolvers/chip.ts +32 -22
  45. package/src/theme/resolvers/coloring.ts +21 -17
  46. package/src/theme/resolvers/iconButton.ts +28 -18
  47. package/src/theme/resolvers/index.ts +2 -0
  48. package/src/theme/resolvers/input.ts +28 -19
  49. package/src/theme/resolvers/menu.ts +120 -0
  50. package/src/theme/resolvers/multiSelect.ts +39 -34
  51. package/src/theme/resolvers/select.ts +35 -30
  52. package/src/theme/themes/createTheme.ts +44 -15
  53. package/src/theme/themes/nativeThemes.ts +7 -6
  54. package/src/theme/types/color.ts +60 -0
  55. package/src/theme/types/components/button.ts +30 -0
  56. package/src/theme/types/components/chat.ts +198 -0
  57. package/src/theme/types/components/checkbox.ts +33 -0
  58. package/src/theme/types/components/chip.ts +30 -0
  59. package/src/theme/types/components/hightide.ts +42 -0
  60. package/src/theme/types/components/iconButton.ts +30 -0
  61. package/src/theme/types/components/index.ts +10 -0
  62. package/src/theme/types/components/input.ts +19 -0
  63. package/src/theme/types/components/menu.ts +57 -0
  64. package/src/theme/types/components/multiSelect.ts +47 -0
  65. package/src/theme/types/components/select.ts +47 -0
  66. package/src/theme/types/decoration.ts +11 -0
  67. package/src/theme/types/index.ts +4 -7
  68. package/src/theme/types/layout.ts +46 -0
  69. package/src/theme/types/resolver.ts +43 -1
  70. package/src/theme/types/theme.ts +14 -10
  71. package/src/theme/types/typography.ts +18 -0
  72. package/src/components/Button/Button.tsx +0 -101
  73. package/src/components/Button/index.ts +0 -1
  74. package/src/components/Checkbox/index.ts +0 -1
  75. package/src/components/IconButton/IconButton.tsx +0 -89
  76. package/src/components/IconButton/index.ts +0 -1
  77. package/src/components/Input/index.ts +0 -1
  78. package/src/components/MultiSelect/index.ts +0 -1
  79. package/src/components/Select/index.ts +0 -1
  80. package/src/icons/Icon.tsx +0 -35
  81. package/src/icons/index.ts +0 -1
  82. package/src/theme/types/button.ts +0 -41
  83. package/src/theme/types/checkbox.ts +0 -36
  84. package/src/theme/types/chip.ts +0 -41
  85. package/src/theme/types/components.ts +0 -17
  86. package/src/theme/types/iconButton.ts +0 -34
  87. package/src/theme/types/input.ts +0 -25
  88. package/src/theme/types/multiSelect.ts +0 -65
  89. package/src/theme/types/select.ts +0 -77
@@ -0,0 +1,90 @@
1
+ import {
2
+ Fragment,
3
+ useMemo,
4
+ type ReactNode
5
+ } from 'react'
6
+ import {
7
+ Pressable,
8
+ Text,
9
+ View,
10
+ type PressableProps,
11
+ type StyleProp,
12
+ type ViewStyle
13
+ } from 'react-native'
14
+ import { ChevronRight } from 'lucide-react-native'
15
+
16
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
17
+ import type {
18
+ MenuActionItemLabelStyle,
19
+ MenuActionItemState,
20
+ MenuActionItemStyle
21
+ } from '@/src/theme/types/components/menu'
22
+ import type { StyleOverwrite } from '@/src/theme/types/resolver'
23
+
24
+ export type MenuNavigationItemProps = Omit<PressableProps, 'children' | 'style'> & {
25
+ label: string,
26
+ leading?: ReactNode,
27
+ style?: StyleProp<ViewStyle>,
28
+ itemStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemStyle>,
29
+ labelStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemLabelStyle>,
30
+ }
31
+
32
+ type PressableInteraction = {
33
+ pressed: boolean,
34
+ hovered?: boolean,
35
+ focused?: boolean,
36
+ }
37
+
38
+ export const MenuNavigationItem = ({
39
+ label,
40
+ leading,
41
+ disabled,
42
+ style,
43
+ itemStyle,
44
+ labelStyle,
45
+ ...props
46
+ }: MenuNavigationItemProps) => {
47
+ const { theme } = useTheme()
48
+
49
+ const resolveState = (interaction: PressableInteraction): MenuActionItemState => ({
50
+ isDisabled: !!disabled,
51
+ isPressed: interaction.pressed,
52
+ isHovered: !!interaction.hovered,
53
+ isFocused: !!interaction.focused,
54
+ })
55
+
56
+ const resolvedContentStyle = useMemo(
57
+ () => theme.components.menu.navigationItemContent({}),
58
+ [theme]
59
+ )
60
+ const trailingColor = useMemo(
61
+ () => theme.components.menu.navigationItemTrailing({}).color,
62
+ [theme]
63
+ )
64
+
65
+ return (
66
+ <Pressable
67
+ {...props}
68
+ disabled={disabled}
69
+ style={(pressableState) => {
70
+ const state = resolveState(pressableState as PressableInteraction)
71
+ return [theme.components.menu.navigationItem(state, itemStyle), style]
72
+ }}
73
+ >
74
+ {(pressableState) => {
75
+ const state = resolveState(pressableState as PressableInteraction)
76
+ const resolvedLabelStyle = theme.components.menu.navigationItemLabel(state, labelStyle)
77
+
78
+ return (
79
+ <Fragment>
80
+ {leading}
81
+ <View style={resolvedContentStyle}>
82
+ <Text style={resolvedLabelStyle}>{label}</Text>
83
+ </View>
84
+ <ChevronRight size={16} color={trailingColor} />
85
+ </Fragment>
86
+ )
87
+ }}
88
+ </Pressable>
89
+ )
90
+ }
@@ -0,0 +1,4 @@
1
+ export * from './Menu'
2
+ export * from './MenuActionItem'
3
+ export * from './MenuItem'
4
+ export * from './MenuNavigationItem'
@@ -0,0 +1,101 @@
1
+ import {
2
+ forwardRef,
3
+ type ReactNode
4
+ } from 'react'
5
+ import {
6
+ Pressable,
7
+ Text,
8
+ type PressableProps,
9
+ type StyleProp,
10
+ type ViewStyle
11
+ } from 'react-native'
12
+
13
+ import {
14
+ coloringTypes,
15
+ type ColoringType
16
+ } from '@helpwave/hightide-design/helpers'
17
+ import type {
18
+ ButtonColoringStyle,
19
+ ElementSize
20
+ } from '@helpwave/hightide-design/types'
21
+
22
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
23
+ import type {
24
+ ButtonState,
25
+ ButtonStyle,
26
+ ButtonTextStyle
27
+ } from '@/src/theme/types/components/button'
28
+ import type { StyleOverwrite } from '@/src/theme/types/resolver'
29
+
30
+ export type ButtonSize = ElementSize
31
+
32
+ export type ButtonColor = ColoringType
33
+
34
+ export const ButtonUtil = {
35
+ colors: coloringTypes,
36
+ sizes: ['xs', 'sm', 'md', 'lg'] as const satisfies readonly ElementSize[],
37
+ coloringStyles: ['outline', 'solid', 'text', 'tonal', 'tonal-outline'] as const satisfies readonly ButtonColoringStyle[],
38
+ }
39
+
40
+ export type ButtonProps = Omit<PressableProps, 'children' | 'style'> & {
41
+ size?: ButtonSize,
42
+ color?: ButtonColor,
43
+ coloringStyle?: ButtonColoringStyle,
44
+ children?: ReactNode,
45
+ style?: StyleProp<ViewStyle>,
46
+ buttonStyle?: StyleOverwrite<ButtonState, ButtonStyle>,
47
+ textStyle?: StyleOverwrite<ButtonState, ButtonTextStyle>,
48
+ }
49
+
50
+ type PressableInteraction = {
51
+ pressed: boolean,
52
+ hovered?: boolean,
53
+ focused?: boolean,
54
+ }
55
+
56
+ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonProps>(function Button({
57
+ children,
58
+ size = 'md',
59
+ color = 'primary',
60
+ coloringStyle = 'solid',
61
+ disabled,
62
+ style,
63
+ buttonStyle,
64
+ textStyle,
65
+ ...props
66
+ }, ref) {
67
+ const { theme } = useTheme()
68
+
69
+ const resolveState = (interaction: PressableInteraction): ButtonState => ({
70
+ size,
71
+ color,
72
+ coloringStyle,
73
+ isDisabled: !!disabled,
74
+ isPressed: interaction.pressed,
75
+ isHovered: !!interaction.hovered,
76
+ isFocused: !!interaction.focused,
77
+ })
78
+
79
+ return (
80
+ <Pressable
81
+ {...props}
82
+ ref={ref}
83
+ disabled={disabled}
84
+ style={(pressableState) => {
85
+ const state = resolveState(pressableState as PressableInteraction)
86
+ return [theme.components.button.button(state, buttonStyle), style]
87
+ }}
88
+ >
89
+ {(pressableState) => {
90
+ const state = resolveState(pressableState as PressableInteraction)
91
+ const resolvedText = theme.components.button.text(state, textStyle)
92
+
93
+ if (typeof children === 'string' || typeof children === 'number') {
94
+ return <Text style={resolvedText}>{children}</Text>
95
+ }
96
+
97
+ return children
98
+ }}
99
+ </Pressable>
100
+ )
101
+ })
@@ -1,16 +1,35 @@
1
- import { useControlledState, useEventCallbackStabilizer } from '@helpwave/hightide-utils/hooks'
2
- import { Check, Minus } from 'lucide-react-native'
3
- import { useCallback } from 'react'
1
+ import {
2
+ useCallback,
3
+ useMemo
4
+ } from 'react'
4
5
  import {
5
6
  Pressable,
6
7
  type PressableProps,
7
8
  type StyleProp,
8
9
  type ViewStyle
9
10
  } from 'react-native'
10
- import { Icon } from '../../icons/Icon'
11
- import { useTheme } from '../../global-contexts/theme'
12
- import type { CheckboxSize, CheckboxState, CheckboxStyle } from '../../theme'
13
- import type { FormFieldDataHandling, FormFieldInteractionStates } from '../../types/formField'
11
+ import {
12
+ Check,
13
+ Minus
14
+ } from 'lucide-react-native'
15
+
16
+ import {
17
+ useControlledState,
18
+ useEventCallbackStabilizer
19
+ } from '@helpwave/hightide-utils/hooks'
20
+
21
+ import { Icon } from '@/src/components/visualization-and-display/Icon'
22
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
23
+ import type {
24
+ CheckboxSize,
25
+ CheckboxState,
26
+ CheckboxStyle
27
+ } from '@/src/theme/types/components/checkbox'
28
+ import type { StyleOverwrite } from '@/src/theme/types/resolver'
29
+ import type {
30
+ FormFieldDataHandling,
31
+ FormFieldInteractionStates
32
+ } from '@/src/types/formField'
14
33
 
15
34
  export type { CheckboxSize }
16
35
 
@@ -24,7 +43,7 @@ export type CheckboxProps = Omit<PressableProps, 'children' | 'style'>
24
43
  alwaysShowCheckIcon?: boolean,
25
44
  isRounded?: boolean,
26
45
  style?: StyleProp<ViewStyle>,
27
- checkboxStyle?: StyleProp<ViewStyle> | ((style: CheckboxStyle) => StyleProp<ViewStyle>),
46
+ checkboxStyle?: StyleOverwrite<CheckboxState, CheckboxStyle>,
28
47
  }
29
48
 
30
49
  export const Checkbox = ({
@@ -60,7 +79,7 @@ export const Checkbox = ({
60
79
  defaultValue: initialValue,
61
80
  })
62
81
 
63
- const state: CheckboxState = {
82
+ const state = useMemo((): CheckboxState => ({
64
83
  size,
65
84
  isChecked: value,
66
85
  isIndeterminate: indeterminate,
@@ -68,13 +87,16 @@ export const Checkbox = ({
68
87
  isDisabled: disabled,
69
88
  isRounded,
70
89
  alwaysShowCheckIcon,
71
- }
90
+ }), [alwaysShowCheckIcon, disabled, indeterminate, invalid, isRounded, size, value])
72
91
 
73
- const resolvedCheckbox = theme.components.checkbox.checkbox(state)
74
- const resolvedIcon = theme.components.checkbox.icon(state)
75
- const appliedCheckboxStyle = typeof checkboxStyle === 'function'
76
- ? checkboxStyle(resolvedCheckbox)
77
- : [resolvedCheckbox, checkboxStyle]
92
+ const resolvedCheckboxStyle = useMemo(
93
+ () => theme.components.checkbox.checkbox(state, checkboxStyle),
94
+ [theme, state, checkboxStyle]
95
+ )
96
+ const resolvedIcon = useMemo(
97
+ () => theme.components.checkbox.icon(state),
98
+ [theme, state]
99
+ )
78
100
 
79
101
  return (
80
102
  <Pressable
@@ -91,7 +113,7 @@ export const Checkbox = ({
91
113
  }
92
114
  props.onPress?.(event)
93
115
  }}
94
- style={[appliedCheckboxStyle, style]}
116
+ style={[resolvedCheckboxStyle, style]}
95
117
  >
96
118
  {resolvedIcon.visible && (
97
119
  indeterminate
@@ -0,0 +1,95 @@
1
+ import {
2
+ forwardRef,
3
+ type ReactNode
4
+ } from 'react'
5
+ import {
6
+ Pressable,
7
+ type PressableProps,
8
+ type StyleProp,
9
+ type ViewStyle
10
+ } from 'react-native'
11
+ import type { LucideIcon } from 'lucide-react-native'
12
+
13
+ import type { ColoringType } from '@helpwave/hightide-design/helpers'
14
+ import type {
15
+ ButtonColoringStyle,
16
+ ElementSize
17
+ } from '@helpwave/hightide-design/types'
18
+
19
+ import { Icon } from '@/src/components/visualization-and-display/Icon'
20
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
21
+ import type {
22
+ IconButtonState,
23
+ IconButtonStyle
24
+ } from '@/src/theme/types/components/iconButton'
25
+ import type { StyleOverwrite } from '@/src/theme/types/resolver'
26
+
27
+ export type IconButtonSize = ElementSize
28
+
29
+ export type IconButtonProps = Omit<PressableProps, 'children' | 'style'> & {
30
+ size?: IconButtonSize,
31
+ color?: ColoringType,
32
+ coloringStyle?: ButtonColoringStyle,
33
+ icon?: LucideIcon,
34
+ iconSize?: ElementSize,
35
+ children?: ReactNode,
36
+ accessibilityLabel: string,
37
+ style?: StyleProp<ViewStyle>,
38
+ buttonStyle?: StyleOverwrite<IconButtonState, IconButtonStyle>,
39
+ }
40
+
41
+ type PressableInteraction = {
42
+ pressed: boolean,
43
+ hovered?: boolean,
44
+ focused?: boolean,
45
+ }
46
+
47
+ export const IconButton = forwardRef<React.ComponentRef<typeof Pressable>, IconButtonProps>(function IconButton({
48
+ children,
49
+ icon,
50
+ iconSize,
51
+ size = 'md',
52
+ color = 'neutral',
53
+ coloringStyle = 'solid',
54
+ disabled,
55
+ accessibilityLabel,
56
+ style,
57
+ buttonStyle,
58
+ ...props
59
+ }, ref) {
60
+ const { theme } = useTheme()
61
+
62
+ const resolveState = (interaction: PressableInteraction): IconButtonState => ({
63
+ size,
64
+ color,
65
+ coloringStyle,
66
+ isDisabled: !!disabled,
67
+ isPressed: interaction.pressed,
68
+ isHovered: !!interaction.hovered,
69
+ isFocused: !!interaction.focused,
70
+ })
71
+
72
+ return (
73
+ <Pressable
74
+ {...props}
75
+ ref={ref}
76
+ disabled={disabled}
77
+ accessibilityRole="button"
78
+ accessibilityLabel={accessibilityLabel}
79
+ style={(pressableState) => {
80
+ const state = resolveState(pressableState as PressableInteraction)
81
+ return [theme.components.iconButton.button(state, buttonStyle), style]
82
+ }}
83
+ >
84
+ {(pressableState) => {
85
+ const state = resolveState(pressableState as PressableInteraction)
86
+ const resolvedIcon = theme.components.iconButton.icon(state)
87
+
88
+ if (icon) {
89
+ return <Icon icon={icon} size={iconSize ?? size} color={resolvedIcon.color} />
90
+ }
91
+ return children
92
+ }}
93
+ </Pressable>
94
+ )
95
+ })
@@ -1,13 +1,30 @@
1
+ import {
2
+ forwardRef,
3
+ useMemo
4
+ } from 'react'
5
+ import {
6
+ TextInput,
7
+ type StyleProp,
8
+ type TextInputProps,
9
+ type TextStyle
10
+ } from 'react-native'
11
+
1
12
  import {
2
13
  useControlledState,
3
14
  useDelay,
4
15
  type UseDelayOptionsResolved
5
16
  } from '@helpwave/hightide-utils/hooks'
6
- import { forwardRef } from 'react'
7
- import { TextInput, type StyleProp, type TextInputProps, type TextStyle } from 'react-native'
8
- import { useTheme } from '../../global-contexts/theme'
9
- import type { InputState, InputStyle } from '../../theme'
10
- import type { FormFieldDataHandling, FormFieldInteractionStates } from '../../types/formField'
17
+
18
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
19
+ import type {
20
+ InputState,
21
+ InputStyle
22
+ } from '@/src/theme/types/components/input'
23
+ import type { StyleOverwrite } from '@/src/theme/types/resolver'
24
+ import type {
25
+ FormFieldDataHandling,
26
+ FormFieldInteractionStates
27
+ } from '@/src/types/formField'
11
28
 
12
29
  export type EditCompleteOptionsResolved = {
13
30
  onBlur: boolean,
@@ -31,7 +48,7 @@ export type InputProps = Omit<TextInputProps, 'value' | 'style'>
31
48
  editCompleteOptions?: EditCompleteOptions,
32
49
  initialValue?: string,
33
50
  style?: StyleProp<TextStyle>,
34
- inputStyle?: StyleProp<TextStyle> | ((style: InputStyle) => StyleProp<TextStyle>),
51
+ inputStyle?: StyleOverwrite<InputState, InputStyle>,
35
52
  }
36
53
 
37
54
  export const Input = forwardRef<TextInput, InputProps>(function Input({
@@ -68,16 +85,20 @@ export const Input = forwardRef<TextInput, InputProps>(function Input({
68
85
  disabled: !afterDelay || disabled || readOnly,
69
86
  })
70
87
 
71
- const state: InputState = {
88
+ const state = useMemo((): InputState => ({
72
89
  isDisabled: disabled,
73
90
  isInvalid: invalid,
74
91
  isReadOnly: readOnly,
75
- }
92
+ }), [disabled, invalid, readOnly])
76
93
 
77
- const resolvedInput = theme.components.input.input(state)
78
- const appliedInputStyle = typeof inputStyle === 'function'
79
- ? inputStyle(resolvedInput)
80
- : [resolvedInput, inputStyle]
94
+ const resolvedInputStyle = useMemo(
95
+ () => theme.components.input.input(state, inputStyle),
96
+ [theme, state, inputStyle]
97
+ )
98
+ const placeholderColor = useMemo(
99
+ () => theme.components.input.placeholderColor(state),
100
+ [theme, state]
101
+ )
81
102
 
82
103
  return (
83
104
  <TextInput
@@ -106,8 +127,8 @@ export const Input = forwardRef<TextInput, InputProps>(function Input({
106
127
  clearTimer()
107
128
  }
108
129
  }}
109
- placeholderTextColor={theme.components.input.placeholderColor(state)}
110
- style={[appliedInputStyle, style]}
130
+ placeholderTextColor={placeholderColor}
131
+ style={[resolvedInputStyle, style]}
111
132
  accessibilityState={{ disabled, selected: required }}
112
133
  />
113
134
  )
@@ -1,4 +1,3 @@
1
- import { Check } from 'lucide-react-native'
2
1
  import { useMemo } from 'react'
3
2
  import {
4
3
  FlatList,
@@ -10,12 +9,20 @@ import {
10
9
  type StyleProp,
11
10
  type ViewStyle
12
11
  } from 'react-native'
13
- import { Chip } from '../Chip/Chip'
14
- import { Icon } from '../../icons/Icon'
15
- import { useMultiSelect, type UseMultiSelectOption } from '../../hooks/useMultiSelect'
16
- import { useTheme } from '../../global-contexts/theme'
17
- import type { MultiSelectState } from '../../theme'
18
- import type { FormFieldDataHandling, FormFieldInteractionStates } from '../../types/formField'
12
+ import { Check } from 'lucide-react-native'
13
+
14
+ import { Chip } from '@/src/components/visualization-and-display/Chip'
15
+ import { Icon } from '@/src/components/visualization-and-display/Icon'
16
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
17
+ import {
18
+ useMultiSelect,
19
+ type UseMultiSelectOption
20
+ } from '@/src/hooks/useMultiSelect'
21
+ import type { MultiSelectState } from '@/src/theme/types/components/multiSelect'
22
+ import type {
23
+ FormFieldDataHandling,
24
+ FormFieldInteractionStates
25
+ } from '@/src/types/formField'
19
26
 
20
27
  export type MultiSelectOption = UseMultiSelectOption
21
28
 
@@ -60,23 +67,48 @@ export const MultiSelect = ({
60
67
  .map((option) => option.label ?? option.id)
61
68
  }, [multiSelect, options])
62
69
 
63
- const state: MultiSelectState = {
70
+ const state = useMemo((): MultiSelectState => ({
64
71
  isDisabled: disabled,
65
72
  isReadOnly: readOnly,
66
73
  isInvalid: invalid,
67
74
  isOpen: multiSelect.isOpen,
68
75
  hasSelections: selectedLabels.length > 0,
69
76
  hasValue: selectedLabels.length > 0,
70
- }
77
+ }), [disabled, invalid, multiSelect.isOpen, readOnly, selectedLabels.length])
71
78
 
72
79
  const multiSelectTheme = theme.components.multiSelect
73
80
 
81
+ const resolvedTriggerStyle = useMemo(
82
+ () => multiSelectTheme.trigger(state),
83
+ [multiSelectTheme, state]
84
+ )
85
+ const resolvedTriggerTextStyle = useMemo(
86
+ () => multiSelectTheme.triggerText(state),
87
+ [multiSelectTheme, state]
88
+ )
89
+ const resolvedOverlayStyle = useMemo(
90
+ () => multiSelectTheme.overlay(state),
91
+ [multiSelectTheme, state]
92
+ )
93
+ const resolvedMenuStyle = useMemo(
94
+ () => multiSelectTheme.menu(state),
95
+ [multiSelectTheme, state]
96
+ )
97
+ const resolvedSearchStyle = useMemo(
98
+ () => multiSelectTheme.search(state),
99
+ [multiSelectTheme, state]
100
+ )
101
+ const searchPlaceholderColor = useMemo(
102
+ () => multiSelectTheme.searchPlaceholderColor(state),
103
+ [multiSelectTheme, state]
104
+ )
105
+
74
106
  return (
75
107
  <View style={style}>
76
108
  <Pressable
77
109
  disabled={!interactive}
78
110
  onPress={() => multiSelect.toggleOpen()}
79
- style={multiSelectTheme.trigger(state)}
111
+ style={resolvedTriggerStyle}
80
112
  >
81
113
  {selectedLabels.length > 0
82
114
  ? (
@@ -88,7 +120,7 @@ export const MultiSelect = ({
88
120
  ))}
89
121
  </View>
90
122
  )
91
- : <Text style={multiSelectTheme.triggerText(state)}>{placeholder}</Text>}
123
+ : <Text style={resolvedTriggerTextStyle}>{placeholder}</Text>}
92
124
  </Pressable>
93
125
 
94
126
  <Modal
@@ -98,11 +130,11 @@ export const MultiSelect = ({
98
130
  onRequestClose={() => multiSelect.setIsOpen(false)}
99
131
  >
100
132
  <Pressable
101
- style={multiSelectTheme.overlay(state)}
133
+ style={resolvedOverlayStyle}
102
134
  onPress={() => multiSelect.setIsOpen(false)}
103
135
  >
104
136
  <Pressable
105
- style={multiSelectTheme.menu(state)}
137
+ style={resolvedMenuStyle}
106
138
  onPress={(event) => event.stopPropagation()}
107
139
  >
108
140
  {showSearch && (
@@ -110,8 +142,8 @@ export const MultiSelect = ({
110
142
  value={multiSelect.searchQuery}
111
143
  onChangeText={multiSelect.setSearchQuery}
112
144
  placeholder="Search…"
113
- placeholderTextColor={multiSelectTheme.searchPlaceholderColor(state)}
114
- style={multiSelectTheme.search(state)}
145
+ placeholderTextColor={searchPlaceholderColor}
146
+ style={resolvedSearchStyle}
115
147
  />
116
148
  )}
117
149
  <FlatList
@@ -9,10 +9,17 @@ import {
9
9
  type StyleProp,
10
10
  type ViewStyle
11
11
  } from 'react-native'
12
- import { useSelect, type UseSelectOption } from '../../hooks/useSelect'
13
- import { useTheme } from '../../global-contexts/theme'
14
- import type { SelectState } from '../../theme'
15
- import type { FormFieldDataHandling, FormFieldInteractionStates } from '../../types/formField'
12
+
13
+ import { useTheme } from '@/src/global-contexts/theme/ThemeContext'
14
+ import {
15
+ useSelect,
16
+ type UseSelectOption
17
+ } from '@/src/hooks/useSelect'
18
+ import type { SelectState } from '@/src/theme/types/components/select'
19
+ import type {
20
+ FormFieldDataHandling,
21
+ FormFieldInteractionStates
22
+ } from '@/src/types/formField'
16
23
 
17
24
  export type SelectOption<T extends string = string> = UseSelectOption & {
18
25
  value?: T,
@@ -58,24 +65,49 @@ export const Select = ({
58
65
  return selected?.label ?? placeholder
59
66
  }, [options, placeholder, select.value])
60
67
 
61
- const state: SelectState = {
68
+ const state = useMemo((): SelectState => ({
62
69
  isDisabled: disabled,
63
70
  isReadOnly: readOnly,
64
71
  isInvalid: invalid,
65
72
  isOpen: select.isOpen,
66
73
  hasValue: !!select.value,
67
- }
74
+ }), [disabled, invalid, readOnly, select.isOpen, select.value])
68
75
 
69
76
  const selectTheme = theme.components.select
70
77
 
78
+ const resolvedTriggerStyle = useMemo(
79
+ () => selectTheme.trigger(state),
80
+ [selectTheme, state]
81
+ )
82
+ const resolvedTriggerTextStyle = useMemo(
83
+ () => selectTheme.triggerText(state),
84
+ [selectTheme, state]
85
+ )
86
+ const resolvedOverlayStyle = useMemo(
87
+ () => selectTheme.overlay(state),
88
+ [selectTheme, state]
89
+ )
90
+ const resolvedMenuStyle = useMemo(
91
+ () => selectTheme.menu(state),
92
+ [selectTheme, state]
93
+ )
94
+ const resolvedSearchStyle = useMemo(
95
+ () => selectTheme.search(state),
96
+ [selectTheme, state]
97
+ )
98
+ const searchPlaceholderColor = useMemo(
99
+ () => selectTheme.searchPlaceholderColor(state),
100
+ [selectTheme, state]
101
+ )
102
+
71
103
  return (
72
104
  <View style={style}>
73
105
  <Pressable
74
106
  disabled={!interactive}
75
107
  onPress={() => select.toggleOpen()}
76
- style={selectTheme.trigger(state)}
108
+ style={resolvedTriggerStyle}
77
109
  >
78
- <Text style={selectTheme.triggerText(state)}>
110
+ <Text style={resolvedTriggerTextStyle}>
79
111
  {selectedLabel}
80
112
  </Text>
81
113
  </Pressable>
@@ -87,11 +119,11 @@ export const Select = ({
87
119
  onRequestClose={() => select.setIsOpen(false)}
88
120
  >
89
121
  <Pressable
90
- style={selectTheme.overlay(state)}
122
+ style={resolvedOverlayStyle}
91
123
  onPress={() => select.setIsOpen(false)}
92
124
  >
93
125
  <Pressable
94
- style={selectTheme.menu(state)}
126
+ style={resolvedMenuStyle}
95
127
  onPress={(event) => event.stopPropagation()}
96
128
  >
97
129
  {showSearch && (
@@ -99,8 +131,8 @@ export const Select = ({
99
131
  value={select.searchQuery}
100
132
  onChangeText={select.setSearchQuery}
101
133
  placeholder="Search…"
102
- placeholderTextColor={selectTheme.searchPlaceholderColor(state)}
103
- style={selectTheme.search(state)}
134
+ placeholderTextColor={searchPlaceholderColor}
135
+ style={resolvedSearchStyle}
104
136
  />
105
137
  )}
106
138
  <FlatList
@@ -0,0 +1,6 @@
1
+ export * from './Button'
2
+ export * from './Checkbox'
3
+ export * from './IconButton'
4
+ export * from './Input'
5
+ export * from './MultiSelect'
6
+ export * from './Select'