@helpwave/hightide-native 0.0.7 → 0.1.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 (125) hide show
  1. package/README.md +4 -4
  2. package/package.json +8 -19
  3. package/src/components/card/Card.tsx +41 -0
  4. package/src/components/card/index.ts +1 -0
  5. package/src/components/chat/ChatAttachmentMessageBubble.tsx +246 -0
  6. package/src/components/chat/ChatConversationRow.tsx +21 -16
  7. package/src/components/chat/ChatDateDivider.tsx +2 -2
  8. package/src/components/chat/ChatMessageBubble.tsx +62 -52
  9. package/src/components/chat/ChatMessageComposer.tsx +16 -14
  10. package/src/components/chat/ChatQuickReplyChip.tsx +41 -43
  11. package/src/components/chat/ChatSystemLine.tsx +14 -7
  12. package/src/components/chat/ChatThreadHeader.tsx +60 -6
  13. package/src/components/chat/index.ts +1 -2
  14. package/src/components/index.ts +3 -1
  15. package/src/components/layout/Divider.tsx +60 -0
  16. package/src/components/layout/index.ts +1 -0
  17. package/src/components/list/ListActionItem.tsx +99 -0
  18. package/src/components/list/ListItem.tsx +96 -0
  19. package/src/components/list/ListNavigationItem.tsx +102 -0
  20. package/src/components/list/index.ts +3 -0
  21. package/src/components/user-interaction/Button.tsx +80 -31
  22. package/src/components/user-interaction/Checkbox.tsx +72 -32
  23. package/src/components/user-interaction/IconButton.tsx +68 -30
  24. package/src/components/user-interaction/Input.tsx +61 -14
  25. package/src/components/user-interaction/MultiSelect.tsx +142 -75
  26. package/src/components/user-interaction/SearchBar.tsx +222 -0
  27. package/src/components/user-interaction/Select.tsx +129 -58
  28. package/src/components/user-interaction/Switch.tsx +207 -0
  29. package/src/components/user-interaction/ThemedPressable.tsx +136 -0
  30. package/src/components/user-interaction/index.ts +3 -0
  31. package/src/components/visualization-and-display/Avatar.tsx +112 -93
  32. package/src/components/visualization-and-display/Chip.tsx +22 -22
  33. package/src/components/visualization-and-display/ThemedIcon.tsx +72 -0
  34. package/src/components/visualization-and-display/ThemedText.tsx +38 -0
  35. package/src/components/visualization-and-display/index.ts +2 -1
  36. package/src/global-contexts/HightideProvider.tsx +19 -10
  37. package/src/global-contexts/content-theme/ContentThemeContext.ts +22 -0
  38. package/src/global-contexts/content-theme/ContentThemeProvider.tsx +32 -0
  39. package/src/global-contexts/content-theme/index.ts +2 -0
  40. package/src/global-contexts/debug/forward-exports.ts +10 -0
  41. package/src/global-contexts/debug/index.ts +1 -0
  42. package/src/global-contexts/index.ts +2 -0
  43. package/src/global-contexts/theme/ThemeProvider.tsx +7 -1
  44. package/src/icons/HightideIconRegistry.ts +33 -0
  45. package/src/icons/index.ts +2 -0
  46. package/src/icons/types.ts +11 -0
  47. package/src/theme/adapters/container-adapter.ts +323 -0
  48. package/src/theme/adapters/defined.ts +11 -0
  49. package/src/theme/adapters/icon-style-adapter.ts +10 -0
  50. package/src/theme/adapters/index.ts +4 -0
  51. package/src/theme/adapters/text-style-adapter.ts +16 -0
  52. package/src/theme/index.ts +1 -0
  53. package/src/theme/resolvers/avatar.ts +385 -186
  54. package/src/theme/resolvers/button.ts +50 -82
  55. package/src/theme/resolvers/card.ts +27 -0
  56. package/src/theme/resolvers/chat/attachment-message-bubble.ts +163 -0
  57. package/src/theme/resolvers/chat/chat-theme.ts +69 -0
  58. package/src/theme/resolvers/chat/conversation-list.ts +35 -0
  59. package/src/theme/resolvers/chat/conversation-row.ts +64 -0
  60. package/src/theme/resolvers/chat/date-divider.ts +31 -0
  61. package/src/theme/resolvers/chat/index.ts +11 -0
  62. package/src/theme/resolvers/chat/message-bubble.ts +57 -0
  63. package/src/theme/resolvers/chat/message-composer.ts +38 -0
  64. package/src/theme/resolvers/chat/message-list.ts +27 -0
  65. package/src/theme/resolvers/chat/quick-reply-chip.ts +80 -0
  66. package/src/theme/resolvers/chat/system-line.ts +41 -0
  67. package/src/theme/resolvers/chat/thread-header.ts +85 -0
  68. package/src/theme/resolvers/checkbox.ts +87 -74
  69. package/src/theme/resolvers/chip.ts +30 -67
  70. package/src/theme/resolvers/colorScheme.ts +136 -0
  71. package/src/theme/resolvers/divider.ts +34 -0
  72. package/src/theme/resolvers/icon.ts +21 -0
  73. package/src/theme/resolvers/iconButton.ts +45 -61
  74. package/src/theme/resolvers/index.ts +8 -2
  75. package/src/theme/resolvers/input.ts +64 -42
  76. package/src/theme/resolvers/listItem.ts +115 -0
  77. package/src/theme/resolvers/multiSelect.ts +159 -84
  78. package/src/theme/resolvers/searchBar.ts +121 -0
  79. package/src/theme/resolvers/select.ts +141 -66
  80. package/src/theme/resolvers/switch.ts +71 -0
  81. package/src/theme/resolvers/themedPressable.ts +54 -0
  82. package/src/theme/themes/createHightideTheme.ts +214 -49
  83. package/src/theme/themes/hightideThemes.ts +10 -6
  84. package/src/theme/types/color.ts +2 -59
  85. package/src/theme/types/components/avatar.ts +37 -36
  86. package/src/theme/types/components/button.ts +13 -14
  87. package/src/theme/types/components/card.ts +9 -0
  88. package/src/theme/types/components/chat.ts +124 -103
  89. package/src/theme/types/components/checkbox.ts +11 -12
  90. package/src/theme/types/components/chip.ts +8 -14
  91. package/src/theme/types/components/divider.ts +18 -0
  92. package/src/theme/types/components/hightide.ts +41 -40
  93. package/src/theme/types/components/iconButton.ts +19 -14
  94. package/src/theme/types/components/index.ts +6 -1
  95. package/src/theme/types/components/input.ts +17 -8
  96. package/src/theme/types/components/listItem.ts +86 -0
  97. package/src/theme/types/components/multiSelect.ts +14 -12
  98. package/src/theme/types/components/searchBar.ts +28 -0
  99. package/src/theme/types/components/select.ts +21 -9
  100. package/src/theme/types/components/switch.ts +22 -0
  101. package/src/theme/types/components/themedPressable.ts +31 -0
  102. package/src/theme/types/decoration.ts +1 -11
  103. package/src/theme/types/icongraphy.ts +8 -0
  104. package/src/theme/types/index.ts +2 -0
  105. package/src/theme/types/layout.ts +25 -57
  106. package/src/theme/types/resolver.ts +54 -1
  107. package/src/theme/types/semantics.ts +72 -0
  108. package/src/theme/types/theme.ts +26 -13
  109. package/src/theme/types/typography.ts +16 -17
  110. package/src/utils/hitBoxOverlay.ts +44 -0
  111. package/src/utils/index.ts +2 -0
  112. package/src/utils/minimumTouchTargetHitSlop.ts +68 -0
  113. package/src/components/chat/ChatAttachmentCard.tsx +0 -110
  114. package/src/components/chat/ChatMessageCard.tsx +0 -118
  115. package/src/components/menu/Menu.tsx +0 -61
  116. package/src/components/menu/MenuActionItem.tsx +0 -90
  117. package/src/components/menu/MenuItem.tsx +0 -73
  118. package/src/components/menu/MenuNavigationItem.tsx +0 -90
  119. package/src/components/menu/index.ts +0 -4
  120. package/src/components/visualization-and-display/Icon.tsx +0 -27
  121. package/src/storybook/helper.tsx +0 -36
  122. package/src/theme/resolvers/chat.ts +0 -418
  123. package/src/theme/resolvers/coloring.ts +0 -94
  124. package/src/theme/resolvers/menu.ts +0 -120
  125. package/src/theme/types/components/menu.ts +0 -57
