Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,709 changes: 162 additions & 4,547 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/app/Navigation/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '../../screens/AuthV2'
import { CreateFolder } from '../../screens/CreateFolder'
import { CreateRecord } from '../../screens/CreateRecord'
import { CreatePasswordItemV2 } from '../../screens/CreateRecord/v2/CreatePasswordItemV2'
import { ErrorScreen } from '../../screens/ErrorScreen'
import { ImagePreview } from '../../screens/ImagePreview'
import { ImagePreviewV2 } from '../../screens/ImagePreview/ImagePreviewV2'
Expand Down Expand Up @@ -103,6 +104,7 @@ export const Navigation = ({ initialRouteName }) => (
name="ImagePreview"
component={isV2() ? ImagePreviewV2 : ImagePreview}
/>
<Stack.Screen name="CreatePasswordItem" component={CreatePasswordItemV2} />
<Stack.Screen name="CreateRecord" component={CreateRecord} />
<Stack.Screen name="CreateFolder" component={CreateFolder} />
<Stack.Screen name="MasterPassword" component={MasterPassword} />
Expand Down
229 changes: 229 additions & 0 deletions src/components/AttachmentFieldsV2/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'

import { useLingui } from '@lingui/react/macro'
import {
AttachmentField,
Button,
MultiSlotInput,
useTheme
} from '@tetherto/pearpass-lib-ui-kit'
import {
Add,
TrashOutlined,
UploadFileFilled
} from '@tetherto/pearpass-lib-ui-kit/icons'

import { BottomSheetAttachmentActionsContentV2 } from '../../containers/BottomSheetAttachmentActionsContentV2/BottomSheetAttachmentActionsContentV2'
import { BottomSheetUploadFileContentV2 } from '../../containers/BottomSheetUploadFileContent/BottomSheetUploadFileContentV2'

type AttachmentLike = {
id?: string | number
name?: string
}

const ATTACHMENT_FIELD_ITEM_TYPES = {
empty: 'empty',
placeholder: 'placeholder',
attachment: 'attachment'
} as const

type AttachmentFieldItemType =
(typeof ATTACHMENT_FIELD_ITEM_TYPES)[keyof typeof ATTACHMENT_FIELD_ITEM_TYPES]

type AttachmentFieldItem<T extends AttachmentLike> =
| {
key: string
type: Extract<AttachmentFieldItemType, 'empty'>
}
| {
key: string
type: Extract<AttachmentFieldItemType, 'placeholder'>
placeholderId: number
}
| {
key: string
type: Extract<AttachmentFieldItemType, 'attachment'>
attachment: T
index: number
}

type Props<T extends AttachmentLike> = {
attachments: T[]
isEditing: boolean
onAdd: (file?: T | null) => void
onReplace: (index: number, file?: T | null) => void
onDelete: (index: number) => void
testID?: string
}

export const AttachmentFieldsV2 = <T extends AttachmentLike>({
attachments,
isEditing,
onAdd,
onReplace,
onDelete,
testID = 'attachments-multi-slot-input'
}: Props<T>) => {
const { t } = useLingui()
const { theme } = useTheme()
const placeholderCounterRef = useRef(0)
const [placeholderIds, setPlaceholderIds] = useState<number[]>([])
const [activeUploadTarget, setActiveUploadTarget] =
useState<AttachmentFieldItem<T> | null>(null)

const addAttachmentPlaceholder = useCallback(() => {
const nextId = placeholderCounterRef.current
placeholderCounterRef.current += 1

setPlaceholderIds((prev) => [...prev, nextId])
}, [])

const removeAttachmentPlaceholder = useCallback((placeholderId: number) => {
setPlaceholderIds((prev) => prev.filter((id) => id !== placeholderId))
}, [])

const attachmentFieldItems = useMemo<AttachmentFieldItem<T>[]>(
() => [
...(!attachments.length && !placeholderIds.length
? [{ key: 'attachment-field-empty', type: ATTACHMENT_FIELD_ITEM_TYPES.empty }]
: []),
...placeholderIds.map((placeholderId) => ({
key: `attachment-field-empty-${placeholderId}`,
type: ATTACHMENT_FIELD_ITEM_TYPES.placeholder,
placeholderId
})),
...attachments.map((attachment, index) => ({
key:
String(attachment?.id ?? '') ||
attachment.name ||
`attachment-field-${index}`,
type: ATTACHMENT_FIELD_ITEM_TYPES.attachment,
attachment,
index
}))
],
[attachments, placeholderIds]
)

const closeUploadSheet = useCallback(() => {
setActiveUploadTarget(null)
}, [])

const openUploadSheet = useCallback((item: AttachmentFieldItem<T>) => {
setActiveUploadTarget(item)
}, [])

const handleUploadSelect = useCallback(
(file?: T | null) => {
if (!activeUploadTarget) {
return
}

if (activeUploadTarget.type === ATTACHMENT_FIELD_ITEM_TYPES.attachment) {
onReplace(activeUploadTarget.index, file)
return
}

onAdd(file)

if (activeUploadTarget.type === ATTACHMENT_FIELD_ITEM_TYPES.placeholder) {
removeAttachmentPlaceholder(activeUploadTarget.placeholderId)
}
},
[activeUploadTarget, onAdd, onReplace, removeAttachmentPlaceholder]
)

const renderAttachmentRightSlot = (item: AttachmentFieldItem<T>) => {
if (item.type !== ATTACHMENT_FIELD_ITEM_TYPES.attachment) {
return (
<Button
size="small"
variant="tertiary"
aria-label="Upload attachment"
iconBefore={
<UploadFileFilled color={theme.colors.colorTextPrimary} />
}
onClick={() => openUploadSheet(item)}
/>
)
}

if (!isEditing) {
return (
<Button
size="small"
variant="tertiary"
aria-label="Delete attachment"
iconBefore={<TrashOutlined color={theme.colors.colorTextPrimary} />}
onClick={() => onDelete(item.index)}
/>
)
}

return (
<BottomSheetAttachmentActionsContentV2
filename={item.attachment?.name ?? ''}
onDelete={() => onDelete(item.index)}
onEdit={() => openUploadSheet(item)}
/>
)
}

const renderAttachmentField = (item: AttachmentFieldItem<T>) => (
<AttachmentField
key={item.key}
label={t`Attachment`}
value={
item.type === ATTACHMENT_FIELD_ITEM_TYPES.attachment
? item.attachment?.name ?? ''
: undefined
}
placeholder={
item.type === ATTACHMENT_FIELD_ITEM_TYPES.attachment
? undefined
: t`Add File / Photos Here`
}
isGrouped
testID={
item.type === ATTACHMENT_FIELD_ITEM_TYPES.attachment
? `attachment-field-${item.index}`
: item.key
}
onClick={
item.type !== ATTACHMENT_FIELD_ITEM_TYPES.attachment || isEditing
? () => openUploadSheet(item)
: undefined
}
rightSlot={renderAttachmentRightSlot(item)}
/>
)

return (
<>
<MultiSlotInput
actions={
<Button
variant="tertiary"
iconBefore={<Add />}
onClick={addAttachmentPlaceholder}
>
{t`Add Another Attachment`}
</Button>
}
testID={testID}
>
{attachmentFieldItems.map(renderAttachmentField)}
</MultiSlotInput>

<BottomSheetUploadFileContentV2
open={activeUploadTarget !== null}
onOpenChange={(open) => {
if (!open) {
closeUploadSheet()
}
}}
onFileSelect={(file) => handleUploadSelect(file as T | null)}
/>
</>
)
}
14 changes: 14 additions & 0 deletions src/components/DateField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ComponentProps } from 'react'

