@helpwave/hightide-native 0.9.0 → 0.9.2

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.
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "access": "public"
11
11
  },
12
12
  "license": "AGPL-3.0-only",
13
- "version": "0.9.0",
13
+ "version": "0.9.2",
14
14
  "type": "module",
15
15
  "files": [
16
16
  "src"
@@ -2,7 +2,8 @@ import { forwardRef, useMemo, useState } from 'react'
2
2
  import {
3
3
  Pressable,
4
4
  View,
5
- type PressableProps
5
+ type PressableProps,
6
+ type TextProps
6
7
  } from 'react-native'
7
8
 
8
9
  import type { ColorPairToken } from '@helpwave/hightide-design/theme-tokens'
@@ -21,6 +22,7 @@ import type { StyleOverwrite } from '../../theme/types/resolver'
21
22
  import { createHitBoxOverlayStyle } from '../../utils/hitBoxOverlay'
22
23
  import { useMinimumTouchTargetHitSlop } from '../../utils/minimumTouchTargetHitSlop'
23
24
  import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
25
+ import { ThemedLoadingSpinner } from '../visualization-and-display/ThemedLoadingSpinner'
24
26
  import { ThemedText } from '../visualization-and-display/ThemedText'
25
27
  import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
26
28
 
@@ -43,7 +45,9 @@ export type ButtonProps = Omit<PressableProps, 'children' | 'style'> & {
43
45
  style?: StyleOverwrite<ButtonState, ButtonStyle>,
44
46
  stateLayerStyle?: StyleOverwrite<ButtonState, ButtonStyle>,
45
47
  textStyle?: StyleOverwrite<ButtonState, ButtonTextStyle>,
48
+ textProps?: Omit<TextProps, 'style'>,
46
49
  iconStyle?: StyleOverwrite<ButtonState, IconStyle>,
50
+ isProcessing?: boolean,
47
51
  }
48
52
 
49
53
  export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonProps>(function Button({
@@ -57,7 +61,9 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
57
61
  style,
58
62
  stateLayerStyle,
59
63
  textStyle,
64
+ textProps,
60
65
  iconStyle,
66
+ isProcessing = false,
61
67
  hitSlop: providedHitSlop,
62
68
  onLayout: providedOnLayout,
63
69
  ...props
@@ -83,20 +89,38 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
83
89
  const resolvedTextStyle = useMemoizedTheme(theme.components.button.text, resolvedState, textStyle)
84
90
  const resolvedIcon = useMemoizedTheme(theme.components.button.icon, resolvedState, iconStyle)
85
91
  const resolvedStateLayerStyle = useMemoizedTheme(theme.components.button.stateLayer, resolvedState, stateLayerStyle)
92
+ const showLeadingSpinner = isProcessing && (leadingIcon !== undefined || trailingIcon === undefined)
93
+ const showTrailingSpinner = isProcessing && leadingIcon === undefined && trailingIcon !== undefined
86
94
 
87
95
  return (
88
96
  <Pressable
89
97
  {...props}
90
98
  ref={ref}
91
99
  disabled={disabled}
100
+ accessibilityState={{
101
+ disabled: !!disabled,
102
+ busy: isProcessing,
103
+ }}
92
104
  hitSlop={hitSlop}
93
105
  onLayout={onLayout}
94
106
  style={resolvedContainerStyle}
107
+ onPress={(event) => {
108
+ if (isProcessing) {
109
+ return
110
+ }
111
+ props.onPress?.(event)
112
+ }}
95
113
  onPressIn={(event) => {
114
+ if (isProcessing) {
115
+ return
116
+ }
96
117
  setIsPressed(true)
97
118
  props.onPressIn?.(event)
98
119
  }}
99
120
  onPressOut={(event) => {
121
+ if (isProcessing) {
122
+ return
123
+ }
100
124
  setIsPressed(false)
101
125
  props.onPressOut?.(event)
102
126
  }}
@@ -117,11 +141,17 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
117
141
  textStyle={resolvedTextStyle}
118
142
  iconStyle={resolvedIcon}
119
143
  >
120
- {leadingIcon !== undefined && (
144
+ {showLeadingSpinner && (
145
+ <ThemedLoadingSpinner />
146
+ )}
147
+ {!isProcessing && leadingIcon !== undefined && (
121
148
  <ThemedIcon icon={leadingIcon} />
122
149
  )}
123
- <ThemedText>{children}</ThemedText>
124
- {trailingIcon !== undefined && (
150
+ <ThemedText numberOfLines={1} ellipsizeMode="tail" {...textProps}>{children}</ThemedText>
151
+ {showTrailingSpinner && (
152
+ <ThemedLoadingSpinner />
153
+ )}
154
+ {!isProcessing && trailingIcon !== undefined && (
125
155
  <ThemedIcon icon={trailingIcon} />
126
156
  )}
127
157
  </ContentThemeOverrideProvider>
@@ -22,7 +22,7 @@ import type { StyleOverwrite } from '../../theme/types/resolver'
22
22
  import type { IconComponent, IconStyle } from '../../icons'
23
23
  import { createHitBoxOverlayStyle } from '../../utils/hitBoxOverlay'
24
24
  import { useMinimumTouchTargetHitSlop } from '../../utils/minimumTouchTargetHitSlop'
25
- import { ThemedIcon } from '../visualization-and-display'
25
+ import { ThemedIcon, ThemedLoadingSpinner } from '../visualization-and-display'
26
26
 
27
27
  export type IconButtonSize = ComponentSize
28
28
 
@@ -42,6 +42,7 @@ export type IconButtonProps = Omit<PressableProps, 'children' | 'style'> & {
42
42
  style?: StyleOverwrite<IconButtonState, IconButtonStyle>,
43
43
  stateLayerStyle?: StyleOverwrite<IconButtonState, IconButtonStyle>,
44
44
  iconStyle?: StyleOverwrite<IconButtonState, IconStyle>,
45
+ isProcessing?: boolean,
45
46
  }
46
47
 
47
48
  export const IconButton = forwardRef<React.ComponentRef<typeof Pressable>, IconButtonProps>(function IconButton({
@@ -54,6 +55,7 @@ export const IconButton = forwardRef<React.ComponentRef<typeof Pressable>, IconB
54
55
  style,
55
56
  stateLayerStyle,
56
57
  iconStyle,
58
+ isProcessing = false,
57
59
  hitSlop: providedHitSlop,
58
60
  onLayout: providedOnLayout,
59
61
  ...props
@@ -92,14 +94,30 @@ export const IconButton = forwardRef<React.ComponentRef<typeof Pressable>, IconB
92
94
  disabled={disabled}
93
95
  accessibilityRole="button"
94
96
  accessibilityLabel={accessibilityLabel}
97
+ accessibilityState={{
98
+ disabled: !!disabled,
99
+ busy: isProcessing,
100
+ }}
95
101
  hitSlop={hitSlop}
96
102
  onLayout={onLayout}
97
103
  style={resolvedContainerStyle}
104
+ onPress={(event) => {
105
+ if (isProcessing) {
106
+ return
107
+ }
108
+ props.onPress?.(event)
109
+ }}
98
110
  onPressIn={(event) => {
111
+ if (isProcessing) {
112
+ return
113
+ }
99
114
  setIsPressed(true)
100
115
  props.onPressIn?.(event)
101
116
  }}
102
117
  onPressOut={(event) => {
118
+ if (isProcessing) {
119
+ return
120
+ }
103
121
  setIsPressed(false)
104
122
  props.onPressOut?.(event)
105
123
  }}
@@ -117,7 +135,11 @@ export const IconButton = forwardRef<React.ComponentRef<typeof Pressable>, IconB
117
135
  <ContentThemeOverrideProvider
118
136
  iconStyle={resolvedIconStyle}
119
137
  >
120
- <ThemedIcon icon={icon} />
138
+ {isProcessing ? (
139
+ <ThemedLoadingSpinner />
140
+ ) : (
141
+ <ThemedIcon icon={icon} />
142
+ )}
121
143
  </ContentThemeOverrideProvider>
122
144
  </Pressable>
123
145
  )
@@ -0,0 +1,41 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import {
3
+ Animated,
4
+ Easing
5
+ } from 'react-native'
6
+ import { LoaderCircle } from 'lucide-react-native'
7
+
8
+ import { ThemedIcon, type ThemedIconProps } from './ThemedIcon'
9
+
10
+ export type ThemedLoadingSpinnerProps = Omit<ThemedIconProps, 'icon'>
11
+
12
+ export const ThemedLoadingSpinner = (props: ThemedLoadingSpinnerProps) => {
13
+ const progress = useRef(new Animated.Value(0)).current
14
+
15
+ useEffect(() => {
16
+ const animation = Animated.loop(
17
+ Animated.timing(progress, {
18
+ toValue: 1,
19
+ duration: 750,
20
+ easing: Easing.linear,
21
+ useNativeDriver: true,
22
+ })
23
+ )
24
+ animation.start()
25
+ return () => {
26
+ animation.stop()
27
+ progress.setValue(0)
28
+ }
29
+ }, [progress])
30
+
31
+ const rotate = progress.interpolate({
32
+ inputRange: [0, 1],
33
+ outputRange: ['0deg', '360deg'],
34
+ })
35
+
36
+ return (
37
+ <Animated.View style={{ transform: [{ rotate }] }}>
38
+ <ThemedIcon icon={LoaderCircle} {...props} />
39
+ </Animated.View>
40
+ )
41
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './Avatar'
2
2
  export * from './Chip'
3
3
  export * from './ThemedIcon'
4
+ export * from './ThemedLoadingSpinner'
4
5
  export * from './ThemedText'