@helpwave/hightide-native 0.6.0 → 0.7.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 (86) hide show
  1. package/package.json +3 -3
  2. package/src/components/card/Card.tsx +2 -4
  3. package/src/components/chat/ChatAttachmentMessageBubble.tsx +152 -90
  4. package/src/components/chat/ChatConversationList.tsx +4 -12
  5. package/src/components/chat/ChatConversationRow.tsx +159 -164
  6. package/src/components/chat/ChatDateDivider.tsx +3 -8
  7. package/src/components/chat/ChatMessageBubble.tsx +51 -45
  8. package/src/components/chat/ChatMessageComposer.tsx +4 -12
  9. package/src/components/chat/ChatMessageList.tsx +3 -9
  10. package/src/components/chat/ChatQuickReplyChip.tsx +41 -27
  11. package/src/components/chat/ChatSystemLine.tsx +4 -12
  12. package/src/components/chat/ChatThreadHeader.tsx +136 -101
  13. package/src/components/layout/Divider.tsx +2 -4
  14. package/src/components/list/ListActionItem.tsx +57 -65
  15. package/src/components/list/ListItem.tsx +8 -28
  16. package/src/components/list/ListItemTextContent.tsx +4 -2
  17. package/src/components/list/ListNavigationItem.tsx +55 -63
  18. package/src/components/user-interaction/Button.tsx +43 -49
  19. package/src/components/user-interaction/Checkbox.tsx +47 -48
  20. package/src/components/user-interaction/IconButton.tsx +40 -42
  21. package/src/components/user-interaction/Input.tsx +69 -80
  22. package/src/components/user-interaction/MultiSelect/MultiSelect.tsx +40 -0
  23. package/src/components/user-interaction/MultiSelect/MultiSelectContext.tsx +70 -0
  24. package/src/components/user-interaction/MultiSelect/MultiSelectMenu.tsx +87 -0
  25. package/src/components/user-interaction/MultiSelect/MultiSelectOption.tsx +87 -0
  26. package/src/components/user-interaction/MultiSelect/MultiSelectRoot.tsx +194 -0
  27. package/src/components/user-interaction/MultiSelect/MultiSelectTrigger.tsx +123 -0
  28. package/src/components/user-interaction/MultiSelect/index.ts +6 -0
  29. package/src/components/user-interaction/SearchBar.tsx +11 -17
  30. package/src/components/user-interaction/Select/Select.tsx +40 -0
  31. package/src/components/user-interaction/Select/SelectContext.tsx +69 -0
  32. package/src/components/user-interaction/Select/SelectMenu.tsx +87 -0
  33. package/src/components/user-interaction/Select/SelectOption.tsx +77 -0
  34. package/src/components/user-interaction/Select/SelectRoot.tsx +186 -0
  35. package/src/components/user-interaction/Select/SelectTrigger.tsx +92 -0
  36. package/src/components/user-interaction/Select/index.ts +6 -0
  37. package/src/components/user-interaction/Switch.tsx +46 -98
  38. package/src/components/user-interaction/Textarea.tsx +165 -12
  39. package/src/components/user-interaction/ThemedPressable.tsx +48 -49
  40. package/src/components/visualization-and-display/Avatar.tsx +19 -17
  41. package/src/components/visualization-and-display/Chip.tsx +4 -12
  42. package/src/components/visualization-and-display/ThemedText.tsx +12 -5
  43. package/src/enums/chatMessageDirection.ts +18 -0
  44. package/src/enums/chatMessageStatus.ts +18 -0
  45. package/src/enums/hightideThemeModes.ts +18 -0
  46. package/src/enums/index.ts +7 -0
  47. package/src/enums/listItemContentOrder.ts +18 -0
  48. package/src/enums/themedTextAppearance.ts +18 -0
  49. package/src/enums/useMultiSelectFirstHighlightBehavior.ts +18 -0
  50. package/src/enums/useSelectFirstHighlightBehavior.ts +18 -0
  51. package/src/global-contexts/content-theme/ContentThemeProvider.tsx +9 -5
  52. package/src/global-contexts/theme/ThemeProvider.tsx +4 -1
  53. package/src/hooks/index.ts +2 -0
  54. package/src/hooks/useAnimatedStyleTransition.ts +460 -0
  55. package/src/hooks/useMemoizedTheme.ts +23 -0
  56. package/src/hooks/useMultiSelect.ts +3 -1
  57. package/src/hooks/useSelect.ts +3 -1
  58. package/src/icons/HightideIconRegistry.ts +2 -0
  59. package/src/theme/adapters/style-adapter-utils.ts +121 -27
  60. package/src/theme/resolvers/avatar.ts +48 -39
  61. package/src/theme/resolvers/button.ts +7 -17
  62. package/src/theme/resolvers/chat/attachment-message-bubble.ts +5 -6
  63. package/src/theme/resolvers/chat/conversation-row.ts +4 -6
  64. package/src/theme/resolvers/chat/message-bubble.ts +1 -2
  65. package/src/theme/resolvers/chat/message-composer.ts +1 -2
  66. package/src/theme/resolvers/chat/quick-reply-chip.ts +2 -3
  67. package/src/theme/resolvers/chat/system-line.ts +1 -2
  68. package/src/theme/resolvers/chat/thread-header.ts +3 -4
  69. package/src/theme/resolvers/checkbox.ts +2 -2
  70. package/src/theme/resolvers/chip.ts +1 -2
  71. package/src/theme/resolvers/iconButton.ts +4 -13
  72. package/src/theme/resolvers/input.ts +30 -14
  73. package/src/theme/resolvers/listItem.ts +2 -3
  74. package/src/theme/resolvers/multiSelect.ts +1 -2
  75. package/src/theme/resolvers/searchBar.ts +1 -2
  76. package/src/theme/resolvers/select.ts +1 -2
  77. package/src/theme/resolvers/themedPressable.ts +4 -15
  78. package/src/theme/themes/hightideThemes.ts +3 -1
  79. package/src/theme/types/components/chat.ts +6 -21
  80. package/src/theme/types/components/input.ts +3 -0
  81. package/src/theme/types/components/textarea.ts +1 -0
  82. package/src/theme/types/resolver.ts +4 -27
  83. package/src/utils/index.ts +1 -0
  84. package/src/utils/pressableInteraction.ts +7 -0
  85. package/src/components/user-interaction/MultiSelect.tsx +0 -282
  86. package/src/components/user-interaction/Select.tsx +0 -242
