Skip to content

Commit

Permalink
Feature/re 2490 elu case (#863)
Browse files Browse the repository at this point in the history
* feat(components): checkboxes

* feat(profil): decla elu

* feat(profil): elu payments history

* feat(profil): elu declaration forms
  • Loading branch information
OverGlass committed Sep 20, 2024
1 parent 2e07559 commit a47e89c
Show file tree
Hide file tree
Showing 24 changed files with 1,373 additions and 588 deletions.
7 changes: 7 additions & 0 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createStyledContext, Spinner, styled, View, withStaticProperties } from
export const ButtonContext = createStyledContext({
pop: false,
disabled: false,
loading: false,
'data-testid': 'Button',
})

Expand Down Expand Up @@ -45,6 +46,12 @@ export const ButtonFrameStyled = styled(View, {
pop: {
true: {},
},
loading: {
true: {
pointerEvents: 'none',
cursor: 'wait',
},
},
size: {
sm: {
height: 32,
Expand Down
52 changes: 49 additions & 3 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react'
import { TouchableWithoutFeedback } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { SafeAreaView, TouchableWithoutFeedback } from 'react-native'
import EuCampaignIllustration from '@/assets/illustrations/EuCampaignIllustration'
import ProfilePopover from '@/components/ProfilePopover/ProfilePopover'
import { ROUTES } from '@/config/routes'
import { useSession } from '@/ctx/SessionProvider'
import { NativeStackHeaderProps } from '@react-navigation/native-stack'
import type { IconProps } from '@tamagui/helpers-icon'
import { ArrowLeft, ChevronDown } from '@tamagui/lucide-icons'
import { Link, router, usePathname, useSegments } from 'expo-router'
import { capitalize } from 'lodash'
import { Button, isWeb, Spinner, Stack, styled, useMedia, View, XStack, YStackProps } from 'tamagui'
import { Button, isWeb, Spinner, Stack, styled, ThemeableStack, useMedia, View, withStaticProperties, XStack, YStackProps } from 'tamagui'
import Text from '../base/Text'
import { SignInButton, SignUpButton } from '../Buttons/AuthButton'
import Container from '../layouts/Container'
Expand Down Expand Up @@ -184,3 +184,49 @@ export const SmallHeader: typeof Header = (props) => {
}

export default Header

const VoxHeaderFrameStyled = styled(ThemeableStack, {
backgroundColor: '$white',
gap: 4,
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: '$textOutline',
$md: {
height: 56,
paddingHorizontal: 26,
paddingVertical: 6,
},
})

const VoxHeaderFrame = (props: React.ComponentProps<typeof VoxHeaderFrameStyled>) => {
return (
<SafeAreaView>
<VoxHeaderFrameStyled {...props} />
</SafeAreaView>
)
}

const VoxHeaderLeftButtonFrame = styled(ThemeableStack, {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
$md: {
minWidth: 36,
height: 36,
},
})

const VoxHeaderLeftButton = (
props: React.ComponentProps<typeof VoxHeaderLeftButtonFrame> & { icon: React.NamedExoticComponent<IconProps>; backTitle?: string },
) => (
<VoxHeaderLeftButtonFrame {...props}>
<props.icon size={24} color="$textPrimary" />
{!!props.backTitle && <Text.LG semibold>{props.backTitle}</Text.LG>}
</VoxHeaderLeftButtonFrame>
)

export const VoxHeader = withStaticProperties(VoxHeaderFrame, {
LeftButtonFrame: VoxHeaderLeftButtonFrame,
LeftButton: VoxHeaderLeftButton,
})
27 changes: 22 additions & 5 deletions src/components/ModalOrPageBase/ModalOrPageBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,34 @@ interface ModalOrPageBaseProps extends PropsWithChildren {
onClose?: () => void
open?: boolean
shouldDisplayCloseHeader?: boolean
header: React.ReactNode
}

/**
* This component create a centered modal in md and more viewport, or a page in small ones
* @constructor
*/
export default function ModalOrPageBase({ children, onClose, open, shouldDisplayCloseHeader }: ModalOrPageBaseProps) {
export default function ModalOrPageBase({ children, onClose, open, shouldDisplayCloseHeader, header }: ModalOrPageBaseProps) {
const viewport = useMedia()
const insets = useSafeAreaInsets()

if (viewport.gtMd) {
return (
<Modal animationType={'fade'} transparent visible={!!open}>
<View style={styles.centeredView}>
<View style={styles.modalView}>{children}</View>
<View
style={styles.centeredView}
onPress={(e) => {
onClose?.()
}}
>
<View
onPress={(e) => {
e.stopPropagation()
}}
style={styles.modalView}
>
{children}
</View>
</View>
</Modal>
)
Expand All @@ -36,7 +49,7 @@ export default function ModalOrPageBase({ children, onClose, open, shouldDisplay
open={!!open}
snapPoints={[100]}
snapPointsMode="percent"
dismissOnSnapToBottom
disableDrag
moveOnKeyboardChange
onOpenChange={(x) => {
if (!x) {
Expand All @@ -51,12 +64,14 @@ export default function ModalOrPageBase({ children, onClose, open, shouldDisplay
</TouchableOpacity>
)}

{header}

<Sheet.ScrollView
keyboardShouldPersistTaps={'handled'}
automaticallyAdjustKeyboardInsets
backgroundColor={'white'}
contentContainerStyle={{
paddingBottom: 250,
// paddingBottom: 250,
flexGrow: 1,
}}
>
Expand All @@ -77,12 +92,14 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
cursor: 'pointer',
},
modalView: {
backgroundColor: 'white',
borderRadius: 20,
margin: Spacing.largeMargin,
alignItems: 'center',
cursor: 'auto',
shadowColor: '#000',
shadowOffset: {
width: 0,
Expand Down
4 changes: 2 additions & 2 deletions src/components/VoxCard/VoxCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const VoxCardFrame = ({ children, ...props }: VoxCardFrameProps) => {
}

export const VoxCardContent = styled(YStack, {
padding: '$4.5',
gap: '$3.5',
padding: 16,
gap: 16,
} as const)

const VoxCardChip = (props: ComponentProps<typeof Chip>) => {
Expand Down
16 changes: 16 additions & 0 deletions src/components/base/Checkbox/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { YStack } from 'tamagui'
import Checkbox from './Checkbox'

export default {
title: 'Checkbox',
component: Checkbox,
}

export const Default = () => (
<YStack>
<Checkbox />
<Checkbox checked />
<Checkbox disabled />
<Checkbox checked disabled />
</YStack>
)
99 changes: 99 additions & 0 deletions src/components/base/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from 'react'
import { createStyledContext, GetThemeValueForKey, getVariableValue, styled } from '@tamagui/core'
import { Check } from '@tamagui/lucide-icons'
import { ThemeableStack } from '@tamagui/stacks'

export const CheckboxContext = createStyledContext({
checked: false,
disabled: false,
})

export const CheckboxGroupZone = styled(ThemeableStack, {
tag: 'button',
context: CheckboxContext,
focusable: true,
height: 34,
width: 34,
borderRadius: 1000,
group: true,
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
animation: 'bouncy',
'$group-hover': { backgroundColor: '$blue2' },
focusStyle: {
backgroundColor: '$blue2',
},
hoverStyle: {
backgroundColor: '$blue2',
},
disabledStyle: {
cursor: 'not-allowed',
opacity: 0.4,
backgroundColor: 'transparent',
},
variants: {
checked: {
true: {},
},
} as const,
})

export const CheckboxGroupItemFrame = styled(ThemeableStack, {
context: CheckboxContext,
animation: 'bouncy',
borderRadius: 2,
height: 18,
width: 18,
backgroundColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '$textSecondary',
padding: 0,

variants: {
checked: {
true: {
borderColor: '$gray7',
},
},
} as const,
})

export const CheckboxGroupIndicatorFrame = styled(ThemeableStack, {
context: CheckboxContext,
width: 18,
height: 18,
animation: 'bouncy',
borderColor: 'transparent',
borderRadius: 2,
justifyContent: 'center',
alignItems: 'center',
scale: 0,
variants: {
checked: {
true: {
backgroundColor: '$gray7',
// borderColor: '$gray7',
scale: 1,
},
},
} as const,
})

export default forwardRef<ComponentRef<typeof CheckboxGroupZone>, ComponentPropsWithoutRef<typeof CheckboxGroupZone> & { color?: 'blue' | 'gray' }>(function (
{ color = 'blue', ...props },
ref,
) {
const hintColor = color === 'gray' ? '$gray7' : '$blue9'
return (
<CheckboxGroupZone {...props} ref={ref}>
<CheckboxGroupItemFrame borderColor={props.checked ? hintColor : '$textSecondary'}>
<CheckboxGroupIndicatorFrame backgroundColor={props.checked ? hintColor : undefined}>
<Check size={14} color="white1" />
</CheckboxGroupIndicatorFrame>
</CheckboxGroupItemFrame>
</CheckboxGroupZone>
)
})
18 changes: 18 additions & 0 deletions src/components/base/CheckboxGroup/CheckboxGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import CheckboxGroup from './CheckboxGroup'

export default {
title: 'CheckboxGroup',
component: CheckboxGroup,
}

const list = [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
]

export const Default = () => {
const [value, setValue] = React.useState(['option1'])
return <CheckboxGroup options={list} value={value} onChange={setValue} />
}
54 changes: 54 additions & 0 deletions src/components/base/CheckboxGroup/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react'
import Checkbox from '@/components/base/Checkbox/Checkbox'
import Text from '@/components/base/Text'
import { styled } from '@tamagui/core'
import { ThemeableStack, XStack } from 'tamagui'

type CheckboxGroupProps = {
options: {
label: string
value: string
}[]
value: string[]
onChange: (value: string[]) => void
}

const CheckboxGroupFrame = styled(ThemeableStack, {
gap: '$3',
flexDirection: 'row',
flexWrap: 'wrap',
// justifyContent: 'space-between',s
})

export default function CheckboxGroup({ options, value, onChange }: CheckboxGroupProps) {
const handlePress = (item: string) => () => {
if (value.includes(item)) {
onChange(value.filter((v) => v !== item))
return
}
onChange([...value, item])
}

const isChecked = (item: string) => value.includes(item)
return (
<CheckboxGroupFrame>
{options.map((option) => (
<XStack
key={option.value}
flexGrow={1}
flexShrink={1}
flexBasis={'48%'}
$md={{ flexBasis: '100%' }}
gap="$2"
group
onPress={handlePress(option.value)}
alignItems="center"
cursor="pointer"
>
<Checkbox checked={isChecked(option.value)} />
<Text.MD multiline>{option.label}</Text.MD>
</XStack>
))}
</CheckboxGroupFrame>
)
}
Loading

0 comments on commit a47e89c

Please sign in to comment.