feat: mobile app package (#1075)
This commit is contained in:
29
packages/mobile/src/Components/BlockingModal.styled.ts
Normal file
29
packages/mobile/src/Components/BlockingModal.styled.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
export const Container = styled.View`
|
||||
flex: 1;
|
||||
background-color: transparent;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const Content = styled.View`
|
||||
background-color: ${({ theme }) => theme.stylekitBackgroundColor};
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
width: 80%;
|
||||
`
|
||||
|
||||
export const Title = styled.Text`
|
||||
color: ${({ theme }) => theme.stylekitForegroundColor};
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
`
|
||||
|
||||
export const Subtitle = styled.Text`
|
||||
color: ${({ theme }) => theme.stylekitForegroundColor};
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
`
|
||||
17
packages/mobile/src/Components/BlockingModal.tsx
Normal file
17
packages/mobile/src/Components/BlockingModal.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { MODAL_BLOCKING_ALERT } from '@Root/Screens/screens'
|
||||
import React from 'react'
|
||||
import { Container, Content, Subtitle, Title } from './BlockingModal.styled'
|
||||
|
||||
type Props = ModalStackNavigationProp<typeof MODAL_BLOCKING_ALERT>
|
||||
|
||||
export const BlockingModal = ({ route: { params } }: Props) => {
|
||||
return (
|
||||
<Container>
|
||||
<Content>
|
||||
{params.title && <Title>{params.title}</Title>}
|
||||
<Subtitle>{params.text}</Subtitle>
|
||||
</Content>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
75
packages/mobile/src/Components/Button.tsx
Normal file
75
packages/mobile/src/Components/Button.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components/native'
|
||||
|
||||
type Props = {
|
||||
onPress: () => void
|
||||
label: string
|
||||
primary?: boolean
|
||||
fullWidth?: boolean
|
||||
last?: boolean
|
||||
}
|
||||
|
||||
const PrimaryButtonContainer = styled.TouchableOpacity.attrs({
|
||||
activeOpacity: 0.84,
|
||||
})<{
|
||||
fullWidth?: boolean
|
||||
last?: boolean
|
||||
}>`
|
||||
background-color: ${({ theme }) => theme.stylekitInfoColor};
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
border-width: 1px;
|
||||
border-color: ${({ theme }) => theme.stylekitInfoColor};
|
||||
margin-bottom: ${({ fullWidth, last }) => (fullWidth && !last ? '16px' : 0)};
|
||||
${({ fullWidth }) =>
|
||||
!fullWidth &&
|
||||
css`
|
||||
align-self: center;
|
||||
`};
|
||||
`
|
||||
|
||||
const SecondaryButtonContainer = styled.TouchableHighlight.attrs(({ theme }) => ({
|
||||
activeOpacity: 0.84,
|
||||
underlayColor: theme.stylekitBorderColor,
|
||||
}))<{
|
||||
fullWidth?: boolean
|
||||
last?: boolean
|
||||
}>`
|
||||
background-color: ${({ theme }) => theme.stylekitBackgroundColor};
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
border-width: 1px;
|
||||
border-color: ${({ theme }) => theme.stylekitBorderColor};
|
||||
margin-bottom: ${({ fullWidth, last }) => (fullWidth && !last ? '16px' : 0)};
|
||||
${({ fullWidth }) =>
|
||||
!fullWidth &&
|
||||
css`
|
||||
align-self: center;
|
||||
`};
|
||||
`
|
||||
|
||||
const ButtonLabel = styled.Text<{ primary?: boolean }>`
|
||||
text-align: center;
|
||||
text-align-vertical: center;
|
||||
font-weight: bold;
|
||||
color: ${({ theme, primary }) => {
|
||||
return primary ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor
|
||||
}};
|
||||
font-size: ${props => props.theme.mainTextFontSize}px;
|
||||
`
|
||||
|
||||
export const Button: React.FC<Props> = ({ onPress, label, primary, fullWidth, last }: Props) => {
|
||||
if (primary) {
|
||||
return (
|
||||
<PrimaryButtonContainer onPress={onPress} fullWidth={fullWidth} last={last}>
|
||||
<ButtonLabel primary={primary}>{label}</ButtonLabel>
|
||||
</PrimaryButtonContainer>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<SecondaryButtonContainer onPress={onPress} fullWidth={fullWidth} last={last}>
|
||||
<ButtonLabel primary={primary}>{label}</ButtonLabel>
|
||||
</SecondaryButtonContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
80
packages/mobile/src/Components/ButtonCell.tsx
Normal file
80
packages/mobile/src/Components/ButtonCell.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import styled, { css } from 'styled-components/native'
|
||||
import { Props as TableCellProps, SectionedTableCellTouchableHighlight } from './SectionedTableCell'
|
||||
|
||||
type Props = {
|
||||
testID?: string
|
||||
maxHeight?: number
|
||||
leftAligned?: boolean
|
||||
bold?: boolean
|
||||
disabled?: boolean
|
||||
important?: boolean
|
||||
onPress: () => void
|
||||
first?: boolean
|
||||
last?: boolean
|
||||
title?: string
|
||||
}
|
||||
|
||||
type ContainerProps = Pick<Props, 'maxHeight'> & TableCellProps
|
||||
const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({
|
||||
underlayColor: props.theme.stylekitBorderColor,
|
||||
}))<ContainerProps>`
|
||||
padding-top: ${12}px;
|
||||
justify-content: center;
|
||||
${({ maxHeight }) =>
|
||||
maxHeight &&
|
||||
css`
|
||||
max-height: 50px;
|
||||
`};
|
||||
`
|
||||
const ButtonContainer = styled.View``
|
||||
|
||||
type ButtonLabelProps = Pick<Props, 'leftAligned' | 'bold' | 'disabled' | 'important'>
|
||||
const ButtonLabel = styled.Text<ButtonLabelProps>`
|
||||
text-align: ${props => (props.leftAligned ? 'left' : 'center')};
|
||||
text-align-vertical: center;
|
||||
color: ${props => {
|
||||
let color = Platform.OS === 'android' ? props.theme.stylekitForegroundColor : props.theme.stylekitInfoColor
|
||||
if (props.disabled) {
|
||||
color = 'gray'
|
||||
} else if (props.important) {
|
||||
color = props.theme.stylekitDangerColor
|
||||
}
|
||||
return color
|
||||
}};
|
||||
font-size: ${props => props.theme.mainTextFontSize}px;
|
||||
${({ bold }) =>
|
||||
bold &&
|
||||
css`
|
||||
font-weight: bold;
|
||||
`}
|
||||
${({ disabled }) =>
|
||||
disabled &&
|
||||
css`
|
||||
opacity: 0.6;
|
||||
`}
|
||||
`
|
||||
|
||||
export const ButtonCell: React.FC<Props> = props => (
|
||||
<Container
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
maxHeight={props.maxHeight}
|
||||
testID={props.testID}
|
||||
disabled={props.disabled}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
<ButtonContainer>
|
||||
<ButtonLabel
|
||||
important={props.important}
|
||||
disabled={props.disabled}
|
||||
bold={props.bold}
|
||||
leftAligned={props.leftAligned}
|
||||
>
|
||||
{props.title}
|
||||
</ButtonLabel>
|
||||
{props.children && props.children}
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
)
|
||||
112
packages/mobile/src/Components/Chip.tsx
Normal file
112
packages/mobile/src/Components/Chip.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
import { Animated } from 'react-native'
|
||||
import { TouchableWithoutFeedback } from 'react-native-gesture-handler'
|
||||
import styled, { css } from 'styled-components/native'
|
||||
|
||||
type Props = {
|
||||
selected: boolean
|
||||
onPress: () => void
|
||||
label: string
|
||||
last?: boolean
|
||||
}
|
||||
|
||||
const Container = styled.View<{
|
||||
last?: boolean
|
||||
}>`
|
||||
border-radius: 100px;
|
||||
padding: 5px 10px;
|
||||
border-width: 1px;
|
||||
${({ last }) =>
|
||||
!last &&
|
||||
css`
|
||||
margin-right: 8px;
|
||||
`};
|
||||
`
|
||||
|
||||
const Label = styled.Text<{ selected: boolean }>`
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const ActiveContainer = styled(Container)`
|
||||
background-color: ${({ theme }) => theme.stylekitInfoColor};
|
||||
border-color: ${({ theme }) => theme.stylekitInfoColor};
|
||||
`
|
||||
|
||||
const InactiveContainer = styled(Container)`
|
||||
position: absolute;
|
||||
background-color: ${({ theme }) => theme.stylekitInfoContrastColor};
|
||||
border-color: ${({ theme }) => theme.stylekitBorderColor};
|
||||
`
|
||||
|
||||
const ActiveLabel = styled(Label)`
|
||||
color: ${({ theme }) => theme.stylekitNeutralContrastColor};
|
||||
`
|
||||
|
||||
const InactiveLabel = styled(Label)`
|
||||
color: ${({ theme }) => theme.stylekitNeutralColor};
|
||||
`
|
||||
|
||||
export const Chip: React.FC<Props> = ({ selected, onPress, label, last }) => {
|
||||
const animationValue = useRef(new Animated.Value(selected ? 1 : 0)).current
|
||||
const selectedRef = useRef<boolean>(selected)
|
||||
|
||||
const toggleChip = useCallback(() => {
|
||||
Animated.timing(animationValue, {
|
||||
toValue: selected ? 1 : 0,
|
||||
duration: 100,
|
||||
useNativeDriver: true,
|
||||
}).start()
|
||||
}, [animationValue, selected])
|
||||
|
||||
useEffect(() => {
|
||||
if (selected !== selectedRef.current) {
|
||||
toggleChip()
|
||||
selectedRef.current = selected
|
||||
}
|
||||
}, [selected, toggleChip])
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={onPress}>
|
||||
<ActiveContainer
|
||||
as={Animated.View}
|
||||
last={last}
|
||||
style={{
|
||||
opacity: animationValue,
|
||||
}}
|
||||
>
|
||||
<ActiveLabel
|
||||
as={Animated.Text}
|
||||
selected={selected}
|
||||
style={{
|
||||
opacity: animationValue,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</ActiveLabel>
|
||||
</ActiveContainer>
|
||||
<InactiveContainer
|
||||
as={Animated.View}
|
||||
last={last}
|
||||
style={{
|
||||
opacity: animationValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<InactiveLabel
|
||||
as={Animated.Text}
|
||||
selected={selected}
|
||||
style={{
|
||||
opacity: animationValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</InactiveLabel>
|
||||
</InactiveContainer>
|
||||
</TouchableWithoutFeedback>
|
||||
)
|
||||
}
|
||||
16
packages/mobile/src/Components/Circle.ts
Normal file
16
packages/mobile/src/Components/Circle.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
type Props = {
|
||||
size?: number
|
||||
backgroundColor?: string
|
||||
borderColor?: string
|
||||
}
|
||||
|
||||
export const Circle = styled.View<Props>`
|
||||
width: ${props => props.size ?? 12}px;
|
||||
height: ${props => props.size ?? 12}px;
|
||||
border-radius: ${props => (props.size ?? 12) / 2}px;
|
||||
background-color: ${props => props.backgroundColor};
|
||||
border-color: ${props => props.borderColor};
|
||||
border-width: 1px;
|
||||
`
|
||||
41
packages/mobile/src/Components/HeaderTitleView.tsx
Normal file
41
packages/mobile/src/Components/HeaderTitleView.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
type Props = {
|
||||
subtitleColor?: string
|
||||
title: string
|
||||
subtitle?: string
|
||||
}
|
||||
|
||||
const Container = styled.View`
|
||||
/* background-color: ${props => props.theme.stylekitContrastBackgroundColor}; */
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
|
||||
${Platform.OS === 'android' && 'align-items: flex-start; min-width: 100px;'}
|
||||
`
|
||||
const Title = styled.Text`
|
||||
color: ${props => props.theme.stylekitForegroundColor};
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
`
|
||||
const SubTitle = styled.Text.attrs(() => ({
|
||||
adjustsFontSizeToFit: true,
|
||||
numberOfLines: 1,
|
||||
}))<{ color?: string }>`
|
||||
color: ${props => props.color ?? props.theme.stylekitForegroundColor};
|
||||
opacity: ${props => (props.color ? 1 : 0.6)};
|
||||
font-size: ${Platform.OS === 'android' ? 13 : 12}px;
|
||||
${Platform.OS === 'ios' && 'text-align: center'}
|
||||
`
|
||||
|
||||
export const HeaderTitleView: React.FC<Props> = props => (
|
||||
<Container>
|
||||
<Title>{props.title}</Title>
|
||||
{props.subtitle && props.subtitle.length > 0 ? (
|
||||
<SubTitle color={props.subtitleColor}>{props.subtitle}</SubTitle>
|
||||
) : undefined}
|
||||
</Container>
|
||||
)
|
||||
8
packages/mobile/src/Components/Icon.styled.ts
Normal file
8
packages/mobile/src/Components/Icon.styled.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export const iconStyles = StyleSheet.create({
|
||||
icon: {
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
})
|
||||
19
packages/mobile/src/Components/IoniconsHeaderButton.tsx
Normal file
19
packages/mobile/src/Components/IoniconsHeaderButton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { useContext } from 'react'
|
||||
import Icon from 'react-native-vector-icons/Ionicons'
|
||||
import { HeaderButton, HeaderButtonProps } from 'react-navigation-header-buttons'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
export const IoniconsHeaderButton = (passMeFurther: HeaderButtonProps) => {
|
||||
// the `passMeFurther` variable here contains props from <Item .../> as well as <HeaderButtons ... />
|
||||
// and it is important to pass those props to `HeaderButton`
|
||||
// then you may add some information like icon size or color (if you use icons)
|
||||
const theme = useContext(ThemeContext)
|
||||
return (
|
||||
<HeaderButton
|
||||
IconComponent={Icon}
|
||||
iconSize={30}
|
||||
color={passMeFurther.disabled ? 'gray' : theme.stylekitInfoColor}
|
||||
{...passMeFurther}
|
||||
/>
|
||||
)
|
||||
}
|
||||
7
packages/mobile/src/Components/SearchBar.styled.ts
Normal file
7
packages/mobile/src/Components/SearchBar.styled.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export const searchBarStyles = StyleSheet.create({
|
||||
androidSearch: {
|
||||
height: 30,
|
||||
},
|
||||
})
|
||||
94
packages/mobile/src/Components/SearchBar.tsx
Normal file
94
packages/mobile/src/Components/SearchBar.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { ThemeServiceContext } from '@Style/ThemeService'
|
||||
import React, { FC, RefObject, useCallback, useContext } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import IosSearchBar from 'react-native-search-bar'
|
||||
import AndroidSearchBar from 'react-native-search-box'
|
||||
import { ThemeContext } from 'styled-components/native'
|
||||
import { searchBarStyles } from './/SearchBar.styled'
|
||||
|
||||
type Props = {
|
||||
onChangeText: (text: string) => void
|
||||
onSearchCancel: () => void
|
||||
iosSearchBarInputRef: RefObject<IosSearchBar>
|
||||
androidSearchBarInputRef: RefObject<typeof AndroidSearchBar>
|
||||
onSearchFocusCallback?: () => void
|
||||
onSearchBlurCallback?: () => void
|
||||
collapseSearchBarOnBlur?: boolean
|
||||
}
|
||||
|
||||
export const SearchBar: FC<Props> = ({
|
||||
onChangeText,
|
||||
onSearchCancel,
|
||||
iosSearchBarInputRef,
|
||||
androidSearchBarInputRef,
|
||||
onSearchFocusCallback,
|
||||
onSearchBlurCallback,
|
||||
collapseSearchBarOnBlur = true,
|
||||
}) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
const themeService = useContext(ThemeServiceContext)
|
||||
|
||||
const onSearchFocus = useCallback(() => {
|
||||
onSearchFocusCallback?.()
|
||||
}, [onSearchFocusCallback])
|
||||
|
||||
const onSearchBlur = useCallback(() => {
|
||||
onSearchBlurCallback?.()
|
||||
}, [onSearchBlurCallback])
|
||||
|
||||
return (
|
||||
<>
|
||||
{Platform.OS === 'ios' && (
|
||||
<IosSearchBar
|
||||
ref={iosSearchBarInputRef}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
placeholder="Search"
|
||||
hideBackground
|
||||
appearance={themeService?.keyboardColorForActiveTheme()}
|
||||
barTintColor={theme.stylekitInfoColor}
|
||||
textFieldBackgroundColor={theme.stylekitContrastBackgroundColor}
|
||||
onChangeText={onChangeText}
|
||||
onSearchButtonPress={() => {
|
||||
iosSearchBarInputRef.current?.blur()
|
||||
}}
|
||||
onCancelButtonPress={() => {
|
||||
iosSearchBarInputRef.current?.blur()
|
||||
onSearchCancel()
|
||||
}}
|
||||
onFocus={onSearchFocus}
|
||||
onBlur={onSearchBlur}
|
||||
/>
|
||||
)}
|
||||
{Platform.OS === 'android' && (
|
||||
<AndroidSearchBar
|
||||
ref={androidSearchBarInputRef}
|
||||
onChangeText={onChangeText}
|
||||
onCancel={() => {
|
||||
onSearchBlur()
|
||||
onSearchCancel()
|
||||
}}
|
||||
onDelete={onSearchCancel}
|
||||
onFocus={onSearchFocus}
|
||||
onBlur={onSearchBlur}
|
||||
collapseOnBlur={collapseSearchBarOnBlur}
|
||||
blurOnSubmit={true}
|
||||
backgroundColor={theme.stylekitBackgroundColor}
|
||||
titleCancelColor={theme.stylekitInfoColor}
|
||||
keyboardDismissMode={'interactive'}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
inputBorderRadius={4}
|
||||
tintColorSearch={theme.stylekitForegroundColor}
|
||||
inputStyle={[
|
||||
searchBarStyles.androidSearch,
|
||||
{
|
||||
color: theme.stylekitForegroundColor,
|
||||
backgroundColor: theme.stylekitContrastBackgroundColor,
|
||||
},
|
||||
]}
|
||||
placeholderExpandedMargin={25}
|
||||
searchIconCollapsedMargin={30}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
76
packages/mobile/src/Components/SectionHeader.tsx
Normal file
76
packages/mobile/src/Components/SectionHeader.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
type Props = {
|
||||
title?: string
|
||||
subtitle?: string
|
||||
buttonText?: string
|
||||
buttonAction?: () => void
|
||||
buttonStyles?: any
|
||||
tinted?: boolean
|
||||
backgroundColor?: string
|
||||
}
|
||||
|
||||
const Container = styled.View<Pick<Props, 'backgroundColor'>>`
|
||||
/* flex: 1; */
|
||||
/* flex-grow: 0; */
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
padding-right: ${props => props.theme.paddingLeft}px;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
background-color: ${props => props.backgroundColor ?? props.theme.stylekitBackgroundColor};
|
||||
`
|
||||
const TitleContainer = styled.View``
|
||||
const Title = styled.Text<Pick<Props, 'tinted'>>`
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
font-size: ${props => {
|
||||
return Platform.OS === 'android' ? props.theme.mainTextFontSize - 2 : props.theme.mainTextFontSize - 4
|
||||
}}px;
|
||||
padding-left: ${props => props.theme.paddingLeft}px;
|
||||
color: ${props => {
|
||||
if (props.tinted) {
|
||||
return props.theme.stylekitInfoColor
|
||||
}
|
||||
|
||||
return Platform.OS === 'android' ? props.theme.stylekitInfoColor : props.theme.stylekitNeutralColor
|
||||
}};
|
||||
font-weight: ${Platform.OS === 'android' ? 'bold' : 'normal'};
|
||||
`
|
||||
const SubTitle = styled.Text`
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
font-size: ${props => props.theme.mainTextFontSize - 5}px;
|
||||
margin-top: 4px;
|
||||
padding-left: ${props => props.theme.paddingLeft}px;
|
||||
color: ${props => props.theme.stylekitNeutralColor};
|
||||
`
|
||||
const ButtonContainer = styled.TouchableOpacity`
|
||||
flex: 1;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
`
|
||||
const Button = styled.Text`
|
||||
color: ${props => props.theme.stylekitInfoColor};
|
||||
`
|
||||
|
||||
export const SectionHeader: React.FC<Props> = props => (
|
||||
<Container>
|
||||
<TitleContainer>
|
||||
{!!props.title && (
|
||||
<Title>
|
||||
{Platform.select({
|
||||
ios: props.title.toUpperCase(),
|
||||
android: props.title,
|
||||
})}
|
||||
</Title>
|
||||
)}
|
||||
{!!props.subtitle && <SubTitle>{props.subtitle}</SubTitle>}
|
||||
</TitleContainer>
|
||||
{!!props.buttonText && (
|
||||
<ButtonContainer onPress={props.buttonAction}>
|
||||
<Button style={props.buttonStyles}>{props.buttonText}</Button>
|
||||
</ButtonContainer>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
132
packages/mobile/src/Components/SectionedAccessoryTableCell.tsx
Normal file
132
packages/mobile/src/Components/SectionedAccessoryTableCell.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import Icon from 'react-native-vector-icons/Ionicons'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/native'
|
||||
import { SectionedTableCellTouchableHighlight } from './/SectionedTableCell'
|
||||
|
||||
type Props = {
|
||||
testID?: string
|
||||
disabled?: boolean
|
||||
onPress: () => void
|
||||
onLongPress?: () => void
|
||||
iconName?: string
|
||||
selected?: () => boolean
|
||||
leftAlignIcon?: boolean
|
||||
color?: string
|
||||
bold?: boolean
|
||||
tinted?: boolean
|
||||
dimmed?: boolean
|
||||
text: string
|
||||
first?: boolean
|
||||
last?: boolean
|
||||
}
|
||||
|
||||
const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs(props => ({
|
||||
underlayColor: props.theme.stylekitBorderColor,
|
||||
}))`
|
||||
flex-direction: column;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
min-height: 47px;
|
||||
background-color: transparent;
|
||||
`
|
||||
const ContentContainer = styled.View<Pick<Props, 'leftAlignIcon'>>`
|
||||
flex: 1;
|
||||
justify-content: ${props => {
|
||||
return props.leftAlignIcon ? 'flex-start' : 'space-between'
|
||||
}};
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`
|
||||
const IconContainer = styled.View`
|
||||
width: 30px;
|
||||
max-width: 30px;
|
||||
`
|
||||
type LabelProps = Pick<Props, 'bold' | 'tinted' | 'dimmed' | 'selected' | 'color'>
|
||||
const Label = styled.Text<LabelProps>`
|
||||
min-width: 80%;
|
||||
color: ${props => {
|
||||
let color = props.theme.stylekitForegroundColor
|
||||
if (props.tinted) {
|
||||
color = props.theme.stylekitInfoColor
|
||||
}
|
||||
if (props.dimmed) {
|
||||
color = props.theme.stylekitNeutralColor
|
||||
}
|
||||
if (props.color) {
|
||||
color = props.color
|
||||
}
|
||||
return color
|
||||
}};
|
||||
font-size: ${props => props.theme.mainTextFontSize}px;
|
||||
${({ bold, selected }) =>
|
||||
((selected && selected() === true) || bold) &&
|
||||
css`
|
||||
font-weight: bold;
|
||||
`};
|
||||
`
|
||||
|
||||
export const SectionedAccessoryTableCell: React.FC<Props> = props => {
|
||||
const themeContext = useContext(ThemeContext)
|
||||
const onPress = () => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
props.onPress()
|
||||
}
|
||||
|
||||
const onLongPress = () => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.onLongPress) {
|
||||
props.onLongPress()
|
||||
}
|
||||
}
|
||||
|
||||
const checkmarkName = Platform.OS === 'android' ? 'md-checkbox' : 'ios-checkmark-circle'
|
||||
const iconName = props.iconName ? props.iconName : props.selected && props.selected() ? checkmarkName : null
|
||||
|
||||
const left = props.leftAlignIcon
|
||||
let iconSize = left ? 25 : 30
|
||||
let color = left ? themeContext.stylekitForegroundColor : themeContext.stylekitInfoColor
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
iconSize -= 5
|
||||
}
|
||||
|
||||
if (props.color) {
|
||||
color = props.color
|
||||
}
|
||||
let icon: any = null
|
||||
|
||||
if (iconName) {
|
||||
icon = (
|
||||
<IconContainer key={iconName}>
|
||||
<Icon name={iconName} size={iconSize} color={color} />
|
||||
</IconContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const textWrapper = (
|
||||
<Label
|
||||
tinted={props.tinted}
|
||||
dimmed={props.dimmed}
|
||||
bold={props.bold}
|
||||
selected={props.selected}
|
||||
color={props.color}
|
||||
key={1}
|
||||
>
|
||||
{props.text}
|
||||
</Label>
|
||||
)
|
||||
|
||||
return (
|
||||
<TouchableContainer first={props.first} last={props.last} onPress={onPress} onLongPress={onLongPress}>
|
||||
<ContentContainer>{props.leftAlignIcon ? [icon, textWrapper] : [textWrapper, icon]}</ContentContainer>
|
||||
</TouchableContainer>
|
||||
)
|
||||
}
|
||||
88
packages/mobile/src/Components/SectionedOptionsTableCell.tsx
Normal file
88
packages/mobile/src/Components/SectionedOptionsTableCell.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components/native'
|
||||
|
||||
export type Option = { selected: boolean; key: string; title: string }
|
||||
|
||||
type Props = {
|
||||
testID?: string
|
||||
title: string
|
||||
first?: boolean
|
||||
height?: number
|
||||
onPress: (option: Option) => void
|
||||
options: Option[]
|
||||
leftAligned?: boolean
|
||||
}
|
||||
type ContainerProps = Omit<Props, 'title' | 'onPress' | 'options'>
|
||||
|
||||
export const Container = styled.View<ContainerProps>`
|
||||
border-bottom-color: ${props => props.theme.stylekitBorderColor};
|
||||
border-bottom-width: 1px;
|
||||
padding-left: ${props => props.theme.paddingLeft}px;
|
||||
padding-right: ${props => props.theme.paddingLeft}px;
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
${({ first, theme }) =>
|
||||
first &&
|
||||
css`
|
||||
border-top-color: ${theme.stylekitBorderColor};
|
||||
border-top-width: ${1}px;
|
||||
`};
|
||||
${({ height }) =>
|
||||
height &&
|
||||
css`
|
||||
height: ${height}px;
|
||||
`};
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-height: 45px;
|
||||
`
|
||||
|
||||
const Title = styled.Text<{ leftAligned?: boolean }>`
|
||||
font-size: ${props => props.theme.mainTextFontSize}px;
|
||||
color: ${props => props.theme.stylekitForegroundColor};
|
||||
text-align: ${props => (props.leftAligned ? 'left' : 'center')};
|
||||
width: 42%;
|
||||
min-width: 0px;
|
||||
`
|
||||
|
||||
const OptionsContainer = styled.View`
|
||||
width: 58%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
`
|
||||
|
||||
const ButtonTouchable = styled.TouchableHighlight.attrs(props => ({
|
||||
underlayColor: props.theme.stylekitBorderColor,
|
||||
}))`
|
||||
border-left-color: ${props => props.theme.stylekitBorderColor};
|
||||
border-left-width: 1px;
|
||||
flex-grow: 1;
|
||||
padding: 10px;
|
||||
padding-top: 12px;
|
||||
`
|
||||
|
||||
const ButtonTitle = styled.Text<{ selected: boolean }>`
|
||||
color: ${props => {
|
||||
return props.selected ? props.theme.stylekitInfoColor : props.theme.stylekitNeutralColor
|
||||
}};
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
export const SectionedOptionsTableCell: React.FC<Props> = props => (
|
||||
<Container first={props.first}>
|
||||
<Title leftAligned={props.leftAligned}>{props.title}</Title>
|
||||
<OptionsContainer>
|
||||
{props.options.map(option => {
|
||||
return (
|
||||
<ButtonTouchable key={option.title} onPress={() => props.onPress(option)}>
|
||||
<ButtonTitle selected={option.selected}>{option.title}</ButtonTitle>
|
||||
</ButtonTouchable>
|
||||
)
|
||||
})}
|
||||
</OptionsContainer>
|
||||
</Container>
|
||||
)
|
||||
59
packages/mobile/src/Components/SectionedTableCell.ts
Normal file
59
packages/mobile/src/Components/SectionedTableCell.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import styled, { css } from 'styled-components/native'
|
||||
|
||||
export type Props = {
|
||||
first?: boolean
|
||||
last?: boolean
|
||||
textInputCell?: any
|
||||
height?: number
|
||||
extraStyles?: any
|
||||
}
|
||||
|
||||
export const SectionedTableCell = styled.View<Props>`
|
||||
border-bottom-color: ${props => props.theme.stylekitBorderColor};
|
||||
border-bottom-width: 1px;
|
||||
padding-left: ${props => props.theme.paddingLeft}px;
|
||||
padding-right: ${props => props.theme.paddingLeft}px;
|
||||
padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px;
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
${({ first, theme }) =>
|
||||
first &&
|
||||
css`
|
||||
border-top-color: ${theme.stylekitBorderColor};
|
||||
border-top-width: ${1}px;
|
||||
`};
|
||||
${({ textInputCell }) =>
|
||||
textInputCell &&
|
||||
css`
|
||||
max-height: 50px;
|
||||
`};
|
||||
${({ height }) =>
|
||||
height &&
|
||||
css`
|
||||
height: ${height}px;
|
||||
`};
|
||||
`
|
||||
|
||||
export const SectionedTableCellTouchableHighlight = styled.TouchableHighlight<Props>`
|
||||
border-bottom-color: ${props => props.theme.stylekitBorderColor};
|
||||
border-bottom-width: 1px;
|
||||
padding-left: ${props => props.theme.paddingLeft}px;
|
||||
padding-right: ${props => props.theme.paddingLeft}px;
|
||||
padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px;
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
${({ first, theme }) =>
|
||||
first &&
|
||||
css`
|
||||
border-top-color: ${theme.stylekitBorderColor};
|
||||
border-top-width: ${1}px;
|
||||
`};
|
||||
${({ textInputCell }) =>
|
||||
textInputCell &&
|
||||
css`
|
||||
max-height: 50px;
|
||||
`};
|
||||
${({ height }) =>
|
||||
height &&
|
||||
css`
|
||||
height: ${height}px;
|
||||
`};
|
||||
`
|
||||
101
packages/mobile/src/Components/SnIcon.tsx
Normal file
101
packages/mobile/src/Components/SnIcon.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import ArchiveIcon from '@standardnotes/icons/dist/mobile-exports/ic-archive.svg'
|
||||
import AttachmentFileIcon from '@standardnotes/icons/dist/mobile-exports/ic-attachment-file.svg'
|
||||
import AuthenticatorIcon from '@standardnotes/icons/dist/mobile-exports/ic-authenticator.svg'
|
||||
import ClearCircleFilledIcon from '@standardnotes/icons/dist/mobile-exports/ic-clear-circle-filled.svg'
|
||||
import CodeIcon from '@standardnotes/icons/dist/mobile-exports/ic-code.svg'
|
||||
import FileDocIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-doc.svg'
|
||||
import FileImageIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-image.svg'
|
||||
import FileMovIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-mov.svg'
|
||||
import FileMusicIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-music.svg'
|
||||
import FileOtherIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-other.svg'
|
||||
import FilePdfIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-pdf.svg'
|
||||
import FilePptIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-ppt.svg'
|
||||
import FileXlsIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-xls.svg'
|
||||
import FileZipIcon from '@standardnotes/icons/dist/mobile-exports/ic-file-zip.svg'
|
||||
import LockFilledIcon from '@standardnotes/icons/dist/mobile-exports/ic-lock-filled.svg'
|
||||
import MarkdownIcon from '@standardnotes/icons/dist/mobile-exports/ic-markdown.svg'
|
||||
import NotesIcon from '@standardnotes/icons/dist/mobile-exports/ic-notes.svg'
|
||||
import OpenInIcon from '@standardnotes/icons/dist/mobile-exports/ic-open-in.svg'
|
||||
import PencilOffIcon from '@standardnotes/icons/dist/mobile-exports/ic-pencil-off.svg'
|
||||
import PinFilledIcon from '@standardnotes/icons/dist/mobile-exports/ic-pin-filled.svg'
|
||||
import SpreadsheetsIcon from '@standardnotes/icons/dist/mobile-exports/ic-spreadsheets.svg'
|
||||
import TasksIcon from '@standardnotes/icons/dist/mobile-exports/ic-tasks.svg'
|
||||
import PlainTextIcon from '@standardnotes/icons/dist/mobile-exports/ic-text-paragraph.svg'
|
||||
import RichTextIcon from '@standardnotes/icons/dist/mobile-exports/ic-text-rich.svg'
|
||||
import TrashFilledIcon from '@standardnotes/icons/dist/mobile-exports/ic-trash-filled.svg'
|
||||
import UserAddIcon from '@standardnotes/icons/dist/mobile-exports/ic-user-add.svg'
|
||||
import FilesIllustration from '@standardnotes/icons/dist/mobile-exports/il-files.svg'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import React, { FC, useContext } from 'react'
|
||||
import { SvgProps } from 'react-native-svg'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { iconStyles } from './/Icon.styled'
|
||||
|
||||
type TIcons = {
|
||||
[key in IconType]: FC<SvgProps>
|
||||
}
|
||||
|
||||
const ICONS: Partial<TIcons> = {
|
||||
'pencil-off': PencilOffIcon,
|
||||
'plain-text': PlainTextIcon,
|
||||
'rich-text': RichTextIcon,
|
||||
code: CodeIcon,
|
||||
markdown: MarkdownIcon,
|
||||
spreadsheets: SpreadsheetsIcon,
|
||||
tasks: TasksIcon,
|
||||
authenticator: AuthenticatorIcon,
|
||||
'trash-filled': TrashFilledIcon,
|
||||
'pin-filled': PinFilledIcon,
|
||||
archive: ArchiveIcon,
|
||||
'user-add': UserAddIcon,
|
||||
'open-in': OpenInIcon,
|
||||
notes: NotesIcon,
|
||||
'attachment-file': AttachmentFileIcon,
|
||||
'files-illustration': FilesIllustration,
|
||||
'file-doc': FileDocIcon,
|
||||
'file-image': FileImageIcon,
|
||||
'file-mov': FileMovIcon,
|
||||
'file-music': FileMusicIcon,
|
||||
'file-other': FileOtherIcon,
|
||||
'file-pdf': FilePdfIcon,
|
||||
'file-ppt': FilePptIcon,
|
||||
'file-xls': FileXlsIcon,
|
||||
'file-zip': FileZipIcon,
|
||||
'clear-circle-filled': ClearCircleFilledIcon,
|
||||
'lock-filled': LockFilledIcon,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: IconType
|
||||
fill?: string
|
||||
style?: Record<string, unknown>
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
export const SnIcon = ({ type, fill, width, height, style = {} }: Props) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
const fillColor = fill || theme.stylekitPalSky
|
||||
|
||||
const IconComponent = ICONS[type]
|
||||
|
||||
if (!IconComponent) {
|
||||
return null
|
||||
}
|
||||
|
||||
let customSizes = {}
|
||||
if (width !== undefined) {
|
||||
customSizes = {
|
||||
...customSizes,
|
||||
width,
|
||||
}
|
||||
}
|
||||
if (height !== undefined) {
|
||||
customSizes = {
|
||||
...customSizes,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
return <IconComponent fill={fillColor} {...customSizes} style={[iconStyles.icon, style]} />
|
||||
}
|
||||
7
packages/mobile/src/Components/TableSection.ts
Normal file
7
packages/mobile/src/Components/TableSection.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components/native'
|
||||
|
||||
export const TableSection = styled.View`
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: ${props => props.theme.stylekitBackgroundColor};
|
||||
`
|
||||
31
packages/mobile/src/Components/ToastWrapper.styled.ts
Normal file
31
packages/mobile/src/Components/ToastWrapper.styled.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { DefaultTheme } from 'styled-components/native'
|
||||
|
||||
export const useToastStyles = (theme: DefaultTheme) => {
|
||||
return (props: { [key: string]: unknown }) => {
|
||||
return StyleSheet.create({
|
||||
info: {
|
||||
borderLeftColor: theme.stylekitInfoColor,
|
||||
height: props.percentComplete !== undefined ? 70 : 60,
|
||||
},
|
||||
animatedViewContainer: {
|
||||
height: 8,
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
borderColor: theme.stylekitInfoColor,
|
||||
marginRight: 8,
|
||||
marginLeft: 12,
|
||||
marginTop: -16,
|
||||
},
|
||||
animatedView: {
|
||||
backgroundColor: theme.stylekitInfoColor,
|
||||
},
|
||||
success: {
|
||||
borderLeftColor: theme.stylekitSuccessColor,
|
||||
},
|
||||
error: {
|
||||
borderLeftColor: theme.stylekitWarningColor,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
53
packages/mobile/src/Components/ToastWrapper.tsx
Normal file
53
packages/mobile/src/Components/ToastWrapper.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useToastStyles } from '@Components/ToastWrapper.styled'
|
||||
import { useProgressBar } from '@Root/Hooks/useProgessBar'
|
||||
import React, { FC, useContext } from 'react'
|
||||
import { Animated, StyleSheet, View } from 'react-native'
|
||||
import Toast, { ErrorToast, InfoToast, SuccessToast, ToastConfig } from 'react-native-toast-message'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
export const ToastWrapper: FC = () => {
|
||||
const theme = useContext(ThemeContext)
|
||||
const styles = useToastStyles(theme)
|
||||
|
||||
const { updateProgressBar, progressBarWidth } = useProgressBar()
|
||||
|
||||
const toastStyles: ToastConfig = {
|
||||
info: props => {
|
||||
const percentComplete = props.props?.percentComplete || 0
|
||||
updateProgressBar(percentComplete)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<InfoToast {...props} style={styles(props.props).info} />
|
||||
{props.props?.percentComplete !== undefined ? (
|
||||
<View style={[styles(props.props).animatedViewContainer]}>
|
||||
<Animated.View
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
...styles(props.props).animatedView,
|
||||
width: progressBarWidth,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
},
|
||||
success: props => {
|
||||
const percentComplete = props.props?.percentComplete || 0
|
||||
updateProgressBar(percentComplete)
|
||||
|
||||
return <SuccessToast {...props} style={styles(props.props).success} />
|
||||
},
|
||||
error: props => {
|
||||
const percentComplete = props.props?.percentComplete || 0
|
||||
updateProgressBar(percentComplete)
|
||||
|
||||
return <ErrorToast {...props} style={styles(props.props).error} />
|
||||
},
|
||||
}
|
||||
|
||||
return <Toast config={toastStyles} />
|
||||
}
|
||||
3
packages/mobile/src/Components/package.json
Normal file
3
packages/mobile/src/Components/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@Components"
|
||||
}
|
||||
Reference in New Issue
Block a user