import { InputField } from '@tetherto/pearpass-lib-ui-kit'

export type DateFieldPickerMode = 'date' | 'time' | 'datetime' | 'month-year'

export type DateFieldProps = ComponentProps<typeof InputField> & {
pickerMode?: DateFieldPickerMode
}

export const DateField = ({
pickerMode: _pickerMode = 'date',
...props
}: DateFieldProps) => <InputField {...props} />
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import { Children, cloneElement, useCallback, useState } from 'react'

import { Children, cloneElement, isValidElement, useCallback, useState } from 'react'
import { ReactNode } from 'react'
import {
ArrowDownIcon,
ArrowUpIcon
} from '@tetherto/pearpass-lib-ui-react-native-components'
import { View } from 'react-native'

import { Title, TitleWrapper, Wrapper } from './styles'

/**
* @param {{
* children: ReactNode
* title?: string
* isCollapse: boolean
* onToggle?: (isOpen: boolean) => void
* isOpened?: boolean
*
* }} props
*/
interface FormGroupProps {
children: ReactNode
title?: string
isCollapse?: boolean // optional
onToggle?: (isOpen: boolean) => void
isOpened?: boolean
}

type FormGroupChildProps = {
index?: number
isFirst?: boolean
isLast?: boolean
onFocus?: () => void
focusedIndex?: number | null
}

export const FormGroup = ({
title,
isCollapse,
isCollapse = false,
children,
onToggle,
isOpened = true
}) => {
}: FormGroupProps) => {
const [isOpen, setIsOpen] = useState(isOpened)
const [focusedIndex, setFocusedIndex] = useState(null)
const [focusedIndex, setFocusedIndex] = useState<number | null>(null)

const handleFocusIndexSet = useCallback((index) => {
const handleFocusIndexSet = useCallback((index: number) => {
setFocusedIndex(index)
}, [])

Expand All @@ -48,7 +53,9 @@ export const FormGroup = ({
{isOpen && (
<View>
{Children.toArray(children)
.filter((child) => child !== null)
.filter((child): child is React.ReactElement<FormGroupChildProps> =>
isValidElement<FormGroupChildProps>(child)
)
.map((child, index, filteredArray) =>
cloneElement(child, {
index,
Expand Down
Loading
Loading