fitcard-ui-react-native v1.9.4
Instalação
Instale as dependências:
yarn add @react-native-community/datetimepicker react-native-gesture-handler react-native-text-input-mask react-native-svg
Instale a biblioteca:
yarn add fitcard-ui-react-native
(IOS) Adicione a biblioteca react-native-text-input-mask no Podfile:
pod 'RNInputMask', :path => '../node_modules/react-native-text-input-mask/ios/InputMask'
Utilização
Envolva todo o app com o ThemeProvider:
import { ThemeProvider } from 'fitcard-ui-react-native';
const theme = {
colors: {
primary: '#FF4F4F',
secondary: '#000',
error: '#FF4F4F',
primaryText: '#000',
secondaryText: '#98A9BB',
background: '#FFF',
foreground: '#FFF',
formsBackground: '#F0F3F8',
border: '#F0F3F8',
toast: {
danger: '#FF4F4F',
success: '#21BA45',
warning: '#FBBD08',
},
},
fonts: {
light: {
fontFamily: 'CircularStd-Book',
},
medium: {
fontFamily: 'CircularStd-Medium',
},
bold: {
fontFamily: 'CircularStd-Bold',
},
black: {
fontFamily: 'CircularStd-Black',
},
},
};
function Root() {
return (
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
);
}
Importando os componentes:
import { Button, Card } from 'fitcard-ui-react-native';
Componentes
- AnimatedScrollablePage
- Button
- BottomSheet
- BottomSheetContainer
- Card
- CustomNavBar
- DateTimePicker
- Disclaimer
- FormGroup
- IconButton
- ListItem
- ListMenu
- MaskedTextInput
- MenuListItem
- MenuListSubitem
- MenuSeparator
- MultiSelect
- NavBar
- NavBar BackAction
- NavBar Action
- OptionList
- Picker
- PagedList
- PageWrapper
- SearchablePicker
- ScrollablePage
- TextInput
- TextArea
- Tag
- TagButton
- TextButton
- Text
- Toast
Button
<Button
text="Button"
onPress={() => console.log('pressed!')}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
text | string | (obrigatório) | Texto a ser exibido dentro do botão. |
onPress | () => void | (obrigatório) | Callback chamado quando o botão é pressionado. |
loading | boolean | false | Indica se o botão está carregando. |
light | boolean | false | Usa o tema claro (light). |
disabled | boolean | false | Desabilita o botão. |
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
textStyle | StyleProp<TextStyle> | null | Estilo do texto do botão. |
TagButton
<TagButton
text="Sort"
icon="sort"
onPress={() => console.log('pressed!')}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
text | string | (obrigatório) | Texto a ser exibido no botão. |
onPress | () => void | (obrigatório) | Callback chamado quando o botão é pressionado. |
active | boolean | false | Indica se está ativo. Quando ativo, o botão ganha cor. |
activeColor | string | Cor padrão do tema | Cor customizada de quando o botão estiver ativo. |
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
textStyle | StyleProp<TextStyle> | null | Estilo do texto. |
icon | string | '' | Nome do ícone. |
iconColor | string | colors.primaryText | Cor do icone. |
disabled | boolean | false | Desabilita o botão. |
IconButton
<IconButton
icon="circle"
type="solid"
onPress={() => console.log('pressed!')}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
icon | string | (obrigatório) | Nome do ícone do botão. |
iconSize | number | 16 | Tamanho do ícone. |
color | string | primary color | Cor do ícone. |
type | 'solid' | 'regular' | 'regular' | O tipo do icone. |
bgColor | string | light primary color | BackgroundColor. |
noBackground | boolean | false | Remove o background do botão. |
onPress | () => void | undefined | Callback chamado quando o botão é pressionado. |
disabled | boolean | false | Desabilita o botão. |
TextButton
<TextButton onPress={() => console.log('pressed!')}>Button</TextButton/>
prop | tipo | padrão | descrição |
---|---|---|---|
color | string | primary color | Cor do texto do botão. |
onPress | () => void | undefined | Callback chamado quando o botão é pressionado. |
style | StyleProp<TextStyle> | null | Estilo do texto do botão. |
bold | boolean | false | Deixa o texto do botão em negrito. |
NavBar
<NavBar
title="Home"
leftComponent={<NavBar.BackAction onPress={() => {}} />}
rightComponent={<NavBar.Action icon="search" />}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
leftComponent | ReactNode | undefined | Componente esquerdo da barra de navegação. |
rightComponent | ReactNode | undefined | Componente direito da barra de navegação. |
flat | boolean | false | Remove a sombra (Android) ou a borda inferior (IOS). |
containerStyle | StyleProp<ViewStyle> | null | Estilo da <View /> que envolve o componente. |
rightContentStyle | StyleProp<ViewStyle> | null | Estilo da <View /> que envolve o componente direito. |
leftContentStyle | StyleProp<ViewStyle> | null | Estilo da <View /> que envolve o componente esquerdo. |
title | string | '' | O texto a ser exibido como título. |
CustomNavBar
<CustomNavBar
leftComponent={<NavBar.BackAction onPress={() => {}} />}
middleComponent={(
<TextInput
placeholder="Pesquise algo..."
size="small"
showClearButton
/>
)}
rightComponent={<TextButton>Button</TextButton/>}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
leftComponent | ReactNode | undefined | Componente esquerdo da barra de navegação. |
middleComponent | ReactNode | undefined | Componente do meio da barra de navegação. |
rightComponent | ReactNode | undefined | Componente direito da barra de navegação. |
flat | boolean | false | Remove a sombra (Android) ou a borda inferior (IOS). |
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente.. |
NavBarBackAction
<NavBar.BackAction onPress={() => console.log('pressed!')} />
prop | tipo | padrão | descrição |
---|---|---|---|
onPress | () => void | undefined | Callback chamado quando o botão é pressionado. |
size | number | 20 (android) 24 (ios) | Tamanho do ícone. |
color | string | primary color | Cor do ícone. |
NavBarAction
<NavBar.Action
icon="search"
color="red"
onPress={() => console.log('pressed!')}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
onPress | () => void | undefined | Callback chamado quando o botão é pressionado. |
icon | string | (obrigatório) | Nome do ícone |
color | string | #000 | Cor do ícone. |
size | number | 20 (android) 24 (ios) | Tamanho do ícone. |
type | 'solid' | 'regular' | 'regular' | Tipo do ícone. |
TextInput
<TextInput
placeholder="Placeholder"
onChangeText={text => console.log(text)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
disabled | boolean | false | Desabilita o campo. |
error | boolean | false | Muda a cor do campo, indicando erro. |
size | 'normal' | 'small' | 'normal' | Tamanho do campo. |
showClearButton | boolean | false | Mostra um pequeno botão para limpar o texto do campo. |
onClear | () => void | undefined | Callback que é chamado quando o campo é limpado. |
onChangeText | (text: string) => void | Prop padrão do React Native. |
DateTimePicker
<DateTimePicker
placeholder="Escolha uma data"
IOSHeaderTitle="Escolha uma data"
date={new Date()}
onChange={(event, value) => console.log(event, value)}
/>
*Herda as props do @react-native-community/datetimepicker.
prop | tipo | padrão | descrição |
---|---|---|---|
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
placeholder | string | '' | Texto a ser exibido quando o valor do Picker está vazio. |
defaultDate | string | '' | Define um valor inicial para a data mostrada no Picker. |
mode | 'time' | 'date' | 'datetime' | 'countdown' | 'date' | O tipo formato de data a ser mostrado. |
disabled | boolean | false | Desabilita o Picker. |
customFormat | string | 'dd MMM, yyyy' | Uma máscara xDate personalizada para formatar o valor de exibido no Picker. |
date | Date | (obrigatório) | A data selecionada a ser exibida no Picker. |
IOSHeaderTitle | string | '' | Texto a ser exibido no header no modal no IOS. |
error | boolean | false | Muda a cor do campo, indicando erro. |
Disclaimer
<Disclaimer
icon="atom"
title="Titulo titulo"
description="Mussum Ipsum, cacilds vidis litro abertis. Si num tem leite então bota uma pinga aí cumpadi! Delegadis gente finis, Vide electram sadipscing et per."
stateView="smooth"
selectColor="danger"
/>
prop | tipo | padrão | descrição |
---|---|---|---|
icon | string | '' | Nome do ícone renderizado do lado esquerdo. |
title | string | '' | Título opcional. |
description | string | '' | Descrição opcional. |
selectColor | 'default' | 'information' | 'warning' | 'danger'| 'success'| 'informationAlt'| 'warningAlt' | 'dangerAlt' | 'successAlt' | 'default' | Cor do componente. |
stateView | 'filled' | 'smooth' | 'outlined' | 'raised' | 'filled' | Define a visualização do componente. |
Picker
<Picker
onValueChange={item => console.log(item)}
selectedValue={'Opção 1'}
items={['Opção 1', 'Opção 2']}
valueExtractor={item => item}
labelExtractor={item => item}
keyExtractor={item => item}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
selectedValue | string | number | (obrigatório) | O item selecionado do array de itens fornecido. |
onValueChange | (itemValue: string | number, itemPosition: number) => void | (obrigatório) | Callback chamado quando o item selecionado é alterado. |
disabled | boolean | false | Desabilita o Picker. |
items | any[] | (obrigatório) | Array de itens disponível para escolha. |
loading | boolean | false | Indica se está carregando. |
keyExtractor | (item: any) => string | (obrigatório) | Indica qual propriedade no array de itens é usada para ser a key. |
labelExtractor | (item: any) => string | (obrigatório) | Indica qual propriedade no array de itens é usada para ser a label. |
valueExtractor | (item: any) => string | (obrigatório) | Indica qual propriedade no array de itens é usada para ser o valor. |
error | boolean | false | Muda a cor do campo, indicando erro. |
FormGroup
<FormGroup label="Usuário" error="Este campos é obrigatório!">
<TextInput placeholder="Seu nome de usuário" secureTextEntry />
</FormGroup>
prop | tipo | padrão | descrição |
---|---|---|---|
label | string | (obrigatório) | Texto a ser exibido como label. |
error | string | '' | Mensagem de erro. |
style | StyleProp<ComponentStyle> | null | Estilo do componente. |
required | boolean | false | Indica se o campo é obrigatório. |
Card
<Card title="Card title" icon="clone">
<Paragraph>
Mussum Ipsum, cacilds vidis litro abertis.
Si num tem leite então bota uma pinga aí cumpadi!
Delegadis gente finis, Vide electram sadipscing et per.
</Paragraph>
</Card>
prop | tipo | padrão | descrição |
---|---|---|---|
icon | 'string' | 'number' | undefined | Nome do icone do card. |
iconColor | string | primary color | Cor do icone do card. |
title | string | (obrigatório) | Texto a ser exibido como título no card. |
rightContent | ReactNode | undefined | O conteúdo posicionado à direta do header do card. |
onPress | () => void | undefined | Deixa o cartão inteiro clicável e é chamado quando é pressionado. |
mode | 'shadowed' | 'bordered' | 'bordered' | Modo do estilo do card. |
ListItem
<ListItem
title="List item title"
description="List item description"
rightComponent={(
<Tag
light
text="Pending"
bgColor="#FFB800"
containerStyle={{ alignSelf: 'flex-end' }}
/>
)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
title | string | undefined | Título do item, exibido em negrito. |
description | string | undefined | Descrição do item da lista, exibida em cinza e pequena. |
rightComponent | ReactNode | undefined | Conteúdo posicionado à direita da linha. |
leftComponent | ReactNode | undefined | Conteúdo posicionado à esquerda da linha. |
rightContentStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente direito. |
leftContentStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente esquerdo. |
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
onPress | () => void | undefined | Quando fornecido, torna a lista clicável e é chamado quando pressionado. |
MenuListItem
Normal:
<MenuListItem
title="Home"
icon="layer-group"
iconType="solid"
onPress={() => console.log('pressed!')}
/>
Collapsible:
<MenuListItem
title="Notifications"
icon="bell"
iconType="solid"
onPress={() => console.log('pressed!')}
>
{...}
</MenuListItem>
prop | tipo | padrão | descrição |
---|---|---|---|
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
textStyle | StyleProp<TextStyle> | null | Estilo do texto do item. |
iconColor | string | #8698AB | Cor do ícone. |
icon | string | undefined | O nome do ícone. |
title | string | (obrigatório) | O texto a ser exibido. |
onPress | () => void | undefined | Callback chamado quando o item é pressionado. |
active | boolean | false | Indica se está ativo (selecionado). |
iconType | 'regular' | 'solid' | 'regular' | O tipo do ícone. |
MenuListSubitem
<MenuListSubitem
title="Notifications sub 1"
onPress={() => console.log('pressed!')}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
title | string | (obrigatório) | O texto a ser exibido. |
onPress | () => void | undefined | Callback chamado quando o subitem é pressionado. |
MenuSeparator
<MenuSeparator>Separador</MenuSeparator>
prop | tipo | padrão | descrição |
---|---|---|---|
style | StyleProp<TextProps> | undefined | Estilo do texto. |
ListMenu
Componente que cria o menu inteiro utilizando a estrutura de um array.
<ListMenu
items={[
{
type: 'separator',
text: 'Separator',
},
{
type: 'menu-item',
text: 'Home',
icon: 'layer-group',
iconType: 'solid',
onPress: () => {},
},
{
type: 'menu-item',
text: 'Notifications',
icon: 'bell',
iconType: 'solid',
onPress: () => {},
subItems: [
{
type: 'menu-item',
text: 'Notifications sub 1',
onPress: () => {},
},
{
type: 'menu-item',
text: 'Notifications sub 2',
onPress: () => {},
},
],
},
{
type: 'menu-item',
text: 'Emails',
iconType: 'solid',
icon: 'layer-group',
onPress: () => {},
},
{
type: 'menu-item',
text: 'Settings',
iconType: 'solid',
icon: 'cog',
onPress: () => {},
},
{
type: 'separator',
text: 'Separator Two',
},
{
type: 'menu-item',
text: 'Star',
iconType: 'solid',
icon: 'star',
},
{
type: 'menu-item',
text: 'Likes',
iconType: 'solid',
icon: 'heart',
},
]}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
items | MenuItem[] | (obrigatório) | Os itens do menu. |
Tag
<Tag
text="Pending"
bgColor="#FFB800"
/>
prop | tipo | padrão | descrição |
---|---|---|---|
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
bgColor | string | #000 | BackgroundColor. |
textColor | string | #FFF | Cor do texto. É ignorado quando light for falso. |
iconColor | string | #FFF | Cor do ícone. É ignorado quando light for falso. |
text | string | '' | Texto a ser exibido. |
icon | string | '' | Nome do ícone. |
light | boolean | false | Muda o design da tag para o claro (light). |
Text
<H1>Heading 1</H1>
<H2>Heading 2</H2>
<H3>Heading 3</H3>
<Paragraph>Paragraph</Paragraph>
<Description>Description</Description>
prop | tipo | padrão | descrição |
---|---|---|---|
style | StyleProp<TextStyle> | null | Estilo do texto |
AnimatedScrollablePage
Renderiza um header com uma animação ao rolar a página para baixo.
<AnimatedScrollablePage title="Home">
{...}
</AnimatedScrollablePage>
prop | tipo | padrão | descrição |
---|---|---|---|
title | string | '' | Texto a ser exibido no header. |
containerStyle | StyleProp<ViewStyle> | null | Estilo da View que envolve o componente. |
OptionList
Renderiza vários <TagButtons />
como uma lista de uma única opção selecionável.
<OptionList
options={[
{ text: 'Cats', value: 1 },
{ text: 'Dogs', value: 2 },
{ text: 'Birds', value: 3 },
]}
keyExtractor={item => item.text}
selected={0}
onChange={index => console.log(index)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
options | Option[] | (obrigatório) | O array para renderizar as opções. |
onChange | (itemIndex: number) => void | (obrigatório) | Callback chamado quando o item selecionado é alterado. |
selected | number | undefined | Índice do item selecionado. |
optionActiveColor | string | colors.primary | Cor da option quando está ativa. |
optionContainerStyle | StyleProp<ViewStyle> | null | Estilo da view que envolve a option <TagButton /> . |
optionTextStyle | StyleProp<TextStyle> | null | Estilo do texto da option <TagButton /> . |
keyExtractor | (item: Option) => string | (obrigatório) | Função para popular a 'key' do componente no loop. |
PageWrapper
Componente padrão para envolver todas as páginas.
<PageWrapper>
{...}
</PageWrapper>
prop | tipo | padrão | descrição |
---|---|---|---|
style | StyleProp<ViewStyle> | null | Estilo da <View /> |
safeArea | boolean | false | Renderiza ou não um SafeAreaView |
ScrollablePage
É usado como componente padrão em todas as páginas com scroll, pois respeita todas as métricas de margins e paddings.
<ScrollablePage>
{...}
</ScrollablePage>
prop | tipo | padrão | descrição |
---|---|---|---|
keyboardShouldPersistTaps | 'never' | 'always' | 'handled' | 'handled' | A prop keyboardShouldPersistTaps normal da View com o 'handled' definido como padrão. |
PagedList
Componente que utiliza a <FlatList />
do React Native e trata a lógica de paginação, implementando o scroll infinito.
<PagedList
title="Title"
page={1}
totalPages={2}
data={[]}
renderItem={renderItem}
keyExtractor={item => String(item.id)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
data | any[] | (obrigatório) | Dados da lista. |
onEndReached | () => void | undefined | Callback chamado quando o fim da <ScrollView /> é atingido. |
page | number | (obrigatório) | Página atual. |
totalPages | number | (obrigatório) | Número total de páginas dos dados. |
useHeader | boolean | false | Renderiza um <AnimatedHeader /> , aparecendo quando rolado. |
title | string | '' | Se useHeader for verdadeiro, usa como título do <AnimatedHeader /> |
BottomSheet
<BottomSheetContainer>
{...page}
<BottomSheet visible={false} height={300}>
{...}
</BottomSheet>
</BottomSheetContainer>
prop | tipo | padrão | descrição |
---|---|---|---|
visible | boolean | false | Indica se o componente está visivel ou não. |
showBackdrop | boolean | true | Indica se o backdrop será visível ou não. |
height | number | (obrigatório) | Altura do card do bottom sheet. |
style | StyleProp<ViewStyle> | null | Estilo do componente. |
backdropStyle | StyleProp<ViewStyle> | null | Estilo do backdrop. |
onBackdropPress | (event: GestureResponderEvent) => boolean | null | É chamado quando é pressionado o backdrop do componente. |
BottomSheetContainer
Componente que permite o backdrop do <BottomSheet />
ser ajustado adequadamente na tela.
<BottomSheetContainer>
{...page}
</BottomSheetContainer>
*Herda as props de uma <View />
do React Native.
TextArea
<TextArea placeholder="Insert a text..." />
prop | tipo | padrão | descrição |
---|---|---|---|
disabled | boolean | false | Desabilita o campo. |
error | boolean | false | Muda a cor do campo, indicando erro. |
*Herda as props de um <TextInput />
do React Native.
MaskedTextInput
<MaskedTextInput
placeholder="Placeholder"
onChangeText={(formatted, extracted) => {
console.log(formatted) // +1 (123) 456-78-90
console.log(extracted) // 1234567890
}}
mask="+1 ([000]) [000] [00] [00]"
/>
prop | tipo | padrão | descrição |
---|---|---|---|
disabled | boolean | false | Desabilita o campo. |
error | boolean | false | Muda a cor do campo, indicando erro. |
size | 'normal' | 'small' | 'normal' | Tamanho do campo. |
mask | string | '' | Máscara utilizada no texto do campo. |
onChangeText | (formatted: string, extracted: string) => void | Callback executado quando o texto é mudado. |
*Herda as props do react-native-text-input-mask.
MultiSelect
<MultiSelect
modalTitle="Choose a item"
placeholder="Choose a item"
searchPlaceholder="Search fruits"
maxSelectedItems={3}
onChange={setSelectedItems}
items={[{ text: 'Apple', value: 1 }, { text: 'Banana', value: 2 }]}
selected={{ 1: 'Apple' }}
keyExtractor={item => String(item.value)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
items | Item[] | (obrigatório) | |
onClose | (selected: Record<string | number, string>) => void | (obrigatório) | Callback executado quando o picker é fechado (Retorna os itens selecionados). |
keyExtractor | (item: Item, index: number) => string | (obrigatório) | |
maxSelectableItems | number | undefined | Máximo de itens selecionáveis. |
placeholder | string | '' | |
modalTitle | string | (obrigatório) | |
searchPlaceholder | string | '' | Placeholder do campo de busca. |
disabled | boolean | false | |
valueKey | string | 'obrigatório' | Nome customizado para ser usado como propriedade do valor. |
textKey | string | 'obrigatório' | Nome customizado para ser usado como propriedade do texto. |
SearchablePicker
<SearchablePicker
modalTitle="Choose a item"
placeholder="Choose a item"
searchPlaceholder="Search fruits"
items={[{ text: 'Apple', value: 1 }, { text: 'Banana', value: 2 }]}
onChange={setSelectedSearchableItems}
selected={1}
keyExtractor={item => String(item.value)}
/>
prop | tipo | padrão | descrição |
---|---|---|---|
items | Item[] | (obrigatório) | |
selected | string | number | undefined | Item selecionado (propriedade "value"). |
onChange | (selected: SelectedItems) => void | (obrigatório) | Callback executado quando um item é escolhido. |
keyExtractor | (item: Item, index: number) => string | (obrigatório) | |
placeholder | string | '' | |
modalTitle | string | (obrigatório) | |
searchPlaceholder | string | '' | Placeholder do campo de busca. |
disabled | boolean | false | |
valueKey | string | 'value' | Nome customizado para ser usado como propriedade do valor. |
textKey | string | 'text' | Nome customizado para ser usado como propriedade do texto. |
loading | boolean | 'false' | Loading |
Toast
Envolva o provider na raiz do projeto:
<ToastProvider>
{...rest}
</ToastProvider>
Utilização:
const toast = useToast();
toast.show({ message: 'Oops! Ocorreu um erro.' });
prop | tipo | padrão | descrição |
---|---|---|---|
message | string | (obrigatório) | Mensagem mostrada no Toast. |
type | 'danger' | 'success' | 'warning' | 'danger' | |
visible | boolean | false | |
buttonText | string | (obrigatório) | Texto a ser mostrado no botão. |
duration | number | 1500 | Duração do toast. |
10 months ago
10 months ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago