@planningcenter/chat-react-native 1.5.0 → 1.6.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.
- package/build/components/conversations.d.ts.map +1 -1
- package/build/components/conversations.js +29 -8
- package/build/components/conversations.js.map +1 -1
- package/build/components/display/button.d.ts +71 -0
- package/build/components/display/button.d.ts.map +1 -0
- package/build/components/display/button.js +136 -0
- package/build/components/display/button.js.map +1 -0
- package/build/components/display/button_color_utils.d.ts +24 -0
- package/build/components/display/button_color_utils.d.ts.map +1 -0
- package/build/components/display/button_color_utils.js +43 -0
- package/build/components/display/button_color_utils.js.map +1 -0
- package/build/components/display/heading.d.ts +4 -0
- package/build/components/display/heading.d.ts.map +1 -1
- package/build/components/display/heading.js +3 -0
- package/build/components/display/heading.js.map +1 -1
- package/build/components/display/icon.d.ts +8 -4
- package/build/components/display/icon.d.ts.map +1 -1
- package/build/components/display/icon.js +21 -13
- package/build/components/display/icon.js.map +1 -1
- package/build/components/display/image.d.ts +7 -2
- package/build/components/display/image.d.ts.map +1 -1
- package/build/components/display/image.js +5 -5
- package/build/components/display/image.js.map +1 -1
- package/build/components/display/index.d.ts +10 -7
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +10 -7
- package/build/components/display/index.js.map +1 -1
- package/build/components/display/spinner.d.ts +5 -1
- package/build/components/display/spinner.d.ts.map +1 -1
- package/build/components/display/spinner.js +19 -13
- package/build/components/display/spinner.js.map +1 -1
- package/build/components/display/text.d.ts +13 -3
- package/build/components/display/text.d.ts.map +1 -1
- package/build/components/display/text.js +17 -5
- package/build/components/display/text.js.map +1 -1
- package/build/components/display/text_button.d.ts +37 -0
- package/build/components/display/text_button.d.ts.map +1 -0
- package/build/components/display/text_button.js +36 -0
- package/build/components/display/text_button.js.map +1 -0
- package/build/components/display/text_inline_button.d.ts +12 -0
- package/build/components/display/text_inline_button.d.ts.map +1 -0
- package/build/components/display/text_inline_button.js +53 -0
- package/build/components/display/text_inline_button.js.map +1 -0
- package/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/components/primitive/avatar_primitive.d.ts +1 -1
- package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
- package/build/components/primitive/avatar_primitive.js +6 -9
- package/build/components/primitive/avatar_primitive.js.map +1 -1
- package/build/contexts/api_provider.d.ts +4 -6
- package/build/contexts/api_provider.d.ts.map +1 -1
- package/build/contexts/api_provider.js +13 -20
- package/build/contexts/api_provider.js.map +1 -1
- package/build/contexts/chat_context.d.ts +7 -5
- package/build/contexts/chat_context.d.ts.map +1 -1
- package/build/contexts/chat_context.js +40 -4
- package/build/contexts/chat_context.js.map +1 -1
- package/build/hooks/index.d.ts +4 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +4 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_create_android_ripple_color.d.ts +4 -0
- package/build/hooks/use_create_android_ripple_color.d.ts.map +1 -0
- package/build/hooks/use_create_android_ripple_color.js +15 -0
- package/build/hooks/use_create_android_ripple_color.js.map +1 -0
- package/build/hooks/use_current_person.d.ts +3 -0
- package/build/hooks/use_current_person.d.ts.map +1 -0
- package/build/hooks/use_current_person.js +13 -0
- package/build/hooks/use_current_person.js.map +1 -0
- package/build/hooks/use_font_scale.d.ts +4 -0
- package/build/hooks/use_font_scale.d.ts.map +1 -0
- package/build/hooks/use_font_scale.js +8 -0
- package/build/hooks/use_font_scale.js.map +1 -0
- package/build/hooks/use_suspense_api.d.ts +61 -0
- package/build/hooks/use_suspense_api.d.ts.map +1 -0
- package/build/hooks/use_suspense_api.js +39 -0
- package/build/hooks/use_suspense_api.js.map +1 -0
- package/build/navigation/index.d.ts +1 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +7 -4
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +59 -6
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/display.d.ts.map +1 -1
- package/build/screens/display.js +277 -51
- package/build/screens/display.js.map +1 -1
- package/build/screens/not_found.js +1 -1
- package/build/screens/not_found.js.map +1 -1
- package/build/types/api_primitives.d.ts +23 -0
- package/build/types/api_primitives.d.ts.map +1 -0
- package/build/types/api_primitives.js +2 -0
- package/build/types/api_primitives.js.map +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +4 -0
- package/build/types/index.js.map +1 -0
- package/build/types/resources/conversation.d.ts +15 -0
- package/build/types/resources/conversation.d.ts.map +1 -0
- package/build/types/resources/conversation.js +2 -0
- package/build/types/resources/conversation.js.map +1 -0
- package/build/types/resources/index.d.ts +5 -0
- package/build/types/resources/index.d.ts.map +1 -0
- package/build/types/resources/index.js +5 -0
- package/build/types/resources/index.js.map +1 -0
- package/build/types/resources/message.d.ts +16 -0
- package/build/types/resources/message.d.ts.map +1 -0
- package/build/types/resources/message.js +2 -0
- package/build/types/resources/message.js.map +1 -0
- package/build/types/resources/oauth_token.d.ts +9 -0
- package/build/types/resources/oauth_token.d.ts.map +1 -0
- package/build/types/resources/oauth_token.js +2 -0
- package/build/types/resources/oauth_token.js.map +1 -0
- package/build/types/resources/person.d.ts +9 -0
- package/build/types/resources/person.d.ts.map +1 -0
- package/build/types/resources/person.js +2 -0
- package/build/types/resources/person.js.map +1 -0
- package/build/types/resources/reaction.d.ts +10 -0
- package/build/types/resources/reaction.d.ts.map +1 -0
- package/build/types/resources/reaction.js +2 -0
- package/build/types/resources/reaction.js.map +1 -0
- package/build/types/utils/index.d.ts +4 -0
- package/build/types/utils/index.d.ts.map +1 -0
- package/build/types/utils/index.js +4 -0
- package/build/types/utils/index.js.map +1 -0
- package/build/utils/client/client.d.ts +21 -12
- package/build/utils/client/client.d.ts.map +1 -1
- package/build/utils/client/client.js +24 -22
- package/build/utils/client/client.js.map +1 -1
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/session.d.ts +0 -5
- package/build/utils/session.d.ts.map +1 -1
- package/build/utils/session.js +0 -10
- package/build/utils/session.js.map +1 -1
- package/build/utils/styles.d.ts +5 -0
- package/build/utils/styles.d.ts.map +1 -1
- package/build/utils/styles.js +9 -0
- package/build/utils/styles.js.map +1 -1
- package/build/utils/theme.d.ts +3 -1
- package/build/utils/theme.d.ts.map +1 -1
- package/build/utils/theme.js +6 -2
- package/build/utils/theme.js.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts +8 -0
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.js +8 -0
- package/build/vendor/tapestry/alias_tokens_color_map.js.map +1 -1
- package/build/vendor/tapestry/tokens.d.ts +21 -0
- package/build/vendor/tapestry/tokens.d.ts.map +1 -1
- package/build/vendor/tapestry/tokens.js +21 -0
- package/build/vendor/tapestry/tokens.js.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/client.ts +72 -19
- package/src/__tests__/session.ts +0 -11
- package/src/__utils__/handlers.ts +1 -1
- package/src/components/conversations.tsx +33 -11
- package/src/components/display/button.tsx +293 -0
- package/src/components/display/button_color_utils.ts +72 -0
- package/src/components/display/heading.tsx +12 -0
- package/src/components/display/icon.tsx +35 -16
- package/src/components/display/image.tsx +29 -7
- package/src/components/display/index.ts +10 -7
- package/src/components/display/spinner.tsx +42 -13
- package/src/components/display/text.tsx +34 -13
- package/src/components/display/text_button.tsx +102 -0
- package/src/components/display/text_inline_button.tsx +91 -0
- package/src/components/index.tsx +1 -0
- package/src/components/primitive/avatar_primitive.tsx +12 -6
- package/src/contexts/api_provider.tsx +18 -26
- package/src/contexts/chat_context.tsx +61 -7
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use_create_android_ripple_color.ts +18 -0
- package/src/hooks/use_current_person.ts +15 -0
- package/src/hooks/use_font_scale.ts +9 -0
- package/src/hooks/use_suspense_api.ts +60 -0
- package/src/navigation/index.tsx +14 -4
- package/src/screens/conversation_screen.tsx +83 -7
- package/src/screens/display.tsx +447 -51
- package/src/screens/not_found.tsx +1 -1
- package/src/types/api_primitives.ts +24 -0
- package/src/types/index.ts +3 -0
- package/src/types/resources/conversation.ts +15 -0
- package/src/types/resources/index.ts +4 -0
- package/src/types/resources/message.ts +18 -0
- package/src/types/resources/oauth_token.ts +8 -0
- package/src/types/resources/person.ts +9 -0
- package/src/types/resources/reaction.ts +9 -0
- package/src/types/utils/index.ts +6 -0
- package/src/utils/client/client.ts +41 -34
- package/src/utils/client/types.d.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/session.ts +0 -13
- package/src/utils/styles.ts +12 -0
- package/src/utils/theme.ts +9 -3
- package/src/vendor/tapestry/alias_tokens_color_map.ts +12 -0
- package/src/vendor/tapestry/tokens.ts +21 -0
- package/build/utils/api.d.ts +0 -9
- package/build/utils/api.d.ts.map +0 -1
- package/build/utils/api.js +0 -36
- package/build/utils/api.js.map +0 -1
- package/src/types.d.ts +0 -35
- package/src/utils/api.ts +0 -47
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
|
3
|
+
import type { PressableProps, ViewStyle } from 'react-native'
|
|
4
|
+
import LinearGradient from 'react-native-linear-gradient'
|
|
5
|
+
import { Icon } from './icon'
|
|
6
|
+
import { useTheme, useFontScale, useCreateAndroidRippleColor } from '../../hooks'
|
|
7
|
+
import { tokens } from '../../vendor/tapestry/tokens'
|
|
8
|
+
import { platformFontWeightBold, platformPressedOpacityStyle } from '../../utils'
|
|
9
|
+
import { Spinner } from './spinner'
|
|
10
|
+
import { getColorKey, useColorOptionMap, useGradientColorMap } from './button_color_utils'
|
|
11
|
+
import type { AppearanceUnion } from './button_color_utils'
|
|
12
|
+
|
|
13
|
+
// =================================
|
|
14
|
+
// ====== Constants ================
|
|
15
|
+
// =================================
|
|
16
|
+
|
|
17
|
+
const SIZES = {
|
|
18
|
+
sm: 'sm',
|
|
19
|
+
md: 'md',
|
|
20
|
+
lg: 'lg',
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
type SizeUnion = (typeof SIZES)[keyof typeof SIZES]
|
|
24
|
+
type SizeStyle = Record<
|
|
25
|
+
SizeUnion,
|
|
26
|
+
{
|
|
27
|
+
fontSize: number
|
|
28
|
+
paddingHorizontal: number
|
|
29
|
+
height: number
|
|
30
|
+
borderRadius: number
|
|
31
|
+
gap: number
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
|
|
35
|
+
const VARIANTS = {
|
|
36
|
+
fill: 'fill',
|
|
37
|
+
outline: 'outline',
|
|
38
|
+
} as const
|
|
39
|
+
|
|
40
|
+
type VariantUnion = (typeof VARIANTS)[keyof typeof VARIANTS]
|
|
41
|
+
type VariantColors = Record<
|
|
42
|
+
VariantUnion,
|
|
43
|
+
{
|
|
44
|
+
backgroundColor: string
|
|
45
|
+
color: string
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
|
|
49
|
+
// =================================
|
|
50
|
+
// ====== Component ================
|
|
51
|
+
// =================================
|
|
52
|
+
|
|
53
|
+
interface ButtonProps extends PressableProps {
|
|
54
|
+
/**
|
|
55
|
+
* Specifies whether fonts should be scaled down automatically to fit given style constraints.
|
|
56
|
+
*/
|
|
57
|
+
adjustsFontSizeToFit?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Specifies whether fonts should scale to respect the device's text size accessibility settings. The default is true.
|
|
60
|
+
*/
|
|
61
|
+
allowFontScaling?: boolean
|
|
62
|
+
/**
|
|
63
|
+
* Updates the button's colors
|
|
64
|
+
*/
|
|
65
|
+
appearance?: AppearanceUnion
|
|
66
|
+
/**
|
|
67
|
+
* Styles the inner View that wraps the button's content
|
|
68
|
+
*/
|
|
69
|
+
buttonInnerStyles?: ViewStyle
|
|
70
|
+
/**
|
|
71
|
+
* Styles the outer LinearGradient that gives the button it's backgrounnd and outline color
|
|
72
|
+
*/
|
|
73
|
+
buttonOuterStyles?: ViewStyle
|
|
74
|
+
/**
|
|
75
|
+
* Generates an icon to the left of the button text
|
|
76
|
+
*/
|
|
77
|
+
iconNameLeft?: string
|
|
78
|
+
/**
|
|
79
|
+
* Generates an icon to the right of the button text
|
|
80
|
+
*/
|
|
81
|
+
iconNameRight?: string
|
|
82
|
+
/**
|
|
83
|
+
* Disables the button and replaces its content with a spinner
|
|
84
|
+
*/
|
|
85
|
+
loading?: boolean
|
|
86
|
+
/**
|
|
87
|
+
* Specifies the maximum size a font can reach when allowFontScaling is enabled.
|
|
88
|
+
*/
|
|
89
|
+
maxFontSizeMultiplier?: number
|
|
90
|
+
/**
|
|
91
|
+
* Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).
|
|
92
|
+
*/
|
|
93
|
+
minimumFontScale?: number
|
|
94
|
+
/**
|
|
95
|
+
* Changes the overall size of the button and its contents
|
|
96
|
+
*/
|
|
97
|
+
size?: SizeUnion
|
|
98
|
+
/**
|
|
99
|
+
* Renders as text within the button
|
|
100
|
+
*/
|
|
101
|
+
title: string
|
|
102
|
+
/**
|
|
103
|
+
* Fill buttons are visually more propmonitent then outline buttons
|
|
104
|
+
*/
|
|
105
|
+
variant?: VariantUnion
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function Button({
|
|
109
|
+
adjustsFontSizeToFit = false,
|
|
110
|
+
allowFontScaling = true,
|
|
111
|
+
appearance = 'primary',
|
|
112
|
+
buttonInnerStyles,
|
|
113
|
+
buttonOuterStyles,
|
|
114
|
+
disabled = false,
|
|
115
|
+
iconNameLeft,
|
|
116
|
+
iconNameRight,
|
|
117
|
+
loading,
|
|
118
|
+
maxFontSizeMultiplier,
|
|
119
|
+
minimumFontScale,
|
|
120
|
+
size = 'md',
|
|
121
|
+
title,
|
|
122
|
+
variant = 'fill',
|
|
123
|
+
...props
|
|
124
|
+
}: ButtonProps) {
|
|
125
|
+
const styles = useStyles({ appearance, disabled, loading, maxFontSizeMultiplier, size, variant })
|
|
126
|
+
const gradientOptionsMap = useGradientColorMap()
|
|
127
|
+
const colorKey = getColorKey({ disabled, loading, appearance })
|
|
128
|
+
|
|
129
|
+
const textStyles = [styles.text, disabled && styles.textDisabled, loading && styles.iconLoading]
|
|
130
|
+
const iconStyles = [styles.icon, disabled && styles.iconDisabled, loading && styles.textLoading]
|
|
131
|
+
|
|
132
|
+
const androidRippleColor = useCreateAndroidRippleColor({ color: gradientOptionsMap[colorKey][0] })
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Pressable
|
|
136
|
+
style={({ pressed }) => [styles.pressable, pressed && platformPressedOpacityStyle]}
|
|
137
|
+
accessibilityRole="button"
|
|
138
|
+
disabled={disabled || loading}
|
|
139
|
+
accessibilityState={{ busy: loading }}
|
|
140
|
+
android_ripple={{ color: androidRippleColor, borderless: false, foreground: true }}
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
<LinearGradient
|
|
144
|
+
start={{ x: 0.1, y: 0.1 }}
|
|
145
|
+
end={{ x: 0.9, y: 0.9 }}
|
|
146
|
+
colors={gradientOptionsMap[colorKey]}
|
|
147
|
+
style={[styles.colorWrapper, buttonOuterStyles]}
|
|
148
|
+
>
|
|
149
|
+
{loading && (
|
|
150
|
+
<Spinner
|
|
151
|
+
size={styles.spinner.fontSize}
|
|
152
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier || 0}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
<View style={[styles.innerWrapper, buttonInnerStyles]}>
|
|
156
|
+
{iconNameLeft && (
|
|
157
|
+
<Icon
|
|
158
|
+
name={iconNameLeft}
|
|
159
|
+
style={iconStyles}
|
|
160
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
<Text
|
|
164
|
+
allowFontScaling={allowFontScaling}
|
|
165
|
+
minimumFontScale={minimumFontScale}
|
|
166
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
167
|
+
adjustsFontSizeToFit={adjustsFontSizeToFit}
|
|
168
|
+
numberOfLines={1}
|
|
169
|
+
style={textStyles}
|
|
170
|
+
>
|
|
171
|
+
{title}
|
|
172
|
+
</Text>
|
|
173
|
+
{iconNameRight && (
|
|
174
|
+
<Icon
|
|
175
|
+
name={iconNameRight}
|
|
176
|
+
style={iconStyles}
|
|
177
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
</View>
|
|
181
|
+
</LinearGradient>
|
|
182
|
+
</Pressable>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// =================================
|
|
187
|
+
// ====== Styles ===================
|
|
188
|
+
// =================================
|
|
189
|
+
|
|
190
|
+
const useStyles = ({
|
|
191
|
+
appearance = 'primary',
|
|
192
|
+
disabled = false,
|
|
193
|
+
loading = false,
|
|
194
|
+
maxFontSizeMultiplier,
|
|
195
|
+
size = 'md',
|
|
196
|
+
variant = 'fill',
|
|
197
|
+
}: Partial<ButtonProps>) => {
|
|
198
|
+
const { colors } = useTheme()
|
|
199
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier })
|
|
200
|
+
const colorOptionMap = useColorOptionMap()
|
|
201
|
+
|
|
202
|
+
const outlineOffsetSm = 2
|
|
203
|
+
const outlineOffset = 4
|
|
204
|
+
|
|
205
|
+
const sizeStyleMap: SizeStyle = {
|
|
206
|
+
[SIZES.sm]: {
|
|
207
|
+
fontSize: 12,
|
|
208
|
+
paddingHorizontal: 12 * fontScale,
|
|
209
|
+
height: 24 * fontScale - outlineOffsetSm,
|
|
210
|
+
borderRadius: 24 * fontScale,
|
|
211
|
+
gap: 4 * fontScale,
|
|
212
|
+
},
|
|
213
|
+
[SIZES.md]: {
|
|
214
|
+
fontSize: 14,
|
|
215
|
+
paddingHorizontal: 16 * fontScale,
|
|
216
|
+
height: 32 * fontScale - outlineOffset,
|
|
217
|
+
borderRadius: 32 * fontScale,
|
|
218
|
+
gap: 6 * fontScale,
|
|
219
|
+
},
|
|
220
|
+
[SIZES.lg]: {
|
|
221
|
+
fontSize: 16,
|
|
222
|
+
paddingHorizontal: 24 * fontScale,
|
|
223
|
+
height: 40 * fontScale - outlineOffset,
|
|
224
|
+
borderRadius: 40 * fontScale,
|
|
225
|
+
gap: 8 * fontScale,
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const variantOutlineColor =
|
|
230
|
+
disabled || loading ? colors.fillColorNeutral090 : colors.fillColorNeutral100Inverted
|
|
231
|
+
|
|
232
|
+
const variantStyleMap: VariantColors = {
|
|
233
|
+
fill: {
|
|
234
|
+
backgroundColor: 'transparent',
|
|
235
|
+
color: tokens.colorNeutral100White,
|
|
236
|
+
},
|
|
237
|
+
outline: {
|
|
238
|
+
backgroundColor: variantOutlineColor,
|
|
239
|
+
color: colorOptionMap[appearance],
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return StyleSheet.create({
|
|
244
|
+
pressable: {
|
|
245
|
+
borderRadius: sizeStyleMap[size].borderRadius,
|
|
246
|
+
overflow: 'hidden',
|
|
247
|
+
},
|
|
248
|
+
colorWrapper: {
|
|
249
|
+
flexDirection: 'row',
|
|
250
|
+
justifyContent: 'center',
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
borderRadius: sizeStyleMap[size].borderRadius,
|
|
253
|
+
},
|
|
254
|
+
innerWrapper: {
|
|
255
|
+
flexDirection: 'row',
|
|
256
|
+
justifyContent: 'center',
|
|
257
|
+
alignItems: 'center',
|
|
258
|
+
margin: size === 'sm' ? 1 : 2,
|
|
259
|
+
gap: sizeStyleMap[size].gap,
|
|
260
|
+
borderRadius: sizeStyleMap[size].borderRadius,
|
|
261
|
+
height: sizeStyleMap[size].height,
|
|
262
|
+
backgroundColor: variantStyleMap[variant].backgroundColor,
|
|
263
|
+
paddingHorizontal: sizeStyleMap[size].paddingHorizontal,
|
|
264
|
+
},
|
|
265
|
+
text: {
|
|
266
|
+
textAlign: 'center',
|
|
267
|
+
textAlignVertical: 'center',
|
|
268
|
+
includeFontPadding: false,
|
|
269
|
+
fontWeight: platformFontWeightBold,
|
|
270
|
+
fontSize: sizeStyleMap[size].fontSize,
|
|
271
|
+
color: variantStyleMap[variant].color,
|
|
272
|
+
},
|
|
273
|
+
textDisabled: {
|
|
274
|
+
color: colors.textColorDefaultDisabled,
|
|
275
|
+
},
|
|
276
|
+
textLoading: {
|
|
277
|
+
opacity: 0,
|
|
278
|
+
},
|
|
279
|
+
icon: {
|
|
280
|
+
fontSize: sizeStyleMap[size].fontSize,
|
|
281
|
+
color: variantStyleMap[variant].color,
|
|
282
|
+
},
|
|
283
|
+
iconDisabled: {
|
|
284
|
+
color: colors.iconColorDefaultDisabled,
|
|
285
|
+
},
|
|
286
|
+
iconLoading: {
|
|
287
|
+
opacity: 0,
|
|
288
|
+
},
|
|
289
|
+
spinner: {
|
|
290
|
+
fontSize: sizeStyleMap[size].fontSize,
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useTheme } from '../../hooks'
|
|
2
|
+
|
|
3
|
+
// =================================
|
|
4
|
+
// ====== Exports ==================
|
|
5
|
+
// =================================
|
|
6
|
+
|
|
7
|
+
export { getColorKey, useColorOptionMap, useGradientColorMap }
|
|
8
|
+
export type { AppearanceUnion }
|
|
9
|
+
|
|
10
|
+
// =================================
|
|
11
|
+
// ====== Constants ================
|
|
12
|
+
// =================================
|
|
13
|
+
|
|
14
|
+
const APPEARANCES = {
|
|
15
|
+
primary: 'primary',
|
|
16
|
+
danger: 'danger',
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
type AppearanceUnion = (typeof APPEARANCES)[keyof typeof APPEARANCES]
|
|
20
|
+
|
|
21
|
+
const COLOR_OPTIONS = {
|
|
22
|
+
...APPEARANCES,
|
|
23
|
+
disabled: 'disabled',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
type ColorOptionUnion = (typeof COLOR_OPTIONS)[keyof typeof COLOR_OPTIONS]
|
|
27
|
+
|
|
28
|
+
// =================================
|
|
29
|
+
// ====== Hooks ====================
|
|
30
|
+
// =================================
|
|
31
|
+
|
|
32
|
+
type ColorOptionMap = Record<ColorOptionUnion, string>
|
|
33
|
+
function useColorOptionMap(): ColorOptionMap {
|
|
34
|
+
const { colors } = useTheme()
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
primary: colors.interaction,
|
|
38
|
+
danger: colors.fillColorStatusErrorMedium,
|
|
39
|
+
disabled: colors.textColorDefaultDisabled,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type GradientColorMap = Record<ColorOptionUnion, [string, string]>
|
|
44
|
+
function useGradientColorMap(): GradientColorMap {
|
|
45
|
+
const { colors } = useTheme()
|
|
46
|
+
|
|
47
|
+
const defaultColorStart = colors.buttonStart || colors.interaction
|
|
48
|
+
const defaultColorEnd = colors.buttonEnd || colors.interaction
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
primary: [defaultColorStart, defaultColorEnd],
|
|
52
|
+
danger: [colors.fillColorStatusErrorMedium, colors.fillColorStatusErrorMedium],
|
|
53
|
+
disabled: [
|
|
54
|
+
colors.fillColorButtonNeutralSolidDisabled,
|
|
55
|
+
colors.fillColorButtonNeutralSolidDisabled,
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =================================
|
|
61
|
+
// ====== Functions ================
|
|
62
|
+
// =================================
|
|
63
|
+
|
|
64
|
+
interface GetColorKeyArgs {
|
|
65
|
+
disabled: boolean | null
|
|
66
|
+
loading?: boolean
|
|
67
|
+
appearance: AppearanceUnion
|
|
68
|
+
}
|
|
69
|
+
function getColorKey({ disabled, loading, appearance }: GetColorKeyArgs) {
|
|
70
|
+
if (disabled || loading) return 'disabled'
|
|
71
|
+
return appearance
|
|
72
|
+
}
|
|
@@ -9,7 +9,15 @@ import {
|
|
|
9
9
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
10
10
|
import { platformFontWeightBold } from '../../utils/styles'
|
|
11
11
|
|
|
12
|
+
// =================================
|
|
13
|
+
// ====== Component ================
|
|
14
|
+
// =================================
|
|
15
|
+
|
|
12
16
|
interface TextProps extends ReactNativeTextProps {
|
|
17
|
+
/**
|
|
18
|
+
* Changes the styles and size of the text.
|
|
19
|
+
* Semantically all React Native headings have the same 'hierarchical' level.
|
|
20
|
+
*/
|
|
13
21
|
variant?: 'h1' | 'h2' | 'h3' | 'h4'
|
|
14
22
|
}
|
|
15
23
|
|
|
@@ -33,6 +41,10 @@ export function Heading({ style, variant = 'h1', children, ...props }: TextProps
|
|
|
33
41
|
)
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
// =================================
|
|
45
|
+
// ====== Styles ===================
|
|
46
|
+
// =================================
|
|
47
|
+
|
|
36
48
|
const useStyles = () => {
|
|
37
49
|
const { colors } = useTheme()
|
|
38
50
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import type { ViewStyle } from 'react-native'
|
|
2
|
+
import { StyleSheet, View } from 'react-native'
|
|
3
|
+
import type { StyleProp, ViewStyle } from 'react-native'
|
|
4
4
|
import { SvgXml } from 'react-native-svg'
|
|
5
5
|
import type { XmlProps } from 'react-native-svg'
|
|
6
|
-
import { useTheme } from '../../hooks'
|
|
6
|
+
import { useFontScale, useTheme } from '../../hooks'
|
|
7
7
|
import * as general from '@planningcenter/icons/paths/general'
|
|
8
8
|
import * as groups from '@planningcenter/icons/paths/groups'
|
|
9
9
|
import * as calendar from '@planningcenter/icons/paths/calendar'
|
|
@@ -12,6 +12,10 @@ import * as churchCenter from '@planningcenter/icons/paths/church-center'
|
|
|
12
12
|
import * as logomark from '@planningcenter/icons/paths/logomark'
|
|
13
13
|
import * as brand from '@planningcenter/icons/paths/brand'
|
|
14
14
|
|
|
15
|
+
// =================================
|
|
16
|
+
// ====== Constants ================
|
|
17
|
+
// =================================
|
|
18
|
+
|
|
15
19
|
const FALLBACK_SIZE = 12
|
|
16
20
|
|
|
17
21
|
const ICONS = {
|
|
@@ -29,7 +33,11 @@ type IconStyle = ViewStyle & {
|
|
|
29
33
|
color?: string
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
// =================================
|
|
37
|
+
// ====== Component ================
|
|
38
|
+
// =================================
|
|
39
|
+
|
|
40
|
+
interface IconProps extends Omit<XmlProps, 'xml' | 'fontSize'> {
|
|
33
41
|
/**
|
|
34
42
|
* Made up of the set.iconName.
|
|
35
43
|
* Example: "general.textMessage"
|
|
@@ -40,10 +48,14 @@ interface IconProps extends Omit<XmlProps, 'xml'> {
|
|
|
40
48
|
* Providing a fontSize style will allow the icon to scale with the device's text a11y size.
|
|
41
49
|
*/
|
|
42
50
|
size?: number
|
|
51
|
+
/**
|
|
52
|
+
* Specifies the maximum size a font can reach when allowFontScaling is enabled.
|
|
53
|
+
*/
|
|
54
|
+
maxFontSizeMultiplier?: number
|
|
43
55
|
/**
|
|
44
56
|
* Icon can handle ViewStyle, color, and fontSize.
|
|
45
57
|
*/
|
|
46
|
-
style?: IconStyle
|
|
58
|
+
style?: StyleProp<IconStyle>
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
export function Icon({
|
|
@@ -52,11 +64,13 @@ export function Icon({
|
|
|
52
64
|
style,
|
|
53
65
|
accessibilityElementsHidden,
|
|
54
66
|
accessibilityLabel,
|
|
67
|
+
maxFontSizeMultiplier,
|
|
55
68
|
...props
|
|
56
69
|
}: IconProps) {
|
|
70
|
+
const flattenStyles = StyleSheet.flatten(style)
|
|
71
|
+
const iconSize = useGetIconSize(size, flattenStyles, maxFontSizeMultiplier)
|
|
57
72
|
const path = getIconPath(name)
|
|
58
|
-
const
|
|
59
|
-
const styles = useStyles(iconSize)
|
|
73
|
+
const styles = useStyles({ iconSize })
|
|
60
74
|
|
|
61
75
|
if (!path) {
|
|
62
76
|
console.warn(`No icon available named ${name}. Remember to use the format "set.iconName"`)
|
|
@@ -74,35 +88,40 @@ export function Icon({
|
|
|
74
88
|
`}
|
|
75
89
|
height={iconSize}
|
|
76
90
|
width={iconSize}
|
|
77
|
-
style={{ ...styles.icon, ...
|
|
91
|
+
style={{ ...styles.icon, ...flattenStyles }}
|
|
78
92
|
{...props}
|
|
79
93
|
/>
|
|
80
94
|
)
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
const
|
|
84
|
-
const fontSize =
|
|
97
|
+
const useGetIconSize = (size?: number, style?: IconStyle, maxFontSizeMultiplier?: number) => {
|
|
98
|
+
const fontSize = style?.fontSize
|
|
99
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier })
|
|
85
100
|
|
|
86
|
-
if (fontSize) return fontSize *
|
|
101
|
+
if (fontSize) return fontSize * fontScale
|
|
87
102
|
|
|
88
103
|
return size || FALLBACK_SIZE
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
const getIconPath = (name: string) => {
|
|
106
|
+
const getIconPath = (name: string): string => {
|
|
92
107
|
const [setName, iconName] = name.split('.')
|
|
93
108
|
|
|
94
109
|
return ICONS[setName]?.[iconName]
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
|
|
112
|
+
// =================================
|
|
113
|
+
// ====== Styles ===================
|
|
114
|
+
// =================================
|
|
115
|
+
|
|
116
|
+
const useStyles = ({ iconSize }: { iconSize: number }) => {
|
|
98
117
|
const { colors } = useTheme()
|
|
99
118
|
|
|
100
119
|
return StyleSheet.create({
|
|
101
120
|
noIcon: {
|
|
102
121
|
backgroundColor: colors.iconColorDefaultDisabled,
|
|
103
|
-
width:
|
|
104
|
-
height:
|
|
105
|
-
borderRadius:
|
|
122
|
+
width: iconSize,
|
|
123
|
+
height: iconSize,
|
|
124
|
+
borderRadius: iconSize / 2,
|
|
106
125
|
},
|
|
107
126
|
icon: {
|
|
108
127
|
color: colors.iconColorDefaultPrimary,
|
|
@@ -12,9 +12,18 @@ import {
|
|
|
12
12
|
import { useTheme } from '../../hooks'
|
|
13
13
|
import { Spinner } from './spinner'
|
|
14
14
|
|
|
15
|
+
// =================================
|
|
16
|
+
// ====== Component ================
|
|
17
|
+
// =================================
|
|
18
|
+
|
|
15
19
|
export interface ImageProps extends ReactNativeImageProps {
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* Describes the image to screen-readers and marks the image as `accessible`.
|
|
22
|
+
* Passing an empty string will hide the image from screen-readers.
|
|
23
|
+
*/
|
|
24
|
+
alt: string
|
|
25
|
+
/**
|
|
26
|
+
* Shows the image's loading spinner right away. Enabled by default.
|
|
18
27
|
*/
|
|
19
28
|
defaultLoading?: boolean
|
|
20
29
|
/**
|
|
@@ -44,13 +53,14 @@ export function Image({
|
|
|
44
53
|
loadingBackgroundStyles,
|
|
45
54
|
style = {},
|
|
46
55
|
wrapperStyle = {},
|
|
56
|
+
alt,
|
|
47
57
|
...props
|
|
48
58
|
}: ImageProps) {
|
|
49
59
|
const [loading, setLoading] = useState(defaultLoading)
|
|
50
60
|
|
|
51
61
|
const imageStyles = StyleSheet.flatten(style)
|
|
52
62
|
const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {}
|
|
53
|
-
const styles = useStyles(width, height, borderRadius)
|
|
63
|
+
const styles = useStyles({ width, height, borderRadius })
|
|
54
64
|
|
|
55
65
|
const handleOnLoad = (event: any) => {
|
|
56
66
|
setLoading(false)
|
|
@@ -58,11 +68,17 @@ export function Image({
|
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
return (
|
|
61
|
-
<View
|
|
71
|
+
<View
|
|
72
|
+
style={wrapperStyle}
|
|
73
|
+
accessible={Boolean(alt)}
|
|
74
|
+
accessibilityRole="image"
|
|
75
|
+
accessibilityState={{ busy: loading }}
|
|
76
|
+
>
|
|
62
77
|
<ReactNativeImage
|
|
63
78
|
style={[styles.image, imageStyles]}
|
|
64
79
|
onLoad={handleOnLoad}
|
|
65
80
|
source={source}
|
|
81
|
+
alt={loading ? '' : alt}
|
|
66
82
|
{...props}
|
|
67
83
|
/>
|
|
68
84
|
{loading && loadingEnabled && (
|
|
@@ -74,11 +90,17 @@ export function Image({
|
|
|
74
90
|
)
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
// =================================
|
|
94
|
+
// ====== Styles ===================
|
|
95
|
+
// =================================
|
|
96
|
+
|
|
97
|
+
interface Styles {
|
|
98
|
+
width: DimensionValue
|
|
99
|
+
height: DimensionValue
|
|
80
100
|
borderRadius: AnimatableNumericValue | string
|
|
81
|
-
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const useStyles = ({ width, height, borderRadius }: Styles) => {
|
|
82
104
|
const { colors } = useTheme()
|
|
83
105
|
|
|
84
106
|
return StyleSheet.create({
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
1
|
+
export * from './avatar'
|
|
2
|
+
export * from './avatar_group'
|
|
3
|
+
export * from './button'
|
|
4
|
+
export * from './heading'
|
|
5
|
+
export * from './icon'
|
|
6
|
+
export * from './image'
|
|
7
|
+
export * from './spinner'
|
|
8
|
+
export * from './text'
|
|
9
|
+
export * from './text_button'
|
|
10
|
+
export * from './text_inline_button'
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react'
|
|
2
2
|
import { Animated, Easing, StyleSheet, View } from 'react-native'
|
|
3
|
-
import { useTheme } from '../../hooks'
|
|
3
|
+
import { useFontScale, useTheme } from '../../hooks'
|
|
4
|
+
|
|
5
|
+
// =================================
|
|
6
|
+
// ====== Constants ================
|
|
7
|
+
// =================================
|
|
8
|
+
|
|
9
|
+
const PREVENT_SCALING_DEFAULT = 1
|
|
10
|
+
|
|
11
|
+
// =================================
|
|
12
|
+
// ====== Component ================
|
|
13
|
+
// =================================
|
|
4
14
|
|
|
5
15
|
interface SpinnerProps {
|
|
6
16
|
/**
|
|
7
17
|
* Size of the spinner in px
|
|
8
18
|
* */
|
|
9
19
|
size?: number
|
|
20
|
+
/**
|
|
21
|
+
* Specifies the maximum size spinner can scale to if the device's font-size is increased.
|
|
22
|
+
*/
|
|
23
|
+
maxFontSizeMultiplier?: number
|
|
10
24
|
}
|
|
11
25
|
|
|
12
|
-
export function Spinner({
|
|
26
|
+
export function Spinner({
|
|
27
|
+
size = 20,
|
|
28
|
+
maxFontSizeMultiplier = PREVENT_SCALING_DEFAULT,
|
|
29
|
+
}: SpinnerProps) {
|
|
13
30
|
const rotation = useRef(new Animated.Value(0)).current
|
|
14
31
|
|
|
15
32
|
const animation = Animated.loop(
|
|
@@ -26,7 +43,7 @@ export function Spinner({ size = 20 }: SpinnerProps) {
|
|
|
26
43
|
outputRange: ['0deg', '360deg'],
|
|
27
44
|
})
|
|
28
45
|
|
|
29
|
-
const styles = useStyles(rotateValue, size)
|
|
46
|
+
const styles = useStyles({ maxFontSizeMultiplier, rotateValue, size })
|
|
30
47
|
|
|
31
48
|
useEffect(() => {
|
|
32
49
|
animation.start()
|
|
@@ -47,8 +64,20 @@ export function Spinner({ size = 20 }: SpinnerProps) {
|
|
|
47
64
|
)
|
|
48
65
|
}
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
// =================================
|
|
68
|
+
// ====== Styles ===================
|
|
69
|
+
// =================================
|
|
70
|
+
|
|
71
|
+
interface Styles {
|
|
72
|
+
maxFontSizeMultiplier: number | undefined
|
|
73
|
+
rotateValue: Animated.AnimatedInterpolation<string | number>
|
|
74
|
+
size: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const useStyles = ({ maxFontSizeMultiplier, rotateValue, size }: Styles) => {
|
|
51
78
|
const { colors } = useTheme()
|
|
79
|
+
const fontScale = useFontScale({ maxFontSizeMultiplier })
|
|
80
|
+
const scalableSize = size * fontScale
|
|
52
81
|
|
|
53
82
|
return StyleSheet.create({
|
|
54
83
|
container: {
|
|
@@ -63,15 +92,15 @@ const useStyles = (rotate: Animated.AnimatedInterpolation<string | number>, size
|
|
|
63
92
|
opacity: 0.7,
|
|
64
93
|
},
|
|
65
94
|
animatedContainer: {
|
|
66
|
-
width:
|
|
67
|
-
height:
|
|
68
|
-
borderRadius:
|
|
69
|
-
transform: [{ rotate }],
|
|
95
|
+
width: scalableSize,
|
|
96
|
+
height: scalableSize,
|
|
97
|
+
borderRadius: scalableSize / 2,
|
|
98
|
+
transform: [{ rotate: rotateValue }],
|
|
70
99
|
},
|
|
71
100
|
circle: {
|
|
72
|
-
width:
|
|
73
|
-
height:
|
|
74
|
-
borderRadius:
|
|
101
|
+
width: scalableSize,
|
|
102
|
+
height: scalableSize,
|
|
103
|
+
borderRadius: scalableSize / 2,
|
|
75
104
|
borderStyle: 'solid',
|
|
76
105
|
borderWidth: 3,
|
|
77
106
|
},
|
|
@@ -88,8 +117,8 @@ const useStyles = (rotate: Animated.AnimatedInterpolation<string | number>, size
|
|
|
88
117
|
position: 'absolute',
|
|
89
118
|
top: 0,
|
|
90
119
|
left: 0,
|
|
91
|
-
width:
|
|
92
|
-
height:
|
|
120
|
+
width: scalableSize / 2,
|
|
121
|
+
height: scalableSize / 2,
|
|
93
122
|
overflow: 'hidden',
|
|
94
123
|
zIndex: 200,
|
|
95
124
|
},
|