@@ -0,0 +1,99 @@
1
+ import {
2
+ Fragment,
3
+ type ReactNode
4
+ } from 'react'
5
+ import {
6
+ Pressable,
7
+ View,
8
+ type PressableProps,
9
+ type StyleProp,
10
+ type ViewStyle
11
+ } from 'react-native'
12
+
13
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
14
+
15
+ import { useTheme } from '../../global-contexts/theme/ThemeContext'
16
+ import { ThemedText } from '../visualization-and-display/ThemedText'
17
+ import type {
18
+ ListActionItemState,
19
+ ListActionItemStyle,
20
+ ListActionItemTitleStyle
21
+ } from '../../theme/types/components/listItem'
22
+ import type { StyleOverwrite } from '../../theme/types/resolver'
23
+
24
+ export type ListActionItemProps = Omit<PressableProps, 'children' | 'style'> & {
25
+ label: string,
26
+ leading?: ReactNode,
27
+ trailing?: ReactNode,
28
+ color?: ColorPairToken,
29
+ style?: StyleProp<ViewStyle>,
30
+ itemStyle?: StyleOverwrite<ListActionItemState, ListActionItemStyle>,
31
+ labelStyle?: StyleOverwrite<ListActionItemState, ListActionItemTitleStyle>,
32
+ }
33
+
34
+ type PressableInteraction = {
35
+ pressed: boolean,
36
+ hovered?: boolean,
37
+ focused?: boolean,
38
+ focusVisible?: boolean,
39
+ }
40
+
41
+ export const ListActionItem = ({
42
+ label,
43
+ leading,
44
+ trailing,
45
+ color,
46
+ disabled,
47
+ style,
48
+ itemStyle,
49
+ labelStyle,
50
+ ...props
51
+ }: ListActionItemProps) => {
52
+ const { theme } = useTheme()
53
+
54
+ const resolveState = (interaction: PressableInteraction): ListActionItemState => ({
55
+ color,
56
+ isDisabled: !!disabled,
57
+ isPressed: interaction.pressed,
58
+ isHovered: !!interaction.hovered,
59
+ isFocused: !!interaction.focused,
60
+ isFocusVisible: !!interaction.focusVisible,
61
+ })
62
+
63
+ return (
64
+ <Pressable
65
+ {...props}
66
+ disabled={disabled}
67
+ style={(pressableState) => {
68
+ const state = resolveState(pressableState as PressableInteraction)
69
+ return [theme.components.listItem.action.container(state, itemStyle), style]
70
+ }}
71
+ >
72
+ {(pressableState) => {
73
+ const state = resolveState(pressableState as PressableInteraction)
74
+ const resolvedLeadingItemContainerStyle = theme.components.listItem.action.leadingItemContainer(state)
75
+ const resolvedContentStyle = theme.components.listItem.action.content(state)
76
+ const resolvedTrailingItemContainerStyle = theme.components.listItem.action.trailingItemContainer(state)
77
+ const resolvedLabelStyle = theme.components.listItem.action.titleText(state, labelStyle)
78
+
79
+ return (
80
+ <Fragment>
81
+ {leading != null && (
82
+ <View style={resolvedLeadingItemContainerStyle}>
83
+ {leading}
84
+ </View>
85
+ )}
86
+ <View style={resolvedContentStyle}>
87
+ <ThemedText style={resolvedLabelStyle}>{label}</ThemedText>
88
+ </View>
89
+ {trailing != null && (
90
+ <View style={resolvedTrailingItemContainerStyle}>
91
+ {trailing}
92
+ </View>
93
+ )}
94
+ </Fragment>
95
+ )
96
+ }}
97
+ </Pressable>
98
+ )
99
+ }
@@ -0,0 +1,96 @@
1
+ import {
2
+ useMemo,
3
+ type ReactNode
4
+ } from 'react'
5
+ import {
6
+ View,
7
+ type StyleProp,
8
+ type ViewProps,
9
+ type ViewStyle
10
+ } from 'react-native'
11
+
12
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
13
+
14
+ import { useTheme } from '../../global-contexts/theme/ThemeContext'
15
+ import { ThemedText } from '../visualization-and-display/ThemedText'
16
+ import type {
17
+ ListItemDescriptionStyle,
18
+ ListItemState,
19
+ ListItemStyle,
20
+ ListItemTitleStyle
21
+ } from '../../theme/types/components/listItem'
22
+ import type { StyleOverwrite } from '../../theme/types/resolver'
23
+
24
+ export type ListItemProps = Omit<ViewProps, 'style'> & {
25
+ label: string,
26
+ value: string,
27
+ leading?: ReactNode,
28
+ trailing?: ReactNode,
29
+ color?: ColorPairToken,
30
+ style?: StyleProp<ViewStyle>,
31
+ itemStyle?: StyleOverwrite<ListItemState, ListItemStyle>,
32
+ labelStyle?: StyleOverwrite<ListItemState, ListItemDescriptionStyle>,
33
+ valueStyle?: StyleOverwrite<ListItemState, ListItemTitleStyle>,
34
+ }
35
+
36
+ export const ListItem = ({
37
+ label,
38
+ value,
39
+ leading,
40
+ trailing,
41
+ color,
42
+ style,
43
+ itemStyle,
44
+ labelStyle,
45
+ valueStyle,
46
+ ...props
47
+ }: ListItemProps) => {
48
+ const { theme } = useTheme()
49
+ const state = useMemo((): ListItemState => ({
50
+ color,
51
+ }), [color])
52
+
53
+ const resolvedItemStyle = useMemo(
54
+ () => theme.components.listItem.default.container(state, itemStyle),
55
+ [theme, state, itemStyle]
56
+ )
57
+ const resolvedLeadingItemContainerStyle = useMemo(
58
+ () => theme.components.listItem.default.leadingItemContainer(state),
59
+ [theme, state]
60
+ )
61
+ const resolvedContentStyle = useMemo(
62
+ () => theme.components.listItem.default.content(state),
63
+ [theme, state]
64
+ )
65
+ const resolvedTrailingItemContainerStyle = useMemo(
66
+ () => theme.components.listItem.default.trailingItemContainer(state),
67
+ [theme, state]
68
+ )
69
+ const resolvedLabelStyle = useMemo(
70
+ () => theme.components.listItem.default.descriptionText(state, labelStyle),
71
+ [theme, state, labelStyle]
72
+ )
73
+ const resolvedValueStyle = useMemo(
74
+ () => theme.components.listItem.default.titleText(state, valueStyle),
75
+ [theme, state, valueStyle]
76
+ )
77
+
78
+ return (
79
+ <View {...props} style={[resolvedItemStyle, style]}>
80
+ {leading != null && (
81
+ <View style={resolvedLeadingItemContainerStyle}>
82
+ {leading}
83
+ </View>
84
+ )}
85
+ <View style={resolvedContentStyle}>
86
+ <ThemedText style={resolvedLabelStyle}>{label}</ThemedText>
87
+ <ThemedText style={resolvedValueStyle}>{value}</ThemedText>
88
+ </View>
89
+ {trailing != null && (
90
+ <View style={resolvedTrailingItemContainerStyle}>
91
+ {trailing}
92
+ </View>
93
+ )}
94
+ </View>
95
+ )
96
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ Fragment,
3
+ type ReactNode
4
+ } from 'react'
5
+ import {
6
+ Pressable,
7
+ View,
8
+ type PressableProps,
9
+ type StyleProp,
10
+ type ViewStyle
11
+ } from 'react-native'
12
+
13
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
14
+
15
+ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
16
+ import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
17
+ import { ThemedText } from '../visualization-and-display/ThemedText'
18
+ import { useTheme } from '../../global-contexts/theme/ThemeContext'
19
+ import type {
20
+ ListNavigationItemState,
21
+ ListNavigationItemStyle,
22
+ ListNavigationItemTitleStyle
23
+ } from '../../theme/types/components/listItem'
24
+ import type { StyleOverwrite } from '../../theme/types/resolver'
25
+
26
+ export type ListNavigationItemProps = Omit<PressableProps, 'children' | 'style'> & {
27
+ label: string,
28
+ leading?: ReactNode,
29
+ color?: ColorPairToken,
30
+ style?: StyleProp<ViewStyle>,
31
+ itemStyle?: StyleOverwrite<ListNavigationItemState, ListNavigationItemStyle>,
32
+ labelStyle?: StyleOverwrite<ListNavigationItemState, ListNavigationItemTitleStyle>,
33
+ }
34
+
35
+ type PressableInteraction = {
36
+ pressed: boolean,
37
+ hovered?: boolean,
38
+ focused?: boolean,
39
+ focusVisible?: boolean,
40
+ }
41
+
42
+ export const ListNavigationItem = ({
43
+ label,
44
+ leading,
45
+ color,
46
+ disabled,
47
+ style,
48
+ itemStyle,
49
+ labelStyle,
50
+ ...props
51
+ }: ListNavigationItemProps) => {
52
+ const { theme } = useTheme()
53
+
54
+ const resolveState = (interaction: PressableInteraction): ListNavigationItemState => ({
55
+ color,
56
+ isDisabled: !!disabled,
57
+ isPressed: interaction.pressed,
58
+ isHovered: !!interaction.hovered,
59
+ isFocused: !!interaction.focused,
60
+ isFocusVisible: !!interaction.focusVisible,
61
+ })
62
+
63
+ return (
64
+ <Pressable
65
+ {...props}
66
+ disabled={disabled}
67
+ style={(pressableState) => {
68
+ const state = resolveState(pressableState as PressableInteraction)
69
+ return [theme.components.listItem.navigation.container(state, itemStyle), style]
70
+ }}
71
+ >
72
+ {(pressableState) => {
73
+ const state = resolveState(pressableState as PressableInteraction)
74
+ const resolvedLeadingItemContainerStyle = theme.components.listItem.navigation.leadingItemContainer(state)
75
+ const resolvedContentStyle = theme.components.listItem.navigation.content(state)
76
+ const resolvedTrailingItemContainerStyle = theme.components.listItem.navigation.trailingItemContainer(state)
77
+ const resolvedLabelStyle = theme.components.listItem.navigation.titleText(state, labelStyle)
78
+ const resolvedIcon = theme.components.listItem.navigation.icon(state)
79
+
80
+ return (
81
+ <Fragment>
82
+ {leading != null && (
83
+ <View style={resolvedLeadingItemContainerStyle}>
84
+ {leading}
85
+ </View>
86
+ )}
87
+ <View style={resolvedContentStyle}>
88
+ <ThemedText style={resolvedLabelStyle}>{label}</ThemedText>
89
+ </View>
90
+ <View style={resolvedTrailingItemContainerStyle}>
91
+ <ThemedIcon
92
+ icon={HightideIconRegistry.ChevronRight}
93
+ size={resolvedIcon.size}
94
+ color={resolvedIcon.color}
95
+ />
96
+ </View>
97
+ </Fragment>
98
+ )
99
+ }}
100
+ </Pressable>
101
+ )
102
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ListActionItem'
2
+ export * from './ListItem'
3
+ export * from './ListNavigationItem'
@@ -1,49 +1,50 @@
1
- import {
2
- forwardRef,
3
- type ReactNode
4
- } from 'react'
1
+ import { Fragment, forwardRef } from 'react'
5
2
  import {
6
3
  Pressable,
7
- Text,
4
+ View,
8
5
  type PressableProps,
9
6
  type StyleProp,
10
7
  type ViewStyle
11
8
  } from 'react-native'
12
9
 
13
- import {
14
- coloringTypes,
15
- type ColoringType
16
- } from '@helpwave/hightide-design/utils'
17
- import type {
18
- ButtonColoringStyle,
19
- ElementSize
20
- } from '@helpwave/hightide-design/types'
10
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
11
+ import type { ComponentSize, ButtonVariant } from '@helpwave/hightide-design/semantic-token-resolvers'
21
12
 
13
+ import { ContentThemeProvider } from '../../global-contexts/content-theme/ContentThemeProvider'
14
+ import { useDebugContext } from '../../global-contexts/debug'
22
15
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
16
+ import type { IconComponent } from '../../icons/types'
23
17
  import type {
24
18
  ButtonState,
25
19
  ButtonStyle,
26
20
  ButtonTextStyle
27
21
  } from '../../theme/types/components/button'
28
22
  import type { StyleOverwrite } from '../../theme/types/resolver'
23
+ import type { Color } from '../../theme/types/color'
24
+ import { createHitBoxOverlayStyle } from '../../utils/hitBoxOverlay'
25
+ import { useMinimumTouchTargetHitSlop } from '../../utils/minimumTouchTargetHitSlop'
26
+ import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
27
+ import { ThemedText } from '../visualization-and-display/ThemedText'
29
28
 
30
- export type ButtonSize = ElementSize
29
+ export type ButtonSize = ComponentSize
31
30
 
32
- export type ButtonColor = ColoringType
31
+ export type ButtonColor = ColorPairToken
33
32
 
34
33
  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[],
34
+ sizes: ['xs', 'sm', 'md', 'lg', 'xl'] as const satisfies readonly ComponentSize[],
35
+ variants: ['elevated', 'filled', 'tonal', 'outlined', 'foreground'] as const satisfies readonly ButtonVariant[],
38
36
  }
