ignite-parse-auth-kit 1.0.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/CONTRIBUTING.md +0 -0
- package/LICENSE +21 -0
- package/README.md +492 -0
- package/app/app.tsx +116 -0
- package/app/components/AlertTongle.tsx +105 -0
- package/app/components/AutoImage.tsx +89 -0
- package/app/components/Button.tsx +248 -0
- package/app/components/Card.tsx +314 -0
- package/app/components/EmptyState.tsx +248 -0
- package/app/components/Header.tsx +332 -0
- package/app/components/Icon.tsx +140 -0
- package/app/components/ListItem.tsx +243 -0
- package/app/components/ListView.tsx +42 -0
- package/app/components/Screen.tsx +305 -0
- package/app/components/Text.test.tsx +23 -0
- package/app/components/Text.tsx +116 -0
- package/app/components/TextField.tsx +292 -0
- package/app/components/Toggle/Checkbox.tsx +123 -0
- package/app/components/Toggle/Radio.tsx +106 -0
- package/app/components/Toggle/Switch.tsx +264 -0
- package/app/components/Toggle/Toggle.tsx +285 -0
- package/app/components/index copy.ts +15 -0
- package/app/components/index.ts +18 -0
- package/app/config/config.base.ts +26 -0
- package/app/config/config.dev.ts +10 -0
- package/app/config/config.prod.ts +10 -0
- package/app/config/index.ts +28 -0
- package/app/context/AuthContext.tsx +14 -0
- package/app/context/EpisodeContext.tsx +136 -0
- package/app/context/auth/AuthProvider.tsx +340 -0
- package/app/context/auth/hooks.ts +29 -0
- package/app/context/auth/index.ts +38 -0
- package/app/context/auth/reducer.ts +68 -0
- package/app/context/auth/services.ts +394 -0
- package/app/context/auth/types.ts +99 -0
- package/app/context/auth/validation.ts +45 -0
- package/app/devtools/ReactotronClient.ts +9 -0
- package/app/devtools/ReactotronClient.web.ts +12 -0
- package/app/devtools/ReactotronConfig.ts +139 -0
- package/app/i18n/ar.ts +126 -0
- package/app/i18n/demo-ar.ts +464 -0
- package/app/i18n/demo-en.ts +462 -0
- package/app/i18n/demo-es.ts +469 -0
- package/app/i18n/demo-fr.ts +471 -0
- package/app/i18n/demo-hi.ts +468 -0
- package/app/i18n/demo-ja.ts +464 -0
- package/app/i18n/demo-ko.ts +457 -0
- package/app/i18n/en.ts +146 -0
- package/app/i18n/es.ts +132 -0
- package/app/i18n/fr.ts +132 -0
- package/app/i18n/hi.ts +131 -0
- package/app/i18n/index.ts +86 -0
- package/app/i18n/ja.ts +130 -0
- package/app/i18n/ko.ts +129 -0
- package/app/i18n/translate.ts +33 -0
- package/app/lib/Parse/index.ts +2 -0
- package/app/lib/Parse/parse.ts +62 -0
- package/app/navigators/AppNavigator.tsx +145 -0
- package/app/navigators/DemoNavigator.tsx +137 -0
- package/app/navigators/navigationUtilities.ts +208 -0
- package/app/screens/ChooseAuthScreen.tsx +224 -0
- package/app/screens/DemoCommunityScreen.tsx +141 -0
- package/app/screens/DemoDebugScreen.tsx +192 -0
- package/app/screens/DemoPodcastListScreen.tsx +387 -0
- package/app/screens/DemoShowroomScreen/DemoDivider.tsx +66 -0
- package/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +313 -0
- package/app/screens/DemoShowroomScreen/DemoUseCase.tsx +52 -0
- package/app/screens/DemoShowroomScreen/DrawerIconButton.tsx +120 -0
- package/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx +59 -0
- package/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx +230 -0
- package/app/screens/DemoShowroomScreen/demos/DemoButton.tsx +234 -0
- package/app/screens/DemoShowroomScreen/demos/DemoCard.tsx +181 -0
- package/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx +78 -0
- package/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx +151 -0
- package/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx +115 -0
- package/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx +218 -0
- package/app/screens/DemoShowroomScreen/demos/DemoText.tsx +144 -0
- package/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx +233 -0
- package/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx +354 -0
- package/app/screens/DemoShowroomScreen/demos/index.ts +12 -0
- package/app/screens/ErrorScreen/ErrorBoundary.tsx +76 -0
- package/app/screens/ErrorScreen/ErrorDetails.tsx +98 -0
- package/app/screens/ForgetPasswordScreen.tsx +180 -0
- package/app/screens/LoginScreen.tsx +260 -0
- package/app/screens/RegisterScreen.tsx +395 -0
- package/app/screens/WelcomeScreen.tsx +114 -0
- package/app/services/api/apiProblem.test.ts +73 -0
- package/app/services/api/apiProblem.ts +74 -0
- package/app/services/api/index.ts +91 -0
- package/app/services/api/types.ts +50 -0
- package/app/theme/colors.ts +85 -0
- package/app/theme/colorsDark.ts +50 -0
- package/app/theme/context.tsx +145 -0
- package/app/theme/context.utils.ts +25 -0
- package/app/theme/spacing.ts +14 -0
- package/app/theme/spacingDark.ts +14 -0
- package/app/theme/styles.ts +24 -0
- package/app/theme/theme.ts +23 -0
- package/app/theme/timing.ts +6 -0
- package/app/theme/types.ts +64 -0
- package/app/theme/typography.ts +71 -0
- package/app/utils/crashReporting.ts +62 -0
- package/app/utils/delay.ts +6 -0
- package/app/utils/formatDate.ts +49 -0
- package/app/utils/gestureHandler.native.ts +3 -0
- package/app/utils/gestureHandler.ts +6 -0
- package/app/utils/hasValidStringProp.ts +11 -0
- package/app/utils/openLinkInBrowser.ts +8 -0
- package/app/utils/storage/index.ts +82 -0
- package/app/utils/storage/storage.test.ts +61 -0
- package/app/utils/useHeader.tsx +37 -0
- package/app/utils/useIsMounted.ts +18 -0
- package/app/utils/useSafeAreaInsetsStyle.ts +46 -0
- package/app.config.ts +39 -0
- package/app.json +67 -0
- package/assets/icons/back.png +0 -0
- package/assets/icons/back@2x.png +0 -0
- package/assets/icons/back@3x.png +0 -0
- package/assets/icons/bell.png +0 -0
- package/assets/icons/bell@2x.png +0 -0
- package/assets/icons/bell@3x.png +0 -0
- package/assets/icons/caretLeft.png +0 -0
- package/assets/icons/caretLeft@2x.png +0 -0
- package/assets/icons/caretLeft@3x.png +0 -0
- package/assets/icons/caretRight.png +0 -0
- package/assets/icons/caretRight@2x.png +0 -0
- package/assets/icons/caretRight@3x.png +0 -0
- package/assets/icons/check.png +0 -0
- package/assets/icons/check@2x.png +0 -0
- package/assets/icons/check@3x.png +0 -0
- package/assets/icons/demo/clap.png +0 -0
- package/assets/icons/demo/clap@2x.png +0 -0
- package/assets/icons/demo/clap@3x.png +0 -0
- package/assets/icons/demo/community.png +0 -0
- package/assets/icons/demo/community@2x.png +0 -0
- package/assets/icons/demo/community@3x.png +0 -0
- package/assets/icons/demo/components.png +0 -0
- package/assets/icons/demo/components@2x.png +0 -0
- package/assets/icons/demo/components@3x.png +0 -0
- package/assets/icons/demo/debug.png +0 -0
- package/assets/icons/demo/debug@2x.png +0 -0
- package/assets/icons/demo/debug@3x.png +0 -0
- package/assets/icons/demo/github.png +0 -0
- package/assets/icons/demo/github@2x.png +0 -0
- package/assets/icons/demo/github@3x.png +0 -0
- package/assets/icons/demo/heart.png +0 -0
- package/assets/icons/demo/heart@2x.png +0 -0
- package/assets/icons/demo/heart@3x.png +0 -0
- package/assets/icons/demo/pin.png +0 -0
- package/assets/icons/demo/pin@2x.png +0 -0
- package/assets/icons/demo/pin@3x.png +0 -0
- package/assets/icons/demo/podcast.png +0 -0
- package/assets/icons/demo/podcast@2x.png +0 -0
- package/assets/icons/demo/podcast@3x.png +0 -0
- package/assets/icons/demo/slack.png +0 -0
- package/assets/icons/demo/slack@2x.png +0 -0
- package/assets/icons/demo/slack@3x.png +0 -0
- package/assets/icons/google.png +0 -0
- package/assets/icons/hidden.png +0 -0
- package/assets/icons/hidden@2x.png +0 -0
- package/assets/icons/hidden@3x.png +0 -0
- package/assets/icons/ladybug.png +0 -0
- package/assets/icons/ladybug@2x.png +0 -0
- package/assets/icons/ladybug@3x.png +0 -0
- package/assets/icons/lock.png +0 -0
- package/assets/icons/lock@2x.png +0 -0
- package/assets/icons/lock@3x.png +0 -0
- package/assets/icons/menu.png +0 -0
- package/assets/icons/menu@2x.png +0 -0
- package/assets/icons/menu@3x.png +0 -0
- package/assets/icons/more.png +0 -0
- package/assets/icons/more@2x.png +0 -0
- package/assets/icons/more@3x.png +0 -0
- package/assets/icons/settings.png +0 -0
- package/assets/icons/settings@2x.png +0 -0
- package/assets/icons/settings@3x.png +0 -0
- package/assets/icons/view.png +0 -0
- package/assets/icons/view@2x.png +0 -0
- package/assets/icons/view@3x.png +0 -0
- package/assets/icons/x.png +0 -0
- package/assets/icons/x@2x.png +0 -0
- package/assets/icons/x@3x.png +0 -0
- package/assets/images/app-icon-all.png +0 -0
- package/assets/images/app-icon-android-adaptive-background.png +0 -0
- package/assets/images/app-icon-android-adaptive-foreground.png +0 -0
- package/assets/images/app-icon-android-legacy.png +0 -0
- package/assets/images/app-icon-ios.png +0 -0
- package/assets/images/app-icon-web-favicon.png +0 -0
- package/assets/images/demo/cr-logo.png +0 -0
- package/assets/images/demo/cr-logo@2x.png +0 -0
- package/assets/images/demo/cr-logo@3x.png +0 -0
- package/assets/images/demo/rnl-logo.png +0 -0
- package/assets/images/demo/rnl-logo@2x.png +0 -0
- package/assets/images/demo/rnl-logo@3x.png +0 -0
- package/assets/images/demo/rnn-logo.png +0 -0
- package/assets/images/demo/rnn-logo@2x.png +0 -0
- package/assets/images/demo/rnn-logo@3x.png +0 -0
- package/assets/images/demo/rnr-image-1.png +0 -0
- package/assets/images/demo/rnr-image-1@2x.png +0 -0
- package/assets/images/demo/rnr-image-1@3x.png +0 -0
- package/assets/images/demo/rnr-image-2.png +0 -0
- package/assets/images/demo/rnr-image-2@2x.png +0 -0
- package/assets/images/demo/rnr-image-2@3x.png +0 -0
- package/assets/images/demo/rnr-image-3.png +0 -0
- package/assets/images/demo/rnr-image-3@2x.png +0 -0
- package/assets/images/demo/rnr-image-3@3x.png +0 -0
- package/assets/images/demo/rnr-logo.png +0 -0
- package/assets/images/demo/rnr-logo@2x.png +0 -0
- package/assets/images/demo/rnr-logo@3x.png +0 -0
- package/assets/images/logo.png +0 -0
- package/assets/images/logo@2x.png +0 -0
- package/assets/images/logo@3x.png +0 -0
- package/assets/images/sad-face.png +0 -0
- package/assets/images/sad-face@2x.png +0 -0
- package/assets/images/sad-face@3x.png +0 -0
- package/assets/images/welcome-face.png +0 -0
- package/assets/images/welcome-face@2x.png +0 -0
- package/assets/images/welcome-face@3x.png +0 -0
- package/babel.config.js +7 -0
- package/bin/cli.js +196 -0
- package/ignite/templates/app-icon/android-adaptive-background.png +0 -0
- package/ignite/templates/app-icon/android-adaptive-foreground.png +0 -0
- package/ignite/templates/app-icon/android-legacy.png +0 -0
- package/ignite/templates/app-icon/ios-universal.png +0 -0
- package/ignite/templates/component/NAME.tsx.ejs +39 -0
- package/ignite/templates/navigator/NAMENavigator.tsx.ejs +18 -0
- package/ignite/templates/screen/NAMEScreen.tsx.ejs +29 -0
- package/ignite/templates/splash-screen/logo.png +0 -0
- package/index.tsx +9 -0
- package/jest.config.js +5 -0
- package/metro.config.js +31 -0
- package/package.json +166 -0
- package/plugins/withSplashScreen.ts +69 -0
- package/src/app/_layout.tsx +58 -0
- package/src/app/index.tsx +5 -0
- package/test/i18n.test.ts +75 -0
- package/test/mockFile.ts +6 -0
- package/test/setup.ts +58 -0
- package/test/test-tsconfig.json +8 -0
- package/tsconfig.json +52 -0
- package/types/lib.es5.d.ts +25 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Alert, StyleProp, TextStyle, View, ViewStyle } from "react-native"
|
|
2
|
+
import { useAppTheme } from "@/theme/context"
|
|
3
|
+
import type { ThemedStyle } from "@/theme/types"
|
|
4
|
+
import { Text } from "@/components/Text"
|
|
5
|
+
import React, { useEffect } from "react"
|
|
6
|
+
import { observer } from "mobx-react-lite"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface AlertTongleProps {
|
|
10
|
+
/**
|
|
11
|
+
* An optional style override useful for padding & margin.
|
|
12
|
+
*/
|
|
13
|
+
style?: StyleProp<ViewStyle>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type AlertItem = {
|
|
17
|
+
title: string
|
|
18
|
+
message: string
|
|
19
|
+
buttons?: Array<{
|
|
20
|
+
text: string
|
|
21
|
+
onPress?: () => void
|
|
22
|
+
style?: 'default' | 'cancel' | 'destructive'
|
|
23
|
+
}>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Static queue manager
|
|
27
|
+
class AlertQueueManager {
|
|
28
|
+
private static queue: AlertItem[] = []
|
|
29
|
+
private static isShowing = false
|
|
30
|
+
private static currentAlert: AlertItem | null = null
|
|
31
|
+
|
|
32
|
+
static enqueue(alert: AlertItem) {
|
|
33
|
+
if (this.isDuplicate(alert)) return
|
|
34
|
+
|
|
35
|
+
this.queue.push(alert)
|
|
36
|
+
this.showNext()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static isDuplicate(alert: AlertItem): boolean {
|
|
40
|
+
if (this.isShowing && this.currentAlert?.title === alert.title && this.currentAlert?.message === alert.message) {
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
if (this.queue.some(q => q.title === alert.title && q.message === alert.message)) {
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private static showNext() {
|
|
50
|
+
if (this.isShowing || this.queue.length === 0) return
|
|
51
|
+
|
|
52
|
+
this.isShowing = true
|
|
53
|
+
const nextAlert = this.queue.shift()!
|
|
54
|
+
this.currentAlert = nextAlert
|
|
55
|
+
|
|
56
|
+
const { title, message, buttons = [{ text: "OK" }] } = nextAlert
|
|
57
|
+
|
|
58
|
+
Alert.alert(title, message, buttons.map(button => ({
|
|
59
|
+
...button,
|
|
60
|
+
onPress: () => {
|
|
61
|
+
button.onPress?.()
|
|
62
|
+
this.isShowing = false
|
|
63
|
+
this.currentAlert = null
|
|
64
|
+
this.showNext()
|
|
65
|
+
},
|
|
66
|
+
})))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Exportable function
|
|
71
|
+
export const showQueuedAlert = (alert: AlertItem) => {
|
|
72
|
+
AlertQueueManager.enqueue(alert)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// COMPONENT
|
|
77
|
+
export const AlertTongle = observer(function AlertTongle(props: AlertTongleProps) {
|
|
78
|
+
const { style } = props
|
|
79
|
+
const $styles = [$container, style]
|
|
80
|
+
const { themed } = useAppTheme()
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
// Example trigger
|
|
84
|
+
showQueuedAlert({
|
|
85
|
+
title: "Initial Alert",
|
|
86
|
+
message: "This is triggered from AlertTongle component",
|
|
87
|
+
})
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<View style={$styles}>
|
|
92
|
+
<Text style={themed($text)}>Hello</Text>
|
|
93
|
+
</View>
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const $container: ViewStyle = {
|
|
98
|
+
justifyContent: "center",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const $text: ThemedStyle<TextStyle> = ({ colors, typography }) => ({
|
|
102
|
+
fontFamily: typography.primary.normal,
|
|
103
|
+
fontSize: 14,
|
|
104
|
+
color: colors.palette.primary500,
|
|
105
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from "react"
|
|
2
|
+
import { Image, ImageProps, ImageURISource, Platform, PixelRatio } from "react-native"
|
|
3
|
+
|
|
4
|
+
export interface AutoImageProps extends ImageProps {
|
|
5
|
+
/**
|
|
6
|
+
* How wide should the image be?
|
|
7
|
+
*/
|
|
8
|
+
maxWidth?: number
|
|
9
|
+
/**
|
|
10
|
+
* How tall should the image be?
|
|
11
|
+
*/
|
|
12
|
+
maxHeight?: number
|
|
13
|
+
headers?: {
|
|
14
|
+
[key: string]: string
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A hook that will return the scaled dimensions of an image based on the
|
|
20
|
+
* provided dimensions' aspect ratio. If no desired dimensions are provided,
|
|
21
|
+
* it will return the original dimensions of the remote image.
|
|
22
|
+
*
|
|
23
|
+
* How is this different from `resizeMode: 'contain'`? Firstly, you can
|
|
24
|
+
* specify only one side's size (not both). Secondly, the image will scale to fit
|
|
25
|
+
* the desired dimensions instead of just being contained within its image-container.
|
|
26
|
+
* @param {number} remoteUri - The URI of the remote image.
|
|
27
|
+
* @param {number} dimensions - The desired dimensions of the image. If not provided, the original dimensions will be returned.
|
|
28
|
+
* @returns {[number, number]} - The scaled dimensions of the image.
|
|
29
|
+
*/
|
|
30
|
+
export function useAutoImage(
|
|
31
|
+
remoteUri: string,
|
|
32
|
+
headers?: {
|
|
33
|
+
[key: string]: string
|
|
34
|
+
},
|
|
35
|
+
dimensions?: [maxWidth?: number, maxHeight?: number],
|
|
36
|
+
): [width: number, height: number] {
|
|
37
|
+
const [[remoteWidth, remoteHeight], setRemoteImageDimensions] = useState([0, 0])
|
|
38
|
+
const remoteAspectRatio = remoteWidth / remoteHeight
|
|
39
|
+
const [maxWidth, maxHeight] = dimensions ?? []
|
|
40
|
+
|
|
41
|
+
useLayoutEffect(() => {
|
|
42
|
+
if (!remoteUri) return
|
|
43
|
+
|
|
44
|
+
if (!headers) {
|
|
45
|
+
Image.getSize(remoteUri, (w, h) => setRemoteImageDimensions([w, h]))
|
|
46
|
+
} else {
|
|
47
|
+
Image.getSizeWithHeaders(remoteUri, headers, (w, h) => setRemoteImageDimensions([w, h]))
|
|
48
|
+
}
|
|
49
|
+
}, [remoteUri, headers])
|
|
50
|
+
|
|
51
|
+
if (Number.isNaN(remoteAspectRatio)) return [0, 0]
|
|
52
|
+
|
|
53
|
+
if (maxWidth && maxHeight) {
|
|
54
|
+
const aspectRatio = Math.min(maxWidth / remoteWidth, maxHeight / remoteHeight)
|
|
55
|
+
return [
|
|
56
|
+
PixelRatio.roundToNearestPixel(remoteWidth * aspectRatio),
|
|
57
|
+
PixelRatio.roundToNearestPixel(remoteHeight * aspectRatio),
|
|
58
|
+
]
|
|
59
|
+
} else if (maxWidth) {
|
|
60
|
+
return [maxWidth, PixelRatio.roundToNearestPixel(maxWidth / remoteAspectRatio)]
|
|
61
|
+
} else if (maxHeight) {
|
|
62
|
+
return [PixelRatio.roundToNearestPixel(maxHeight * remoteAspectRatio), maxHeight]
|
|
63
|
+
} else {
|
|
64
|
+
return [remoteWidth, remoteHeight]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* An Image component that automatically sizes a remote or data-uri image.
|
|
70
|
+
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/AutoImage/}
|
|
71
|
+
* @param {AutoImageProps} props - The props for the `AutoImage` component.
|
|
72
|
+
* @returns {JSX.Element} The rendered `AutoImage` component.
|
|
73
|
+
*/
|
|
74
|
+
export function AutoImage(props: AutoImageProps) {
|
|
75
|
+
const { maxWidth, maxHeight, ...ImageProps } = props
|
|
76
|
+
const source = props.source as ImageURISource
|
|
77
|
+
const headers = source?.headers
|
|
78
|
+
|
|
79
|
+
const [width, height] = useAutoImage(
|
|
80
|
+
Platform.select({
|
|
81
|
+
web: (source?.uri as string) ?? (source as string),
|
|
82
|
+
default: source?.uri as string,
|
|
83
|
+
}),
|
|
84
|
+
headers,
|
|
85
|
+
[maxWidth, maxHeight],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return <Image {...ImageProps} style={[{ width, height }, props.style]} />
|
|
89
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { ComponentType } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
PressableProps,
|
|
5
|
+
PressableStateCallbackType,
|
|
6
|
+
StyleProp,
|
|
7
|
+
TextStyle,
|
|
8
|
+
ViewStyle,
|
|
9
|
+
} from "react-native"
|
|
10
|
+
|
|
11
|
+
import { useAppTheme } from "@/theme/context"
|
|
12
|
+
import { $styles } from "@/theme/styles"
|
|
13
|
+
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
|
|
14
|
+
|
|
15
|
+
import { Text, TextProps } from "./Text"
|
|
16
|
+
|
|
17
|
+
type Presets = "default" | "filled" | "reversed"
|
|
18
|
+
|
|
19
|
+
export interface ButtonAccessoryProps {
|
|
20
|
+
style: StyleProp<any>
|
|
21
|
+
pressableState: PressableStateCallbackType
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ButtonProps extends PressableProps {
|
|
26
|
+
/**
|
|
27
|
+
* Text which is looked up via i18n.
|
|
28
|
+
*/
|
|
29
|
+
tx?: TextProps["tx"]
|
|
30
|
+
/**
|
|
31
|
+
* The text to display if not using `tx` or nested components.
|
|
32
|
+
*/
|
|
33
|
+
text?: TextProps["text"]
|
|
34
|
+
/**
|
|
35
|
+
* Optional options to pass to i18n. Useful for interpolation
|
|
36
|
+
* as well as explicitly setting locale or translation fallbacks.
|
|
37
|
+
*/
|
|
38
|
+
txOptions?: TextProps["txOptions"]
|
|
39
|
+
/**
|
|
40
|
+
* An optional style override useful for padding & margin.
|
|
41
|
+
*/
|
|
42
|
+
style?: StyleProp<ViewStyle>
|
|
43
|
+
/**
|
|
44
|
+
* An optional style override for the "pressed" state.
|
|
45
|
+
*/
|
|
46
|
+
pressedStyle?: StyleProp<ViewStyle>
|
|
47
|
+
/**
|
|
48
|
+
* An optional style override for the button text.
|
|
49
|
+
*/
|
|
50
|
+
textStyle?: StyleProp<TextStyle>
|
|
51
|
+
/**
|
|
52
|
+
* An optional style override for the button text when in the "pressed" state.
|
|
53
|
+
*/
|
|
54
|
+
pressedTextStyle?: StyleProp<TextStyle>
|
|
55
|
+
/**
|
|
56
|
+
* An optional style override for the button text when in the "disabled" state.
|
|
57
|
+
*/
|
|
58
|
+
disabledTextStyle?: StyleProp<TextStyle>
|
|
59
|
+
/**
|
|
60
|
+
* One of the different types of button presets.
|
|
61
|
+
*/
|
|
62
|
+
preset?: Presets
|
|
63
|
+
/**
|
|
64
|
+
* An optional component to render on the right side of the text.
|
|
65
|
+
* Example: `RightAccessory={(props) => <View {...props} />}`
|
|
66
|
+
*/
|
|
67
|
+
RightAccessory?: ComponentType<ButtonAccessoryProps>
|
|
68
|
+
/**
|
|
69
|
+
* An optional component to render on the left side of the text.
|
|
70
|
+
* Example: `LeftAccessory={(props) => <View {...props} />}`
|
|
71
|
+
*/
|
|
72
|
+
LeftAccessory?: ComponentType<ButtonAccessoryProps>
|
|
73
|
+
/**
|
|
74
|
+
* Children components.
|
|
75
|
+
*/
|
|
76
|
+
children?: React.ReactNode
|
|
77
|
+
/**
|
|
78
|
+
* disabled prop, accessed directly for declarative styling reasons.
|
|
79
|
+
* https://reactnative.dev/docs/pressable#disabled
|
|
80
|
+
*/
|
|
81
|
+
disabled?: boolean
|
|
82
|
+
/**
|
|
83
|
+
* An optional style override for the disabled state
|
|
84
|
+
*/
|
|
85
|
+
disabledStyle?: StyleProp<ViewStyle>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A component that allows users to take actions and make choices.
|
|
90
|
+
* Wraps the Text component with a Pressable component.
|
|
91
|
+
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Button/}
|
|
92
|
+
* @param {ButtonProps} props - The props for the `Button` component.
|
|
93
|
+
* @returns {JSX.Element} The rendered `Button` component.
|
|
94
|
+
* @example
|
|
95
|
+
* <Button
|
|
96
|
+
* tx="common:ok"
|
|
97
|
+
* style={styles.button}
|
|
98
|
+
* textStyle={styles.buttonText}
|
|
99
|
+
* onPress={handleButtonPress}
|
|
100
|
+
* />
|
|
101
|
+
*/
|
|
102
|
+
export function Button(props: ButtonProps) {
|
|
103
|
+
const {
|
|
104
|
+
tx,
|
|
105
|
+
text,
|
|
106
|
+
txOptions,
|
|
107
|
+
style: $viewStyleOverride,
|
|
108
|
+
pressedStyle: $pressedViewStyleOverride,
|
|
109
|
+
textStyle: $textStyleOverride,
|
|
110
|
+
pressedTextStyle: $pressedTextStyleOverride,
|
|
111
|
+
disabledTextStyle: $disabledTextStyleOverride,
|
|
112
|
+
children,
|
|
113
|
+
RightAccessory,
|
|
114
|
+
LeftAccessory,
|
|
115
|
+
disabled,
|
|
116
|
+
disabledStyle: $disabledViewStyleOverride,
|
|
117
|
+
...rest
|
|
118
|
+
} = props
|
|
119
|
+
|
|
120
|
+
const { themed } = useAppTheme()
|
|
121
|
+
|
|
122
|
+
const preset: Presets = props.preset ?? "default"
|
|
123
|
+
/**
|
|
124
|
+
* @param {PressableStateCallbackType} root0 - The root object containing the pressed state.
|
|
125
|
+
* @param {boolean} root0.pressed - The pressed state.
|
|
126
|
+
* @returns {StyleProp<ViewStyle>} The view style based on the pressed state.
|
|
127
|
+
*/
|
|
128
|
+
function $viewStyle({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> {
|
|
129
|
+
return [
|
|
130
|
+
themed($viewPresets[preset]),
|
|
131
|
+
$viewStyleOverride,
|
|
132
|
+
!!pressed && themed([$pressedViewPresets[preset], $pressedViewStyleOverride]),
|
|
133
|
+
!!disabled && $disabledViewStyleOverride,
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @param {PressableStateCallbackType} root0 - The root object containing the pressed state.
|
|
138
|
+
* @param {boolean} root0.pressed - The pressed state.
|
|
139
|
+
* @returns {StyleProp<TextStyle>} The text style based on the pressed state.
|
|
140
|
+
*/
|
|
141
|
+
function $textStyle({ pressed }: PressableStateCallbackType): StyleProp<TextStyle> {
|
|
142
|
+
return [
|
|
143
|
+
themed($textPresets[preset]),
|
|
144
|
+
$textStyleOverride,
|
|
145
|
+
!!pressed && themed([$pressedTextPresets[preset], $pressedTextStyleOverride]),
|
|
146
|
+
!!disabled && $disabledTextStyleOverride,
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Pressable
|
|
152
|
+
style={$viewStyle}
|
|
153
|
+
accessibilityRole="button"
|
|
154
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
155
|
+
{...rest}
|
|
156
|
+
disabled={disabled}
|
|
157
|
+
>
|
|
158
|
+
{(state) => (
|
|
159
|
+
<>
|
|
160
|
+
{!!LeftAccessory && (
|
|
161
|
+
<LeftAccessory style={$leftAccessoryStyle} pressableState={state} disabled={disabled} />
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
<Text tx={tx} text={text} txOptions={txOptions} style={$textStyle(state)}>
|
|
165
|
+
{children}
|
|
166
|
+
</Text>
|
|
167
|
+
|
|
168
|
+
{!!RightAccessory && (
|
|
169
|
+
<RightAccessory
|
|
170
|
+
style={$rightAccessoryStyle}
|
|
171
|
+
pressableState={state}
|
|
172
|
+
disabled={disabled}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
</>
|
|
176
|
+
)}
|
|
177
|
+
</Pressable>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const $baseViewStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|
182
|
+
minHeight: 56,
|
|
183
|
+
borderRadius: 4,
|
|
184
|
+
justifyContent: "center",
|
|
185
|
+
alignItems: "center",
|
|
186
|
+
paddingVertical: spacing.sm,
|
|
187
|
+
paddingHorizontal: spacing.sm,
|
|
188
|
+
overflow: "hidden",
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const $baseTextStyle: ThemedStyle<TextStyle> = ({ typography }) => ({
|
|
192
|
+
fontSize: 16,
|
|
193
|
+
lineHeight: 20,
|
|
194
|
+
fontFamily: typography.primary.medium,
|
|
195
|
+
textAlign: "center",
|
|
196
|
+
flexShrink: 1,
|
|
197
|
+
flexGrow: 0,
|
|
198
|
+
zIndex: 2,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|
202
|
+
marginStart: spacing.xs,
|
|
203
|
+
zIndex: 1,
|
|
204
|
+
})
|
|
205
|
+
const $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|
206
|
+
marginEnd: spacing.xs,
|
|
207
|
+
zIndex: 1,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const $viewPresets: Record<Presets, ThemedStyleArray<ViewStyle>> = {
|
|
211
|
+
default: [
|
|
212
|
+
$styles.row,
|
|
213
|
+
$baseViewStyle,
|
|
214
|
+
({ colors }) => ({
|
|
215
|
+
borderWidth: 1,
|
|
216
|
+
borderColor: colors.palette.neutral400,
|
|
217
|
+
backgroundColor: colors.palette.neutral100,
|
|
218
|
+
}),
|
|
219
|
+
],
|
|
220
|
+
filled: [
|
|
221
|
+
$styles.row,
|
|
222
|
+
$baseViewStyle,
|
|
223
|
+
({ colors }) => ({ backgroundColor: colors.palette.neutral300 }),
|
|
224
|
+
],
|
|
225
|
+
reversed: [
|
|
226
|
+
$styles.row,
|
|
227
|
+
$baseViewStyle,
|
|
228
|
+
({ colors }) => ({ backgroundColor: colors.palette.neutral800 }),
|
|
229
|
+
],
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const $textPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {
|
|
233
|
+
default: [$baseTextStyle],
|
|
234
|
+
filled: [$baseTextStyle],
|
|
235
|
+
reversed: [$baseTextStyle, ({ colors }) => ({ color: colors.palette.neutral100 })],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const $pressedViewPresets: Record<Presets, ThemedStyle<ViewStyle>> = {
|
|
239
|
+
default: ({ colors }) => ({ backgroundColor: colors.palette.neutral200 }),
|
|
240
|
+
filled: ({ colors }) => ({ backgroundColor: colors.palette.neutral400 }),
|
|
241
|
+
reversed: ({ colors }) => ({ backgroundColor: colors.palette.neutral700 }),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const $pressedTextPresets: Record<Presets, ThemedStyle<TextStyle>> = {
|
|
245
|
+
default: () => ({ opacity: 0.9 }),
|
|
246
|
+
filled: () => ({ opacity: 0.9 }),
|
|
247
|
+
reversed: () => ({ opacity: 0.9 }),
|
|
248
|
+
}
|