@helpwave/hightide-native 0.9.1 → 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.1",
13
+ "version": "0.9.2",
14
14
  "type": "module",
15
15
  "files": [
16
16
  "src"
@@ -22,6 +22,7 @@ import type { StyleOverwrite } from '../../theme/types/resolver'
22
22
  import { createHitBoxOverlayStyle } from '../../utils/hitBoxOverlay'
23
23
  import { useMinimumTouchTargetHitSlop } from '../../utils/minimumTouchTargetHitSlop'
24
24
  import { ThemedIcon } from '../visualization-and-display/ThemedIcon'
25
+ import { ThemedLoadingSpinner } from '../visualization-and-display/ThemedLoadingSpinner'
25
26
  import { ThemedText } from '../visualization-and-display/ThemedText'
26
27
  import { useMemoizedTheme } from '../../hooks/useMemoizedTheme'
27
28
 
@@ -46,6 +47,7 @@ export type ButtonProps = Omit<PressableProps, 'children' | 'style'> & {
46
47
  textStyle?: StyleOverwrite<ButtonState, ButtonTextStyle>,
47
48
  textProps?: Omit<TextProps, 'style'>,
48
49
  iconStyle?: StyleOverwrite<ButtonState, IconStyle>,
50
+ isProcessing?: boolean,
49
51
  }
50
52
 
51
53
  export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonProps>(function Button({
@@ -61,6 +63,7 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
61
63
  textStyle,
62
64
  textProps,
63
65
  iconStyle,
66
+ isProcessing = false,
64
67
  hitSlop: providedHitSlop,
65
68
  onLayout: providedOnLayout,
66
69
  ...props
@@ -86,20 +89,38 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
86
89
  const resolvedTextStyle = useMemoizedTheme(theme.components.button.text, resolvedState, textStyle)
87
90
  const resolvedIcon = useMemoizedTheme(theme.components.button.icon, resolvedState, iconStyle)
88
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
89
94
 
90
95
  return (
91
96
  <Pressable
92
97
  {...props}
93
98
  ref={ref}
94
99
  disabled={disabled}
100
+ accessibilityState={{
101
+ disabled: !!disabled,
102
+ busy: isProcessing,
103
+ }}
95
104
  hitSlop={hitSlop}
96
105
  onLayout={onLayout}
97
106
  style={resolvedContainerStyle}
107
+ onPress={(event) => {
108
+ if (isProcessing) {
109
+ return
110
+ }
111
+ props.onPress?.(event)
112
+ }}
98
113
  onPressIn={(event) => {
114
+ if (isProcessing) {
115
+ return
116
+ }
99
117
  setIsPressed(true)
100
118
  props.onPressIn?.(event)
101
119
  }}
102
120
  onPressOut={(event) => {
121
+ if (isProcessing) {
122
+ return
123
+ }
103
124
  setIsPressed(false)
104
125
  props.onPressOut?.(event)
105
126
  }}
@@ -120,11 +141,17 @@ export const Button = forwardRef<React.ComponentRef<typeof Pressable>, ButtonPro
120
141
  textStyle={resolvedTextStyle}
121
142
  iconStyle={resolvedIcon}
122
143
  >
123
- {leadingIcon !== undefined && (
144
+ {showLeadingSpinner && (
145
+ <ThemedLoadingSpinner />
146
+ )}
147
+ {!isProcessing && leadingIcon !== undefined && (
124
148
  <ThemedIcon icon={leadingIcon} />
125
149
  )}
126
150
  <ThemedText numberOfLines={1} ellipsizeMode="tail" {...textProps}>{children}</ThemedText>
127
- {trailingIcon !== undefined && (
151
+ {showTrailingSpinner && (
152
+ <ThemedLoadingSpinner />
153
+ )}
154
+ {!isProcessing && trailingIcon !== undefined && (
128
155
  <ThemedIcon icon={trailingIcon} />
129
156
  )}
130
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'