@@ -1,27 +1,28 @@
1
1
  import {
2
2
  useMemo,
3
+ useState,
3
4
  type ReactNode
4
5
  } from 'react'
6
+ import {
7
+ View,
8
+ type PressableProps
9
+ } from 'react-native'
5
10
 
6
11
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
7
- import {
8
- ThemedPressable,
9
- type ThemedPressableProps
10
- } from '../user-interaction/ThemedPressable'
12
+ import { useMemoizedTheme, useMemoizedThemeFactory } from '../../hooks/useMemoizedTheme'
11
13
  import { ThemedText } from '../visualization-and-display/ThemedText'
12
14
  import type {
13
15
  ChatQuickReplyChipState,
14
16
  PressableContainerStyle,
15
17
  PressableIconStyle,
16
18
  PressableState,
19
+ PressableStateLayerStyle,
17
20
  PressableTextStyle
18
21
  } from '../../theme/types/components/chat'
19
- import type { StyleOverwrite } from '../../theme/types/resolver'
22
+ import type { StyleOverwrite, StyleResolverFunction } from '../../theme/types/resolver'
23
+ import { ThemedPressable } from '../user-interaction'
20
24
 
21
- export type ChatQuickReplyChipProps = Omit<
22
- ThemedPressableProps,
23
- 'children' | 'containerStyle' | 'textStyle' | 'stateLayerStyle' | 'iconStyle'
24
- > & {
25
+ export type ChatQuickReplyChipProps = Omit<PressableProps, 'children' | 'style'> & {
25
26
  isActive?: boolean,
26
27
  children?: ReactNode,
27
28
  style?: StyleOverwrite<PressableState, PressableContainerStyle>,
@@ -29,41 +30,54 @@ export type ChatQuickReplyChipProps = Omit<
29
30
  iconStyle?: StyleOverwrite<PressableState, PressableIconStyle>,
30
31
  }
31
32
 
33
+ type QuickReplyChipPressableResolvers = {
34
+ container: StyleResolverFunction<PressableState, PressableContainerStyle>,
35
+ stateLayer: StyleResolverFunction<PressableState, PressableStateLayerStyle>,
36
+ text: StyleResolverFunction<PressableState, PressableTextStyle>,
37
+ icon: StyleResolverFunction<PressableState, PressableIconStyle>,
38
+ }
39
+
32
40
  export const ChatQuickReplyChip = ({
33
41
  isActive = false,
34
42
  children,
35
43
  disabled,
36
44
  style,
37
45
  textStyle,
38
- iconStyle,
39
46
  ...props
40
47
  }: ChatQuickReplyChipProps) => {
41
48
  const { theme } = useTheme()
49
+ const [isPressed, setIsPressed] = useState(false)
42
50
  const state = useMemo((): ChatQuickReplyChipState => ({ isActive }), [isActive])
43
- const pressableResolvers = useMemo(
44
- () => theme.components.chat.quickReplyChip.pressable(state),
45
- [theme, state]
46
- )
51
+ const pressableThemeState = useMemo((): PressableState => ({
52
+ isPressed,
53
+ isDisabled: !!disabled,
54
+ }), [disabled, isPressed])
55
+ const pressableResolvers = useMemoizedThemeFactory<
56
+ ChatQuickReplyChipState,
57
+ QuickReplyChipPressableResolvers
58
+ >(theme.components.chat.quickReplyChip.pressable, state)
59
+
60
+ const resolvedContainerStyle = useMemoizedTheme(pressableResolvers.container, pressableThemeState, style)
61
+ const resolvedStateLayerStyle = useMemoizedTheme(pressableResolvers.stateLayer, pressableThemeState)
62
+ const resolvedTextStyle = useMemoizedTheme(pressableResolvers.text, pressableThemeState, textStyle)
47
63
 
48
64
  return (
49
65
  <ThemedPressable
50
66
  {...props}
51
67
  disabled={disabled}
52
- style={(pressableState) => (
53
- pressableResolvers.container(pressableState, style)
54
- )}
55
- stateLayerStyle={(pressableState) => (
56
- pressableResolvers.stateLayer(pressableState)
57
- )}
58
- textStyle={(pressableState) => (
59
- pressableResolvers.text(pressableState, textStyle)
60
- )}
61
- iconStyle={(pressableState) => (
62
- pressableResolvers.icon(pressableState, iconStyle)
63
- )}
68
+ style={resolvedContainerStyle}
69
+ onPressIn={(event) => {
70
+ setIsPressed(true)
71
+ props.onPressIn?.(event)
72
+ }}
73
+ onPressOut={(event) => {
74
+ setIsPressed(false)
75
+ props.onPressOut?.(event)
76
+ }}
64
77
  >
78
+ <View pointerEvents="none" style={resolvedStateLayerStyle} />
65
79
  {typeof children === 'string' || typeof children === 'number' ? (
66
- <ThemedText>{children}</ThemedText>
80
+ <ThemedText style={resolvedTextStyle}>{children}</ThemedText>
67
81
  ) : (
68
82
  children
69
83
  )}
@@ -15,6 +15,7 @@ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
15
15
  import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
16
16
  import { ThemedText } from '../visualization-and-display/ThemedText'
17
17
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
18
+ import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
18
19
  import type {
19
20
  ChatSystemLineState,
20
21
  ChatSystemLineStyle,
@@ -43,18 +44,9 @@ export const ChatSystemLine = ({
43
44
  const { theme } = useTheme()
44
45
  const state = useMemo(() => ({ color }), [color])
45
46
 
46
- const resolvedLineStyle = useMemo(
47
- () => theme.components.chat.systemLine.container(state, lineStyle),
48
- [theme, state, lineStyle]
49
- )
50
- const resolvedTextStyle = useMemo(
51
- () => theme.components.chat.systemLine.text(state, textStyle),
52
- [theme, state, textStyle]
53
- )
54
- const resolvedIcon = useMemo(
55
- () => theme.components.chat.systemLine.icon(state),
56
- [theme, state]
57
- )
47
+ const resolvedLineStyle = useMemoizedTheme(theme.components.chat.systemLine.container, state, lineStyle)
48
+ const resolvedTextStyle = useMemoizedTheme(theme.components.chat.systemLine.text, state, textStyle)
49
+ const resolvedIcon = useMemoizedTheme(theme.components.chat.systemLine.icon, state)
58
50
 
59
51
  return (
60
52
  <View {...props} style={[resolvedLineStyle, style]}>
@@ -1,24 +1,28 @@
1
1
  import {
2
2
  useMemo,
3
+ useState,
3
4
  type ReactNode
4
5
  } from 'react'
5
6
  import {
7
+ StyleSheet,
6
8
  View,
9
+ type PressableProps,
7
10
  type StyleProp,
8
11
  type ViewProps,
9
12
  type ViewStyle
10
13
  } from 'react-native'
11
14
 
12
15
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
16
+ import { useMemoizedTheme, useMemoizedThemeFactory } from '../../hooks/useMemoizedTheme'
13
17
  import { ThemedText } from '../visualization-and-display/ThemedText'
14
18
  import {
15
19
  Avatar,
16
20
  type AvatarProps
17
21
  } from '../visualization-and-display/Avatar'
18
- import {
19
- ThemedPressable,
20
- type ThemedPressableProps
21
- } from '../user-interaction/ThemedPressable'
22
+ import type {
23
+ AvatarState,
24
+ AvatarThemeResolvers
25
+ } from '../../theme/types/components/avatar'
22
26
  import type {
23
27
  ChatThreadHeaderContentRowStyle,
24
28
  ChatThreadHeaderStyle,
@@ -28,6 +32,8 @@ import type {
28
32
  PressableState
29
33
  } from '../../theme/types/components/chat'
30
34
  import type { StyleOverwrite } from '../../theme/types/resolver'
35
+ import { ThemedPressable } from '../user-interaction'
36
+ import type { ThemedPressableState, ThemedPressableThemeResolvers } from '../../theme'
31
37
 
32
38
  export type ChatThreadHeaderProps = Omit<ViewProps, 'style'> & {
33
39
  avatar?: AvatarProps,
@@ -35,7 +41,7 @@ export type ChatThreadHeaderProps = Omit<ViewProps, 'style'> & {
35
41
  subtitle?: ReactNode,
36
42
  leftActions?: ReactNode,
37
43
  rightActions?: ReactNode,
38
- onPress?: ThemedPressableProps['onPress'],
44
+ onPress?: PressableProps['onPress'],
39
45
  disabled?: boolean,
40
46
  style?: StyleProp<ViewStyle>,
41
47
  headerStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderStyle>,
@@ -45,65 +51,146 @@ export type ChatThreadHeaderProps = Omit<ViewProps, 'style'> & {
45
51
  pressableContainerStyle?: StyleOverwrite<PressableState, PressableContainerStyle>,
46
52
  }
47
53
 
48
- export const ChatThreadHeader = ({
54
+ type ChatThreadHeaderPressableContentProps = {
55
+ avatar?: AvatarProps,
56
+ title: ReactNode,
57
+ subtitle?: ReactNode,
58
+ disabled?: boolean,
59
+ onPress?: PressableProps['onPress'],
60
+ contentRowStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderContentRowStyle>,
61
+ titleStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderTitleStyle>,
62
+ subtitleStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderSubtitleStyle>,
63
+ pressableContainerStyle?: StyleOverwrite<PressableState, PressableContainerStyle>,
64
+ }
65
+
66
+ const ChatThreadHeaderPressableContent = ({
49
67
  avatar,
50
68
  title,
51
69
  subtitle,
52
- leftActions,
53
- rightActions,
54
- onPress,
55
70
  disabled,
56
- style,
57
- headerStyle,
71
+ onPress,
58
72
  contentRowStyle,
59
73
  titleStyle,
60
74
  subtitleStyle,
61
75
  pressableContainerStyle,
62
- ...props
63
- }: ChatThreadHeaderProps) => {
76
+ }: ChatThreadHeaderPressableContentProps) => {
64
77
  const { theme } = useTheme()
65
- const state = useMemo(() => ({}), [])
78
+ const [isPressed, setIsPressed] = useState(false)
79
+ const staticState = useMemo(() => ({}), [])
66
80
  const resolvedAvatar = useMemo(() => ({
67
81
  ...avatar,
68
82
  }), [avatar])
69
83
 
70
- const resolvedHeaderStyle = useMemo(
71
- () => theme.components.chat.threadHeader.container(state, headerStyle),
72
- [theme, state, headerStyle]
73
- )
74
- const resolvedContentRowStyle = useMemo(
75
- () => theme.components.chat.threadHeader.contentRow(state, contentRowStyle),
76
- [theme, state, contentRowStyle]
77
- )
78
- const resolvedTitleStyle = useMemo(
79
- () => theme.components.chat.threadHeader.title(state, titleStyle),
80
- [theme, state, titleStyle]
81
- )
82
- const resolvedSubtitleStyle = useMemo(
83
- () => theme.components.chat.threadHeader.subtitle(state, subtitleStyle),
84
- [theme, state, subtitleStyle]
85
- )
86
- const pressableResolvers = useMemo(
87
- () => theme.components.chat.threadHeader.pressable(state),
88
- [theme, state]
89
- )
84
+ const pressableThemeState = useMemo((): PressableState => ({
85
+ isPressed,
86
+ isDisabled: !!disabled,
87
+ }), [disabled, isPressed])
88
+ const pressableResolvers = useMemoizedThemeFactory<
89
+ ThemedPressableState,
90
+ ThemedPressableThemeResolvers
91
+ >(theme.components.chat.threadHeader.pressable, staticState)
92
+ const resolvedContentRowStyle = useMemoizedTheme(theme.components.chat.threadHeader.contentRow, staticState, contentRowStyle)
93
+ const resolvedTitleStyle = useMemoizedTheme(theme.components.chat.threadHeader.title, staticState, titleStyle)
94
+ const resolvedSubtitleStyle = useMemoizedTheme(theme.components.chat.threadHeader.subtitle, staticState, subtitleStyle)
95
+ const resolvedContainerStyle = useMemoizedTheme(pressableResolvers.container, pressableThemeState, pressableContainerStyle)
96
+ const resolvedStateLayerStyle = useMemoizedTheme(pressableResolvers.stateLayer, pressableThemeState)
90
97
 
91
- const avatarTheme = useMemo(
92
- () => theme.components.chat.threadHeader.avatar({
98
+ const avatarTheme = useMemoizedThemeFactory<AvatarState, AvatarThemeResolvers>(
99
+ theme.components.chat.threadHeader.avatar,
100
+ {
93
101
  size: resolvedAvatar.size,
94
102
  color: resolvedAvatar.color,
95
- }),
96
- [theme, resolvedAvatar.size, resolvedAvatar.color]
103
+ }
97
104
  )
98
105
  const avatarSize = useMemo(() => {
99
106
  if (typeof resolvedAvatar.size === 'number' || typeof resolvedAvatar.size === 'string') {
100
107
  return resolvedAvatar.size
101
108
  }
102
109
 
103
- const width = avatarTheme.container({}).width
110
+ const width = StyleSheet.flatten(avatarTheme.container({})).width
104
111
  return typeof width === 'number' ? width : resolvedAvatar.size
105
112
  }, [avatarTheme, resolvedAvatar.size])
106
113
 
114
+ return (
115
+ <ThemedPressable
116
+ disabled={disabled}
117
+ onPress={onPress}
118
+ style={resolvedContainerStyle}
119
+ stateLayerStyle={resolvedStateLayerStyle}
120
+ onPressIn={() => setIsPressed(true)}
121
+ onPressOut={() => setIsPressed(false)}
122
+ >
123
+ <Avatar
124
+ {...resolvedAvatar}
125
+ size={avatarSize}
126
+ avatarStyle={(avatarState) => avatarTheme.container(
127
+ {
128
+ ...avatarState,
129
+ size: avatarSize,
130
+ },
131
+ resolvedAvatar.avatarStyle
132
+ )}
133
+ imageStyle={(avatarState) => avatarTheme.image(
134
+ {
135
+ ...avatarState,
136
+ size: avatarSize,
137
+ },
138
+ resolvedAvatar.imageStyle
139
+ )}
140
+ textStyle={(avatarState) => avatarTheme.text(
141
+ {
142
+ ...avatarState,
143
+ size: avatarSize,
144
+ },
145
+ resolvedAvatar.textStyle
146
+ )}
147
+ iconStyle={(avatarState) => avatarTheme.icon(
148
+ {
149
+ ...avatarState,
150
+ size: avatarSize,
151
+ },
152
+ resolvedAvatar.iconStyle
153
+ )}
154
+ />
155
+ <View style={resolvedContentRowStyle}>
156
+ {typeof title === 'string' || typeof title === 'number' ? (
157
+ <ThemedText style={resolvedTitleStyle} numberOfLines={1}>{title}</ThemedText>
158
+ ) : (
159
+ title
160
+ )}
161
+ {subtitle != null && (
162
+ typeof subtitle === 'string' || typeof subtitle === 'number' ? (
163
+ <ThemedText style={resolvedSubtitleStyle} numberOfLines={1}>{subtitle}</ThemedText>
164
+ ) : (
165
+ subtitle
166
+ )
167
+ )}
168
+ </View>
169
+ </ThemedPressable>
170
+ )
171
+ }
172
+
173
+ export const ChatThreadHeader = ({
174
+ avatar,
175
+ title,
176
+ subtitle,
177
+ leftActions,
178
+ rightActions,
179
+ onPress,
180
+ disabled,
181
+ style,
182
+ headerStyle,
183
+ contentRowStyle,
184
+ titleStyle,
185
+ subtitleStyle,
186
+ pressableContainerStyle,
187
+ ...props
188
+ }: ChatThreadHeaderProps) => {
189
+ const { theme } = useTheme()
190
+ const staticState = useMemo(() => ({}), [])
191
+
192
+ const resolvedHeaderStyle = useMemoizedTheme(theme.components.chat.threadHeader.container, staticState, headerStyle)
193
+
107
194
  return (
108
195
  <View {...props} style={[resolvedHeaderStyle, style]}>
109
196
  {leftActions != null && (
@@ -111,69 +198,17 @@ export const ChatThreadHeader = ({
111
198
  {leftActions}
112
199
  </View>
113
200
  )}
114
- <ThemedPressable
115
- onPress={onPress}
201
+ <ChatThreadHeaderPressableContent
202
+ avatar={avatar}
203
+ title={title}
204
+ subtitle={subtitle}
116
205
  disabled={disabled}
117
- style={(pressableState) => (
118
- pressableResolvers.container(pressableState, pressableContainerStyle)
119
- )}
120
- stateLayerStyle={(pressableState) => (
121
- pressableResolvers.stateLayer(pressableState)
122
- )}
123
- textStyle={(pressableState) => (
124
- pressableResolvers.text(pressableState)
125
- )}
126
- iconStyle={(pressableState) => (
127
- pressableResolvers.icon(pressableState)
128
- )}
129
- >
130
- <Avatar
131
- {...resolvedAvatar}
132
- size={avatarSize}
133
- avatarStyle={(avatarState) => avatarTheme.container(
134
- {
135
- ...avatarState,
136
- size: avatarSize,
137
- },
138
- resolvedAvatar.avatarStyle
139
- )}
140
- imageStyle={(avatarState) => avatarTheme.image(
141
- {
142
- ...avatarState,
143
- size: avatarSize,
144
- },
145
- resolvedAvatar.imageStyle
146
- )}
147
- textStyle={(avatarState) => avatarTheme.text(
148
- {
149
- ...avatarState,
150
- size: avatarSize,
151
- },
152
- resolvedAvatar.textStyle
153
- )}
154
- iconStyle={(avatarState) => avatarTheme.icon(
155
- {
156
- ...avatarState,
157
- size: avatarSize,
158
- },
159
- resolvedAvatar.iconStyle
160
- )}
161
- />
162
- <View style={resolvedContentRowStyle}>
163
- {typeof title === 'string' || typeof title === 'number' ? (
164
- <ThemedText style={resolvedTitleStyle} numberOfLines={1}>{title}</ThemedText>
165
- ) : (
166
- title
167
- )}
168
- {subtitle != null && (
169
- typeof subtitle === 'string' || typeof subtitle === 'number' ? (
170
- <ThemedText style={resolvedSubtitleStyle} numberOfLines={1}>{subtitle}</ThemedText>
171
- ) : (
172
- subtitle
173
- )
174
- )}
175
- </View>
176
- </ThemedPressable>
206
+ onPress={onPress}
207
+ contentRowStyle={contentRowStyle}
208
+ titleStyle={titleStyle}
209
+ subtitleStyle={subtitleStyle}
210
+ pressableContainerStyle={pressableContainerStyle}
211
+ />
177
212
  {rightActions != null && (
178
213
  <View style={{ flexDirection: 'row', alignItems: 'center', flexShrink: 0 }}>
179
214
  {rightActions}
@@ -13,6 +13,7 @@ import type { ColorToken } from '@helpwave/hightide-design/primitive-tokens'
13
13
  import type { DividerDirection } from '@helpwave/hightide-design/component-token-resolvers'
14
14
 
15
15
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
16
+ import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
16
17
  import type {
17
18
  DividerState,
18
19
  DividerStyle
@@ -45,10 +46,7 @@ export const Divider = forwardRef<View, DividerProps>(function Divider({
45
46
  margin,
46
47
  }), [direction, color, width, margin])
47
48
 
48
- const resolvedDividerStyle = useMemo(
49
- () => theme.components.divider.container(state, dividerStyle),
50
- [theme, state, dividerStyle]
51
- )
49
+ const resolvedDividerStyle = useMemoizedTheme(theme.components.divider.container, state, dividerStyle)
52
50
 
53
51
  return (
54
52
  <View
@@ -1,5 +1,6 @@
1
1
  import {
2
- Fragment,
2
+ useMemo,
3
+ useState,
3
4
  type ReactNode
4
5
  } from 'react'
5
6
  import {
@@ -14,6 +15,7 @@ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
14
15
 
15
16
  import { useDebugContext } from '../../global-contexts/debug'
16
17
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
18
+ import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
17
19
  import type {
18
20
  ListActionItemDescriptionStyle,
19
21
  ListActionItemState,
@@ -43,13 +45,6 @@ export type ListActionItemProps = Omit<PressableProps, 'children' | 'style'> & {
43
45
  subtitleStyle?: StyleOverwrite<ListActionItemState, ListActionItemDescriptionStyle>,
44
46
  }
45
47
 
46
- type PressableInteraction = {
47
- pressed: boolean,
48
- hovered?: boolean,
49
- focused?: boolean,
50
- focusVisible?: boolean,
51
- }
52
-
53
48
  export const ListActionItem = ({
54
49
  title,
55
50
  subtitle,
@@ -74,15 +69,21 @@ export const ListActionItem = ({
74
69
  hitSlop: providedHitSlop,
75
70
  onLayout: providedOnLayout,
76
71
  })
72
+ const [isPressed, setIsPressed] = useState(false)
77
73
 
78
- const resolveState = (interaction: PressableInteraction): ListActionItemState => ({
74
+ const resolvedState = useMemo((): ListActionItemState => ({
79
75
  color,
80
76
  isDisabled: !!disabled,
81
- isPressed: interaction.pressed,
82
- isHovered: !!interaction.hovered,
83
- isFocused: !!interaction.focused,
84
- isFocusVisible: !!interaction.focusVisible,
85
- })
77
+ isPressed,
78
+ }), [color, disabled, isPressed])
79
+
80
+ const resolvedItemStyle = useMemoizedTheme(theme.components.listItem.action.container, resolvedState, itemStyle)
81
+ const resolvedLeadingItemContainerStyle = useMemoizedTheme(theme.components.listItem.action.leadingItemContainer, resolvedState)
82
+ const resolvedContentStyle = useMemoizedTheme(theme.components.listItem.action.content, resolvedState)
83
+ const resolvedTrailingItemContainerStyle = useMemoizedTheme(theme.components.listItem.action.trailingItemContainer, resolvedState)
84
+ const resolvedTitleStyle = useMemoizedTheme(theme.components.listItem.action.titleText, resolvedState, titleStyle)
85
+ const resolvedSubtitleStyle = useMemoizedTheme(theme.components.listItem.action.descriptionText, resolvedState, subtitleStyle)
86
+ const resolvedIconStyle = useMemoizedTheme(theme.components.listItem.action.icon, resolvedState)
86
87
 
87
88
  return (
88
89
  <Pressable
@@ -90,59 +91,50 @@ export const ListActionItem = ({
90
91
  disabled={disabled}
91
92
  hitSlop={hitSlop}
92
93
  onLayout={onLayout}
93
- style={(pressableState) => {
94
- const state = resolveState(pressableState as PressableInteraction)
95
- return [theme.components.listItem.action.container(state, itemStyle), style]
94
+ style={[resolvedItemStyle, style]}
95
+ onPressIn={(event) => {
96
+ setIsPressed(true)
97
+ props.onPressIn?.(event)
96
98
  }}
97
- >
98
- {(pressableState) => {
99
- const state = resolveState(pressableState as PressableInteraction)
100
- const resolvedLeadingItemContainerStyle = theme.components.listItem.action.leadingItemContainer(state)
101
- const resolvedContentStyle = theme.components.listItem.action.content(state)
102
- const resolvedTrailingItemContainerStyle = theme.components.listItem.action.trailingItemContainer(state)
103
- const resolvedTitleStyle = theme.components.listItem.action.titleText(state, titleStyle)
104
- const resolvedSubtitleStyle = theme.components.listItem.action.descriptionText(state, subtitleStyle)
105
- const resolvedIconStyle = theme.components.listItem.action.icon(state)
106
-
107
- return (
108
- <Fragment>
109
- {hitBox.isVisualizing && (
110
- <View
111
- pointerEvents="none"
112
- style={createHitBoxOverlayStyle(hitSlop, hitBox.color)}
113
- />
114
- )}
115
- {leading != null && (
116
- <ListItemAccessory
117
- style={resolvedLeadingItemContainerStyle}
118
- foreground={color?.onColor}
119
- iconStyle={resolvedIconStyle}
120
- >
121
- {leading}
122
- </ListItemAccessory>
123
- )}
124
- <View style={resolvedContentStyle}>
125
- <ListItemTextContent
126
- title={title}
127
- subtitle={subtitle}
128
- content={content}
129
- contentOrder={contentOrder}
130
- titleStyle={resolvedTitleStyle}
131
- subtitleStyle={resolvedSubtitleStyle}
132
- />
133
- </View>
134
- {trailing != null && (
135
- <ListItemAccessory
136
- style={resolvedTrailingItemContainerStyle}
137
- foreground={color?.onColor}
138
- iconStyle={resolvedIconStyle}
139
- >
140
- {trailing}
141
- </ListItemAccessory>
142
- )}
143
- </Fragment>
144
- )
99
+ onPressOut={(event) => {
100
+ setIsPressed(false)
101
+ props.onPressOut?.(event)
145
102
  }}
103
+ >
104
+ {hitBox.isVisualizing && (
105
+ <View
106
+ pointerEvents="none"
107
+ style={createHitBoxOverlayStyle(hitSlop, hitBox.color)}
108
+ />
109
+ )}
110
+ {leading != null && (
111
+ <ListItemAccessory
112
+ style={resolvedLeadingItemContainerStyle}
113
+ foreground={color?.onColor}
114
+ iconStyle={resolvedIconStyle}
115
+ >
116
+ {leading}
117
+ </ListItemAccessory>
118
+ )}
119
+ <View style={resolvedContentStyle}>
120
+ <ListItemTextContent
121
+ title={title}
122
+ subtitle={subtitle}
123
+ content={content}
124
+ contentOrder={contentOrder}
125
+ titleStyle={resolvedTitleStyle}
126
+ subtitleStyle={resolvedSubtitleStyle}
127
+ />
128
+ </View>
129
+ {trailing != null && (
130
+ <ListItemAccessory
131
+ style={resolvedTrailingItemContainerStyle}
132
+ foreground={color?.onColor}
133
+ iconStyle={resolvedIconStyle}
134
+ >
135
+ {trailing}
136
+ </ListItemAccessory>
137
+ )}
146
138
  </Pressable>
147
139
  )
148
140
  }
@@ -12,6 +12,7 @@ import {
12
12
  import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
13
13
 
14
14
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
15
+ import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
15
16
  import type {
16
17
  ListItemDescriptionStyle,
17
18
  ListItemState,
@@ -58,34 +59,13 @@ export const ListItem = ({
58
59
  color,
59
60
  }), [color])
60
61
 
61
- const resolvedItemStyle = useMemo(
62
- () => theme.components.listItem.default.container(state, itemStyle),
63
- [theme, state, itemStyle]
64
- )
65
- const resolvedLeadingItemContainerStyle = useMemo(
66
- () => theme.components.listItem.default.leadingItemContainer(state),
67
- [theme, state]
68
- )
69
- const resolvedContentStyle = useMemo(
70
- () => theme.components.listItem.default.content(state),
71
- [theme, state]
72
- )
73
- const resolvedTrailingItemContainerStyle = useMemo(
74
- () => theme.components.listItem.default.trailingItemContainer(state),
75
- [theme, state]
76
- )
77
- const resolvedTitleStyle = useMemo(
78
- () => theme.components.listItem.default.titleText(state, titleStyle),
79
- [theme, state, titleStyle]
80
- )
81
- const resolvedSubtitleStyle = useMemo(
82
- () => theme.components.listItem.default.descriptionText(state, subtitleStyle),
83
- [theme, state, subtitleStyle]
84
- )
85
- const resolvedIconStyle = useMemo(
86
- () => theme.components.listItem.default.icon(state),
87
- [theme, state]
88
- )
62
+ const resolvedItemStyle = useMemoizedTheme(theme.components.listItem.default.container, state, itemStyle)
63
+ const resolvedLeadingItemContainerStyle = useMemoizedTheme(theme.components.listItem.default.leadingItemContainer, state)
64
+ const resolvedContentStyle = useMemoizedTheme(theme.components.listItem.default.content, state)
65
+ const resolvedTrailingItemContainerStyle = useMemoizedTheme(theme.components.listItem.default.trailingItemContainer, state)
66
+ const resolvedTitleStyle = useMemoizedTheme(theme.components.listItem.default.titleText, state, titleStyle)
67
+ const resolvedSubtitleStyle = useMemoizedTheme(theme.components.listItem.default.descriptionText, state, subtitleStyle)
68
+ const resolvedIconStyle = useMemoizedTheme(theme.components.listItem.default.icon, state)
89
69
 
90
70
  return (
91
71
  <View {...props} style={[resolvedItemStyle, style]}>