39
37
 
40
38
  export type ButtonProps = Omit<PressableProps, 'children' | 'style'> & {
41
39
  size?: ButtonSize,
42
40
  color?: ButtonColor,
43
- coloringStyle?: ButtonColoringStyle,
44
- children?: ReactNode,
41
+ variant?: ButtonVariant,
42
+ children: string,
43
+ leadingIcon?: IconComponent,
44
+ trailingIcon?: IconComponent,
45
45
  style?: StyleProp<ViewStyle>,
46
- buttonStyle?: StyleOverwrite<ButtonState, ButtonStyle>,
46
+ containerStyle?: StyleOverwrite<ButtonState, ButtonStyle>,
47
+ stateLayerStyle?: StyleOverwrite<ButtonState, ButtonStyle>,
47
48
  textStyle?: StyleOverwrite<ButtonState, ButtonTextStyle>,
48
49
  }
49
50
 
@@ -51,29 +52,42 @@ type PressableInteraction = {
51
52
  pressed: boolean,
52
53
  hovered?: boolean,
53
54
  focused?: boolean,
55
+ focusVisible?: boolean,
54
56
  }
55
57
 
56
58
  export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonProps>(function Button({
57
59
  children,
58
60
  size = 'md',
59
- color = 'primary',
60
- coloringStyle = 'solid',
61
+ color,
62
+ variant = 'filled',
61
63
  disabled,
64
+ leadingIcon,
65
+ trailingIcon,
62
66
  style,
63
- buttonStyle,
67
+ containerStyle,
68
+ stateLayerStyle,
64
69
  textStyle,
70
+ hitSlop: providedHitSlop,
71
+ onLayout: providedOnLayout,
65
72
  ...props
66
73
  }, ref) {
67
74
  const { theme } = useTheme()
75
+ const { hitBox } = useDebugContext()
76
+ const { hitSlop, onLayout } = useMinimumTouchTargetHitSlop({
77
+ touchTargetSize: theme.semantics.touchTargetSize({}),
78
+ hitSlop: providedHitSlop,
79
+ onLayout: providedOnLayout,
80
+ })
68
81
 
69
82
  const resolveState = (interaction: PressableInteraction): ButtonState => ({
70
83
  size,
71
84
  color,
72
- coloringStyle,
85
+ variant,
73
86
  isDisabled: !!disabled,
74
87
  isPressed: interaction.pressed,
75
88
  isHovered: !!interaction.hovered,
76
89
  isFocused: !!interaction.focused,
90
+ isFocusVisible: !!interaction.focusVisible,
77
91
  })
78
92
 
79
93
  return (
@@ -81,20 +95,55 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
81
95
  {...props}
82
96
  ref={ref}
83
97
  disabled={disabled}
98
+ hitSlop={hitSlop}
99
+ onLayout={onLayout}
84
100
  style={(pressableState) => {
85
101
  const state = resolveState(pressableState as PressableInteraction)
86
- return [theme.components.button.button(state, buttonStyle), style]
102
+ return [
103
+ theme.components.button.container(state, containerStyle),
104
+ style,
105
+ ]
87
106
  }}
88
107
  >
89
108
  {(pressableState) => {
90
109
  const state = resolveState(pressableState as PressableInteraction)
91
110
  const resolvedText = theme.components.button.text(state, textStyle)
111
+ const resolvedIcon = theme.components.button.icon(state)
92
112
 
93
- if (typeof children === 'string' || typeof children === 'number') {
94
- return <Text style={resolvedText}>{children}</Text>
95
- }
96
-
97
- return children
113
+ return (
114
+ <Fragment>
115
+ {hitBox.isVisualizing && (
116
+ <View
117
+ pointerEvents="none"
118
+ style={createHitBoxOverlayStyle(hitSlop, hitBox.color)}
119
+ />
120
+ )}
121
+ <View
122
+ pointerEvents="none"
123
+ style={theme.components.button.stateLayer(state, stateLayerStyle)}
124
+ />
125
+ <ContentThemeProvider
126
+ foregroundColor={resolvedText.color as Color}
127
+ textStyle={resolvedText}
128
+ >
129
+ {leadingIcon !== undefined && (
130
+ <ThemedIcon
131
+ icon={leadingIcon}
132
+ size={resolvedIcon.size}
133
+ strokeWidth={resolvedIcon.strokeWidth}
134
+ />
135
+ )}
136
+ <ThemedText>{children}</ThemedText>
137
+ {trailingIcon !== undefined && (
138
+ <ThemedIcon
139
+ icon={trailingIcon}
140
+ size={resolvedIcon.size}
141
+ strokeWidth={resolvedIcon.strokeWidth}
142
+ />
143
+ )}
144
+ </ContentThemeProvider>
145
+ </Fragment>
146
+ )
98
147
  }}
99
148
  </Pressable>
100
149
  )
@@ -1,28 +1,28 @@
1
1
  import {
2
- useCallback,
3
- useMemo
2
+ Fragment,
3
+ useCallback
4
4
  } from 'react'
5
5
  import {
6
6
  Pressable,
7
+ View,
7
8
  type PressableProps,
8
9
  type StyleProp,
9
10
  type ViewStyle
10
11
  } from 'react-native'
11
- import {
12
- Check,
13
- Minus
14
- } from 'lucide-react-native'
15
-
16
12
  import {
17
13
  useControlledState,
18
14
  useEventCallbackStabilizer
19
15
  } from '@helpwave/hightide-utils/hooks'
20
16
 
21
- import { Icon } from '../visualization-and-display/Icon'
17
+ import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
18
+ import { HightideIconRegistry } from '../../icons/HightideIconRegistry'
19
+ import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
20
+ import { useDebugContext } from '../../global-contexts/debug'
22
21
  import { useTheme } from '../../global-contexts/theme/ThemeContext'
23
22
  import type {
24
23
  CheckboxSize,
25
24
  CheckboxState,
25
+ CheckboxStateLayerStyle,
26
26
  CheckboxStyle
27
27
  } from '../../theme/types/components/checkbox'
28
28
  import type { StyleOverwrite } from '../../theme/types/resolver'
@@ -30,8 +30,8 @@ import type {
30
30
  FormFieldDataHandling,
31
31
  FormFieldInteractionStates
32
32
  } from '../../types/formField'
33
-
34
- export type { CheckboxSize }
33
+ import { createHitBoxOverlayStyle } from '../../utils/hitBoxOverlay'
34
+ import { useMinimumTouchTargetHitSlop } from '../../utils/minimumTouchTargetHitSlop'
35
35
 
36
36
  export type CheckboxProps = Omit<PressableProps, 'children' | 'style'>
37
37
  & Partial<FormFieldInteractionStates>
@@ -40,12 +40,20 @@ export type CheckboxProps = Omit<PressableProps, 'children' | 'style'>
40
40
  initialValue?: boolean,
41
41
  indeterminate?: boolean,
42
42
  size?: CheckboxSize,
43
- alwaysShowCheckIcon?: boolean,
44
43
  isRounded?: boolean,
44
+ color?: ColorPairToken,
45
45
  style?: StyleProp<ViewStyle>,
46
- checkboxStyle?: StyleOverwrite<CheckboxState, CheckboxStyle>,
46
+ containerStyle?: StyleOverwrite<CheckboxState, CheckboxStyle>,
47
+ stateLayerStyle?: StyleOverwrite<CheckboxState, CheckboxStateLayerStyle>,
47
48
  }
48
49
 
50
+ type PressableInteraction = {
51
+ pressed: boolean,
52
+ hovered?: boolean,
53
+ focused?: boolean,
54
+ focusVisible?: boolean,
55
+ }
56
+
49
57
  export const Checkbox = ({
50
58
  value: controlledValue,
51
59
  initialValue = false,
@@ -56,13 +64,22 @@ export const Checkbox = ({
56
64
  onValueChange,
57
65
  onEditComplete,
58
66
  size = 'md',
59
- alwaysShowCheckIcon = false,
60
67
  isRounded = false,
68
+ color,
61
69
  style,
62
- checkboxStyle,
70
+ containerStyle,
71
+ stateLayerStyle,
72
+ hitSlop: providedHitSlop,
73
+ onLayout: providedOnLayout,
63
74
  ...props
64
75
  }: CheckboxProps) => {
65
76
  const { theme } = useTheme()
77
+ const { hitBox } = useDebugContext()
78
+ const { hitSlop, onLayout } = useMinimumTouchTargetHitSlop({
79
+ touchTargetSize: theme.semantics.touchTargetSize({}),
80
+ hitSlop: providedHitSlop,
81
+ onLayout: providedOnLayout,
82
+ })
66
83
  const interactive = !disabled && !readOnly
67
84
 
68
85
  const onEditCompleteStable = useEventCallbackStabilizer(onEditComplete)
@@ -79,24 +96,20 @@ export const Checkbox = ({
79
96
  defaultValue: initialValue,
80
97
  })
81
98
 
82
- const state = useMemo((): CheckboxState => ({
99
+ const resolveState = (interaction: PressableInteraction): CheckboxState => ({
83
100
  size,
101
+ color,
84
102
  isChecked: value,
85
103
  isIndeterminate: indeterminate,
86
104
  isInvalid: invalid,
87
105
  isDisabled: disabled,
106
+ isReadonly: readOnly,
88
107
  isRounded,
89
- alwaysShowCheckIcon,
90
- }), [alwaysShowCheckIcon, disabled, indeterminate, invalid, isRounded, size, value])
91
-
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
- )
108
+ isPressed: interaction.pressed,
109
+ isHovered: !!interaction.hovered,
110
+ isFocused: !!interaction.focused,
111
+ isFocusVisible: !!interaction.focusVisible,
112
+ })
100
113
 
101
114
  return (
102
115
  <Pressable
@@ -107,19 +120,46 @@ export const Checkbox = ({
107
120
  checked: indeterminate ? 'mixed' : value,
108
121
  disabled,
109
122
  }}
123
+ hitSlop={hitSlop}
124
+ onLayout={onLayout}
110
125
  onPress={(event) => {
111
126
  if (interactive) {
112
127
  setValue((previous) => !previous)
113
128
  }
114
129
  props.onPress?.(event)
115
130
  }}
116
- style={[resolvedCheckboxStyle, style]}
131
+ style={(pressableState) => {
132
+ const state = resolveState(pressableState as PressableInteraction)
133
+ return [theme.components.checkbox.container(state, containerStyle), style]
134
+ }}
117
135
  >
118
- {resolvedIcon.visible && (
119
- indeterminate
120
- ? <Icon icon={Minus} size={resolvedIcon.size} color={resolvedIcon.color} />
121
- : <Icon icon={Check} size={resolvedIcon.size} color={resolvedIcon.color} />
122
- )}
136
+ {(pressableState) => {
137
+ const state = resolveState(pressableState as PressableInteraction)
138
+ const resolvedIcon = theme.components.checkbox.icon(state)
139
+ const showIcon = !!(indeterminate || value)
140
+
141
+ return (
142
+ <Fragment>
143
+ {hitBox.isVisualizing && (
144
+ <View
145
+ pointerEvents="none"
146
+ style={createHitBoxOverlayStyle(hitSlop, hitBox.color)}
147
+ />
148
+ )}
149
+ <View
150
+ pointerEvents="none"
151
+ style={theme.components.checkbox.stateLayer(state, stateLayerStyle)}
152
+ />
153
+ {showIcon && (
154
+ <ThemedIcon
155
+ icon={indeterminate ? HightideIconRegistry.Minus : HightideIconRegistry.Check}
156
+ size={resolvedIcon.size}
157
+ color={resolvedIcon.color}
158
+ />
159
+ )}
160
+ </Fragment>
161
+ )
162
+ }}
123
163
  </Pressable>
124
164
  )
125
165
  }