@helpwave/hightide-native 0.0.6 → 0.0.8
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 +3 -3
- package/src/components/chat/ChatMessageCard.tsx +1 -1
- package/src/components/chat/ChatSystemLine.tsx +1 -1
- package/src/components/user-interaction/Button.tsx +1 -1
- package/src/components/user-interaction/IconButton.tsx +1 -1
- package/src/components/user-interaction/Switch.tsx +177 -0
- package/src/components/user-interaction/index.ts +1 -0
- package/src/components/visualization-and-display/Avatar.tsx +286 -0
- package/src/components/visualization-and-display/Chip.tsx +1 -1
- package/src/components/visualization-and-display/index.ts +1 -0
- package/src/global-contexts/hightide-config/HightideConfigUtils.ts +3 -3
- package/src/global-contexts/theme/ThemeContext.ts +2 -2
- package/src/global-contexts/theme/ThemeProvider.tsx +3 -3
- package/src/theme/resolvers/avatar.ts +231 -0
- package/src/theme/resolvers/button.ts +1 -1
- package/src/theme/resolvers/chat.ts +2 -2
- package/src/theme/resolvers/checkbox.ts +1 -1
- package/src/theme/resolvers/chip.ts +1 -1
- package/src/theme/resolvers/coloring.ts +1 -1
- package/src/theme/resolvers/iconButton.ts +1 -1
- package/src/theme/resolvers/index.ts +2 -0
- package/src/theme/resolvers/input.ts +1 -1
- package/src/theme/resolvers/menu.ts +1 -1
- package/src/theme/resolvers/multiSelect.ts +1 -1
- package/src/theme/resolvers/select.ts +1 -1
- package/src/theme/resolvers/switch.ts +65 -0
- package/src/theme/themes/{createTheme.ts → createHightideTheme.ts} +8 -4
- package/src/theme/themes/hightideThemes.ts +14 -0
- package/src/theme/themes/index.ts +2 -2
- package/src/theme/types/components/avatar.ts +77 -0
- package/src/theme/types/components/button.ts +1 -1
- package/src/theme/types/components/chat.ts +1 -1
- package/src/theme/types/components/chip.ts +1 -1
- package/src/theme/types/components/hightide.ts +4 -0
- package/src/theme/types/components/iconButton.ts +1 -1
- package/src/theme/types/components/index.ts +2 -0
- package/src/theme/types/components/switch.ts +15 -0
- package/src/theme/types/layout.ts +12 -0
- package/src/theme/types/theme.ts +15 -6
- package/src/theme/themes/nativeThemes.ts +0 -16
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"license": "AGPL-3.0-only",
|
|
13
|
-
"version": "0.0.
|
|
13
|
+
"version": "0.0.8",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"files": [
|
|
16
16
|
"src"
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"lucide-react-native": "0.561.0",
|
|
46
|
-
"@helpwave/hightide-design": "0.0.
|
|
47
|
-
"@helpwave/hightide-utils": "0.0.
|
|
46
|
+
"@helpwave/hightide-design": "0.0.6",
|
|
47
|
+
"@helpwave/hightide-utils": "0.0.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@react-native-async-storage/async-storage": ">=2.0.0",
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type ViewStyle
|
|
11
11
|
} from 'react-native'
|
|
12
12
|
|
|
13
|
-
import type { ColoringType } from '@helpwave/hightide-design/
|
|
13
|
+
import type { ColoringType } from '@helpwave/hightide-design/utils'
|
|
14
14
|
|
|
15
15
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
16
16
|
import type {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from 'react-native'
|
|
12
12
|
import { CheckCheck } from 'lucide-react-native'
|
|
13
13
|
|
|
14
|
-
import type { ColoringType } from '@helpwave/hightide-design/
|
|
14
|
+
import type { ColoringType } from '@helpwave/hightide-design/utils'
|
|
15
15
|
|
|
16
16
|
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
17
17
|
import type {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from 'react-native'
|
|
11
11
|
import type { LucideIcon } from 'lucide-react-native'
|
|
12
12
|
|
|
13
|
-
import type { ColoringType } from '@helpwave/hightide-design/
|
|
13
|
+
import type { ColoringType } from '@helpwave/hightide-design/utils'
|
|
14
14
|
import type {
|
|
15
15
|
ButtonColoringStyle,
|
|
16
16
|
ElementSize
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef
|
|
6
|
+
} from 'react'
|
|
7
|
+
import {
|
|
8
|
+
Animated,
|
|
9
|
+
Pressable,
|
|
10
|
+
View,
|
|
11
|
+
type PressableProps,
|
|
12
|
+
type StyleProp,
|
|
13
|
+
type ViewStyle
|
|
14
|
+
} from 'react-native'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
useControlledState,
|
|
18
|
+
useEventCallbackStabilizer
|
|
19
|
+
} from '@helpwave/hightide-utils/hooks'
|
|
20
|
+
|
|
21
|
+
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
22
|
+
import type { SwitchState } from '../../theme/types/components/switch'
|
|
23
|
+
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
24
|
+
import type { Color } from '../../theme/types/color'
|
|
25
|
+
import type {
|
|
26
|
+
FormFieldDataHandling,
|
|
27
|
+
FormFieldInteractionStates
|
|
28
|
+
} from '../../types/formField'
|
|
29
|
+
|
|
30
|
+
const TRACK_WIDTH = 48
|
|
31
|
+
const TRACK_HEIGHT = 28
|
|
32
|
+
const TRACK_BORDER_WIDTH = 2
|
|
33
|
+
const TRACK_PADDING = 4
|
|
34
|
+
const THUMB_SIZE_INACTIVE = 12
|
|
35
|
+
const THUMB_SIZE_ACTIVE = 16
|
|
36
|
+
const THUMB_OFFSET_INACTIVE = 2
|
|
37
|
+
const ANIMATION_DURATION_MS = 250
|
|
38
|
+
|
|
39
|
+
const TRACK_INNER_WIDTH = TRACK_WIDTH - (TRACK_BORDER_WIDTH * 2) - (TRACK_PADDING * 2)
|
|
40
|
+
const TRACK_INNER_HEIGHT = TRACK_HEIGHT - (TRACK_BORDER_WIDTH * 2) - (TRACK_PADDING * 2)
|
|
41
|
+
const THUMB_OFFSET_ACTIVE = TRACK_INNER_WIDTH - THUMB_SIZE_ACTIVE
|
|
42
|
+
const THUMB_TOP_INACTIVE = (TRACK_INNER_HEIGHT - THUMB_SIZE_INACTIVE) / 2
|
|
43
|
+
const THUMB_TOP_ACTIVE = (TRACK_INNER_HEIGHT - THUMB_SIZE_ACTIVE) / 2
|
|
44
|
+
|
|
45
|
+
export type SwitchProps = Omit<PressableProps, 'children' | 'style' | 'disabled'>
|
|
46
|
+
& Partial<FormFieldInteractionStates>
|
|
47
|
+
& Partial<FormFieldDataHandling<boolean>>
|
|
48
|
+
& {
|
|
49
|
+
initialValue?: boolean,
|
|
50
|
+
style?: StyleProp<ViewStyle>,
|
|
51
|
+
trackColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
52
|
+
borderColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
53
|
+
thumbColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Switch = ({
|
|
57
|
+
value: controlledValue,
|
|
58
|
+
initialValue = false,
|
|
59
|
+
invalid = false,
|
|
60
|
+
disabled = false,
|
|
61
|
+
readOnly = false,
|
|
62
|
+
onValueChange,
|
|
63
|
+
onEditComplete,
|
|
64
|
+
style,
|
|
65
|
+
trackColorStyle,
|
|
66
|
+
borderColorStyle,
|
|
67
|
+
thumbColorStyle,
|
|
68
|
+
accessibilityLabel,
|
|
69
|
+
...props
|
|
70
|
+
}: SwitchProps) => {
|
|
71
|
+
const { theme } = useTheme()
|
|
72
|
+
const interactive = !disabled && !readOnly
|
|
73
|
+
|
|
74
|
+
const onEditCompleteStable = useEventCallbackStabilizer(onEditComplete)
|
|
75
|
+
const onValueChangeStable = useEventCallbackStabilizer(onValueChange)
|
|
76
|
+
|
|
77
|
+
const onChangeWrapper = useCallback((nextValue: boolean) => {
|
|
78
|
+
onValueChangeStable(nextValue)
|
|
79
|
+
onEditCompleteStable(nextValue)
|
|
80
|
+
}, [onEditCompleteStable, onValueChangeStable])
|
|
81
|
+
|
|
82
|
+
const [value, setValue] = useControlledState({
|
|
83
|
+
value: controlledValue,
|
|
84
|
+
onValueChange: onChangeWrapper,
|
|
85
|
+
defaultValue: initialValue,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const state = useMemo((): SwitchState => ({
|
|
89
|
+
isActive: value,
|
|
90
|
+
isInvalid: invalid,
|
|
91
|
+
isDisabled: disabled,
|
|
92
|
+
isReadOnly: readOnly,
|
|
93
|
+
}), [disabled, invalid, readOnly, value])
|
|
94
|
+
|
|
95
|
+
const trackColor = useMemo(
|
|
96
|
+
() => theme.components.switch.trackColor(state, trackColorStyle),
|
|
97
|
+
[theme, state, trackColorStyle]
|
|
98
|
+
)
|
|
99
|
+
const borderColor = useMemo(
|
|
100
|
+
() => theme.components.switch.borderColor(state, borderColorStyle),
|
|
101
|
+
[theme, state, borderColorStyle]
|
|
102
|
+
)
|
|
103
|
+
const thumbColor = useMemo(
|
|
104
|
+
() => theme.components.switch.thumbColor(state, thumbColorStyle),
|
|
105
|
+
[theme, state, thumbColorStyle]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const progress = useRef(new Animated.Value(value ? 1 : 0)).current
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
Animated.timing(progress, {
|
|
112
|
+
toValue: value ? 1 : 0,
|
|
113
|
+
duration: ANIMATION_DURATION_MS,
|
|
114
|
+
useNativeDriver: false,
|
|
115
|
+
}).start()
|
|
116
|
+
}, [progress, value])
|
|
117
|
+
|
|
118
|
+
const thumbSize = progress.interpolate({
|
|
119
|
+
inputRange: [0, 1],
|
|
120
|
+
outputRange: [THUMB_SIZE_INACTIVE, THUMB_SIZE_ACTIVE],
|
|
121
|
+
})
|
|
122
|
+
const thumbTranslateX = progress.interpolate({
|
|
123
|
+
inputRange: [0, 1],
|
|
124
|
+
outputRange: [THUMB_OFFSET_INACTIVE, THUMB_OFFSET_ACTIVE],
|
|
125
|
+
})
|
|
126
|
+
const thumbTop = progress.interpolate({
|
|
127
|
+
inputRange: [0, 1],
|
|
128
|
+
outputRange: [THUMB_TOP_INACTIVE, THUMB_TOP_ACTIVE],
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Pressable
|
|
133
|
+
{...props}
|
|
134
|
+
disabled={!interactive}
|
|
135
|
+
accessibilityRole="switch"
|
|
136
|
+
accessibilityLabel={accessibilityLabel}
|
|
137
|
+
accessibilityState={{
|
|
138
|
+
checked: value,
|
|
139
|
+
disabled,
|
|
140
|
+
}}
|
|
141
|
+
onPress={(event) => {
|
|
142
|
+
if (interactive) {
|
|
143
|
+
setValue((previous) => !previous)
|
|
144
|
+
}
|
|
145
|
+
props.onPress?.(event)
|
|
146
|
+
}}
|
|
147
|
+
style={style}
|
|
148
|
+
>
|
|
149
|
+
<View
|
|
150
|
+
style={{
|
|
151
|
+
width: TRACK_WIDTH,
|
|
152
|
+
height: TRACK_HEIGHT,
|
|
153
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
154
|
+
borderWidth: TRACK_BORDER_WIDTH,
|
|
155
|
+
borderColor,
|
|
156
|
+
backgroundColor: trackColor,
|
|
157
|
+
padding: TRACK_PADDING,
|
|
158
|
+
opacity: disabled ? 0.6 : 1,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<View style={{ flex: 1 }}>
|
|
162
|
+
<Animated.View
|
|
163
|
+
style={{
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
top: thumbTop,
|
|
166
|
+
width: thumbSize,
|
|
167
|
+
height: thumbSize,
|
|
168
|
+
borderRadius: THUMB_SIZE_ACTIVE / 2,
|
|
169
|
+
backgroundColor: thumbColor,
|
|
170
|
+
transform: [{ translateX: thumbTranslateX }],
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
</Pressable>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useState,
|
|
5
|
+
type ComponentType,
|
|
6
|
+
type ReactNode
|
|
7
|
+
} from 'react'
|
|
8
|
+
import {
|
|
9
|
+
Image,
|
|
10
|
+
Text,
|
|
11
|
+
View,
|
|
12
|
+
type ImageProps,
|
|
13
|
+
type StyleProp,
|
|
14
|
+
type ViewProps,
|
|
15
|
+
type ViewStyle
|
|
16
|
+
} from 'react-native'
|
|
17
|
+
import { User } from 'lucide-react-native'
|
|
18
|
+
|
|
19
|
+
import type { ElementSize } from '@helpwave/hightide-design/types'
|
|
20
|
+
|
|
21
|
+
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
22
|
+
import type {
|
|
23
|
+
AvatarGroupMoreStyle,
|
|
24
|
+
AvatarGroupStackStyle,
|
|
25
|
+
AvatarImageStyle,
|
|
26
|
+
AvatarIconStyle,
|
|
27
|
+
AvatarStatus,
|
|
28
|
+
AvatarStyle,
|
|
29
|
+
AvatarTextStyle,
|
|
30
|
+
AvatarWithLabelContainerStyle,
|
|
31
|
+
AvatarWithLabelTextStyle,
|
|
32
|
+
AvatarWithStatusContainerStyle,
|
|
33
|
+
AvatarStatusDotStyle
|
|
34
|
+
} from '../../theme/types/components/avatar'
|
|
35
|
+
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
36
|
+
|
|
37
|
+
export type AvatarSize = ElementSize
|
|
38
|
+
|
|
39
|
+
type ImageConfig = {
|
|
40
|
+
avatarUrl: string,
|
|
41
|
+
alt: string,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type AvatarImageProps = Omit<ImageProps, 'source'> & {
|
|
45
|
+
source: { uri: string },
|
|
46
|
+
alt?: string,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DefaultAvatarImage: ComponentType<AvatarImageProps> = ({ alt, ...props }) => (
|
|
50
|
+
<Image
|
|
51
|
+
{...props}
|
|
52
|
+
accessibilityLabel={alt}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
export const AvatarUtil = {
|
|
57
|
+
sizes: ['xs', 'sm', 'md', 'lg'] as const satisfies readonly ElementSize[],
|
|
58
|
+
statuses: ['online', 'offline', 'away', 'busy', 'unknown'] as const satisfies readonly AvatarStatus[],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type AvatarProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
62
|
+
image?: ImageConfig,
|
|
63
|
+
name?: string,
|
|
64
|
+
size?: AvatarSize,
|
|
65
|
+
ImageComponent?: ComponentType<AvatarImageProps>,
|
|
66
|
+
style?: StyleProp<ViewStyle>,
|
|
67
|
+
avatarStyle?: StyleOverwrite<{ size?: ElementSize, isGrouped?: boolean, groupIndex?: number }, AvatarStyle>,
|
|
68
|
+
imageStyle?: StyleOverwrite<{ size?: ElementSize, isGrouped?: boolean, groupIndex?: number }, AvatarImageStyle>,
|
|
69
|
+
textStyle?: StyleOverwrite<{ size?: ElementSize, isGrouped?: boolean, groupIndex?: number }, AvatarTextStyle>,
|
|
70
|
+
iconStyle?: StyleOverwrite<{ size?: ElementSize, isGrouped?: boolean, groupIndex?: number }, AvatarIconStyle>,
|
|
71
|
+
isGrouped?: boolean,
|
|
72
|
+
groupIndex?: number,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const Avatar = ({
|
|
76
|
+
image: initialImage,
|
|
77
|
+
name,
|
|
78
|
+
size = 'md',
|
|
79
|
+
ImageComponent = DefaultAvatarImage,
|
|
80
|
+
style,
|
|
81
|
+
avatarStyle,
|
|
82
|
+
imageStyle,
|
|
83
|
+
textStyle,
|
|
84
|
+
iconStyle,
|
|
85
|
+
isGrouped = false,
|
|
86
|
+
groupIndex,
|
|
87
|
+
...props
|
|
88
|
+
}: AvatarProps) => {
|
|
89
|
+
const { theme } = useTheme()
|
|
90
|
+
const [hasError, setHasError] = useState(false)
|
|
91
|
+
const [hasLoaded, setHasLoaded] = useState(false)
|
|
92
|
+
const [image, setImage] = useState(initialImage)
|
|
93
|
+
const ImageElement = ImageComponent
|
|
94
|
+
|
|
95
|
+
const state = useMemo(() => ({
|
|
96
|
+
size,
|
|
97
|
+
isGrouped,
|
|
98
|
+
groupIndex,
|
|
99
|
+
}), [size, isGrouped, groupIndex])
|
|
100
|
+
|
|
101
|
+
const displayName = useMemo(() => {
|
|
102
|
+
const maxLetters = size === 'sm' ? 1 : 2
|
|
103
|
+
return (name ?? '')
|
|
104
|
+
.split(' ')
|
|
105
|
+
.filter((_, index) => index < maxLetters)
|
|
106
|
+
.map(value => value[0])
|
|
107
|
+
.join('')
|
|
108
|
+
.toUpperCase()
|
|
109
|
+
}, [name, size])
|
|
110
|
+
|
|
111
|
+
const isShowingImage = !!image && (!hasError || !hasLoaded)
|
|
112
|
+
const isShowingFallback = !hasLoaded || hasError
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (initialImage?.avatarUrl !== image?.avatarUrl) {
|
|
116
|
+
setHasError(false)
|
|
117
|
+
setHasLoaded(false)
|
|
118
|
+
}
|
|
119
|
+
setImage(initialImage)
|
|
120
|
+
}, [image?.avatarUrl, initialImage])
|
|
121
|
+
|
|
122
|
+
const resolvedAvatar = theme.components.avatar.avatar(state, avatarStyle)
|
|
123
|
+
const resolvedImage = theme.components.avatar.image(state, imageStyle)
|
|
124
|
+
const resolvedText = theme.components.avatar.text(state, textStyle)
|
|
125
|
+
const resolvedIcon = theme.components.avatar.icon(state, iconStyle)
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<View
|
|
129
|
+
{...props}
|
|
130
|
+
style={[resolvedAvatar, style]}
|
|
131
|
+
>
|
|
132
|
+
{isShowingImage && (
|
|
133
|
+
<ImageElement
|
|
134
|
+
key={image?.avatarUrl}
|
|
135
|
+
source={{ uri: image?.avatarUrl ?? '' }}
|
|
136
|
+
alt={image?.alt}
|
|
137
|
+
style={[resolvedImage, { opacity: hasLoaded && !hasError ? 1 : 0 }]}
|
|
138
|
+
onLoad={() => setHasLoaded(true)}
|
|
139
|
+
onError={() => setHasError(true)}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
{isShowingFallback && (
|
|
143
|
+
name ? (
|
|
144
|
+
<Text style={resolvedText}>{displayName}</Text>
|
|
145
|
+
) : (
|
|
146
|
+
<User
|
|
147
|
+
size={resolvedIcon.size}
|
|
148
|
+
strokeWidth={resolvedIcon.strokeWidth}
|
|
149
|
+
color={resolvedIcon.color}
|
|
150
|
+
/>
|
|
151
|
+
)
|
|
152
|
+
)}
|
|
153
|
+
</View>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type AvatarGroupProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
158
|
+
avatars: Omit<AvatarProps, 'size' | 'isGrouped' | 'groupIndex'>[],
|
|
159
|
+
showTotalNumber?: boolean,
|
|
160
|
+
size?: AvatarSize,
|
|
161
|
+
ImageComponent?: ComponentType<AvatarImageProps>,
|
|
162
|
+
style?: StyleProp<ViewStyle>,
|
|
163
|
+
stackStyle?: StyleOverwrite<{ size?: ElementSize, count?: number }, AvatarGroupStackStyle>,
|
|
164
|
+
moreStyle?: StyleOverwrite<{ size?: ElementSize, count?: number }, AvatarGroupMoreStyle>,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const AvatarGroup = ({
|
|
168
|
+
avatars,
|
|
169
|
+
showTotalNumber = true,
|
|
170
|
+
size = 'md',
|
|
171
|
+
ImageComponent,
|
|
172
|
+
style,
|
|
173
|
+
stackStyle,
|
|
174
|
+
moreStyle,
|
|
175
|
+
...props
|
|
176
|
+
}: AvatarGroupProps) => {
|
|
177
|
+
const { theme } = useTheme()
|
|
178
|
+
const maxShownProfiles = theme.layout.avatarGroup.maxShown
|
|
179
|
+
const displayedProfiles = avatars.length < maxShownProfiles ? avatars : avatars.slice(0, maxShownProfiles)
|
|
180
|
+
const notDisplayedProfiles = avatars.length - maxShownProfiles
|
|
181
|
+
|
|
182
|
+
const state = useMemo(() => ({
|
|
183
|
+
size,
|
|
184
|
+
count: displayedProfiles.length,
|
|
185
|
+
}), [size, displayedProfiles.length])
|
|
186
|
+
|
|
187
|
+
const resolvedContainer = theme.components.avatar.group.container(state)
|
|
188
|
+
const resolvedStack = theme.components.avatar.group.stack(state, stackStyle)
|
|
189
|
+
const resolvedMore = theme.components.avatar.group.more(state, moreStyle)
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<View
|
|
193
|
+
{...props}
|
|
194
|
+
style={[resolvedContainer, style]}
|
|
195
|
+
>
|
|
196
|
+
<View style={resolvedStack}>
|
|
197
|
+
{displayedProfiles.map((avatar, index) => (
|
|
198
|
+
<Avatar
|
|
199
|
+
{...avatar}
|
|
200
|
+
key={index}
|
|
201
|
+
size={size}
|
|
202
|
+
isGrouped
|
|
203
|
+
groupIndex={index}
|
|
204
|
+
ImageComponent={avatar.ImageComponent ?? ImageComponent}
|
|
205
|
+
/>
|
|
206
|
+
))}
|
|
207
|
+
</View>
|
|
208
|
+
{showTotalNumber && notDisplayedProfiles > 0 && (
|
|
209
|
+
<Text style={resolvedMore}>
|
|
210
|
+
{`+ ${notDisplayedProfiles}`}
|
|
211
|
+
</Text>
|
|
212
|
+
)}
|
|
213
|
+
</View>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export type { AvatarStatus }
|
|
218
|
+
|
|
219
|
+
export type AvatarWithStatusProps = AvatarProps & {
|
|
220
|
+
status?: AvatarStatus,
|
|
221
|
+
containerStyle?: StyleOverwrite<{ size?: ElementSize, status?: AvatarStatus }, AvatarWithStatusContainerStyle>,
|
|
222
|
+
statusDotStyle?: StyleOverwrite<{ size?: ElementSize, status?: AvatarStatus }, AvatarStatusDotStyle>,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const AvatarWithStatus = ({
|
|
226
|
+
status = 'unknown',
|
|
227
|
+
size = 'md',
|
|
228
|
+
style,
|
|
229
|
+
containerStyle,
|
|
230
|
+
statusDotStyle,
|
|
231
|
+
...avatarProps
|
|
232
|
+
}: AvatarWithStatusProps) => {
|
|
233
|
+
const { theme } = useTheme()
|
|
234
|
+
|
|
235
|
+
const state = useMemo(() => ({
|
|
236
|
+
size,
|
|
237
|
+
status,
|
|
238
|
+
}), [size, status])
|
|
239
|
+
|
|
240
|
+
const resolvedContainer = theme.components.avatar.withStatus.container(state, containerStyle)
|
|
241
|
+
const resolvedStatusDot = theme.components.avatar.withStatus.statusDot(state, statusDotStyle)
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<View style={[resolvedContainer, style]}>
|
|
245
|
+
<Avatar {...avatarProps} size={size} />
|
|
246
|
+
<View style={resolvedStatusDot} />
|
|
247
|
+
</View>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
type AvatarWithLabelPosition = 'left' | 'right'
|
|
252
|
+
|
|
253
|
+
export type AvatarWithLabelProps = AvatarProps & {
|
|
254
|
+
label: ReactNode,
|
|
255
|
+
labelPosition?: AvatarWithLabelPosition,
|
|
256
|
+
containerStyle?: StyleOverwrite<{ size?: ElementSize }, AvatarWithLabelContainerStyle>,
|
|
257
|
+
labelStyle?: StyleOverwrite<{ size?: ElementSize }, AvatarWithLabelTextStyle>,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const AvatarWithLabel = ({
|
|
261
|
+
label,
|
|
262
|
+
labelPosition = 'left',
|
|
263
|
+
size = 'md',
|
|
264
|
+
style,
|
|
265
|
+
containerStyle,
|
|
266
|
+
labelStyle,
|
|
267
|
+
...avatarProps
|
|
268
|
+
}: AvatarWithLabelProps) => {
|
|
269
|
+
const { theme } = useTheme()
|
|
270
|
+
|
|
271
|
+
const state = useMemo(() => ({ size }), [size])
|
|
272
|
+
const resolvedContainer = theme.components.avatar.withLabel.container(state, containerStyle)
|
|
273
|
+
const resolvedLabel = theme.components.avatar.withLabel.text(state, labelStyle)
|
|
274
|
+
|
|
275
|
+
const avatar = <Avatar {...avatarProps} size={size} />
|
|
276
|
+
const labelElement = typeof label === 'string' || typeof label === 'number'
|
|
277
|
+
? <Text style={resolvedLabel}>{label}</Text>
|
|
278
|
+
: label
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<View style={[resolvedContainer, style]}>
|
|
282
|
+
{labelPosition === 'left' ? labelElement : avatar}
|
|
283
|
+
{labelPosition === 'left' ? avatar : labelElement}
|
|
284
|
+
</View>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { SupportedThemesConfig } from '@helpwave/hightide-utils/context'
|
|
2
2
|
|
|
3
3
|
import type { SupportedLocalesConfig } from '../localization/forward-exports'
|
|
4
|
-
import { themes } from '../../theme/themes/
|
|
5
|
-
import type {
|
|
4
|
+
import { themes } from '../../theme/themes/hightideThemes'
|
|
5
|
+
import type { HightideTheme } from '../../theme/types/theme'
|
|
6
6
|
|
|
7
7
|
export const HightideConfigUtils = {
|
|
8
8
|
defaultSupportedLocales: {
|
|
@@ -24,5 +24,5 @@ export const HightideConfigUtils = {
|
|
|
24
24
|
},
|
|
25
25
|
theme: themes.dark,
|
|
26
26
|
},
|
|
27
|
-
} as const satisfies SupportedThemesConfig<
|
|
27
|
+
} as const satisfies SupportedThemesConfig<HightideTheme>,
|
|
28
28
|
}
|
|
@@ -7,11 +7,11 @@ import type {
|
|
|
7
7
|
ThemeConfigValue,
|
|
8
8
|
ThemeInformation
|
|
9
9
|
} from './forward-exports'
|
|
10
|
-
import type {
|
|
10
|
+
import type { HightideTheme } from '../../theme/types/theme'
|
|
11
11
|
|
|
12
12
|
export type { ThemeInformation }
|
|
13
13
|
|
|
14
|
-
export type ThemeContextValue = ThemeConfigValue<
|
|
14
|
+
export type ThemeContextValue = ThemeConfigValue<HightideTheme> & {
|
|
15
15
|
isInitialized: boolean,
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
type SystemTheme
|
|
20
20
|
} from './forward-exports'
|
|
21
21
|
import { useNativeKeyValueStore } from '../../hooks/useNativeKeyValueStore'
|
|
22
|
-
import type { ThemeMode } from '../../theme/themes/
|
|
23
|
-
import type {
|
|
22
|
+
import type { ThemeMode } from '../../theme/themes/hightideThemes'
|
|
23
|
+
import type { HightideTheme } from '../../theme/types/theme'
|
|
24
24
|
|
|
25
25
|
export type ThemeProviderProps<T> = PropsWithChildren & {
|
|
26
26
|
theme?: ThemeMode | null,
|
|
@@ -66,7 +66,7 @@ export const ThemeProvider = ({
|
|
|
66
66
|
fallbackTheme = 'light',
|
|
67
67
|
supportedThemes = HightideConfigUtils.defaultSupportedThemes,
|
|
68
68
|
onChangedTheme,
|
|
69
|
-
}: ThemeProviderProps<
|
|
69
|
+
}: ThemeProviderProps<HightideTheme>) => {
|
|
70
70
|
const store = useNativeKeyValueStore()
|
|
71
71
|
const systemTheme = useNativeSystemTheme()
|
|
72
72
|
|