-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(native-app): Problem and No data UX related component (#14828)
* Added general problem component and translation hook wrapper * Update problem component * Update problem components logic and add problem to document-details screen * Update apps/native/app/src/ui/lib/problem/problem.tsx rename to error Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Rename to withContainer --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
- Loading branch information
1 parent
dffa964
commit 9a95a96
Showing
6 changed files
with
297 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useIntl } from 'react-intl' | ||
import { TranslatedMessage } from '../messages' | ||
|
||
/** | ||
* Helper hook to simplify translations in the app. | ||
*/ | ||
export const useTranslate = () => { | ||
const intl = useIntl() | ||
|
||
return (key: TranslatedMessage) => | ||
intl.formatMessage({ | ||
id: key, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
apps/native/app/src/ui/lib/problem/problem-template.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { Colors, Typography } from '@ui' | ||
import { ReactNode } from 'react' | ||
import { Image, View } from 'react-native' | ||
import styled from 'styled-components/native' | ||
|
||
type Variant = 'info' | 'error' | 'warning' | ||
|
||
export type ProblemTemplateBaseProps = { | ||
variant: Variant | ||
title: string | ||
message: string | ReactNode | ||
withContainer?: boolean | ||
} | ||
|
||
interface WithIconProps extends ProblemTemplateBaseProps { | ||
showIcon?: boolean | ||
tag?: never | ||
} | ||
|
||
interface WithTagProps extends ProblemTemplateBaseProps { | ||
tag?: string | ||
showIcon?: never | ||
} | ||
|
||
export type ProblemTemplateProps = WithIconProps | WithTagProps | ||
|
||
const getIcon = (variant: Variant) => { | ||
switch (variant) { | ||
case 'warning': | ||
return require('../../assets/icons/warning.png') | ||
|
||
case 'info': | ||
return require('../../assets/icons/info.png') | ||
} | ||
} | ||
|
||
const getColorsByVariant = ( | ||
variant: Variant, | ||
): { | ||
borderColor: Colors | ||
tagBackgroundColor: Colors | ||
tagColor: Colors | ||
} => { | ||
switch (variant) { | ||
case 'error': | ||
return { | ||
borderColor: 'red200', | ||
tagBackgroundColor: 'red100', | ||
tagColor: 'red600', | ||
} | ||
|
||
case 'info': | ||
return { | ||
borderColor: 'blue200', | ||
tagBackgroundColor: 'blue100', | ||
tagColor: 'blue400', | ||
} | ||
|
||
case 'warning': | ||
return { | ||
borderColor: 'yellow400', | ||
tagBackgroundColor: 'yellow300', | ||
tagColor: 'dark400', | ||
} | ||
} | ||
} | ||
|
||
const Host = styled.View<{ | ||
borderColor: Colors | ||
noContainer?: boolean | ||
}>` | ||
border-color: ${({ borderColor, theme }) => theme.color[borderColor]}; | ||
border-width: 1px; | ||
border-radius: 24px; | ||
justify-content: center; | ||
align-items: center; | ||
flex: 1; | ||
row-gap: ${({ theme }) => theme.spacing[3]}px; | ||
padding: ${({ theme }) => theme.spacing[2]}px; | ||
${({ noContainer, theme }) => noContainer && `margin: ${theme.spacing[2]}px;`} | ||
` | ||
|
||
const Tag = styled(Typography)<{ | ||
backgroundColor: Colors | ||
color?: Colors | ||
}>` | ||
background-color: ${({ backgroundColor, theme }) => | ||
theme.color[backgroundColor]}; | ||
padding: ${({ theme }) => theme.spacing[1]}px; | ||
border-radius: ${({ theme }) => theme.border.radius.large}; | ||
overflow: hidden; | ||
${({ color, theme }) => color && `color: ${theme.color[color]};`} | ||
` | ||
|
||
const Icon = styled(Image)(({ theme }) => ({ | ||
width: theme.spacing[3], | ||
height: theme.spacing[3], | ||
})) | ||
|
||
const Content = styled(View)` | ||
align-items: center; | ||
row-gap: ${({ theme }) => theme.spacing[1]}px; | ||
` | ||
|
||
export const ProblemTemplate = ({ | ||
variant, | ||
title, | ||
message, | ||
showIcon, | ||
tag, | ||
withContainer, | ||
}: ProblemTemplateProps) => { | ||
const { borderColor, tagColor, tagBackgroundColor } = | ||
getColorsByVariant(variant) | ||
|
||
return ( | ||
<Host borderColor={borderColor} noContainer={withContainer}> | ||
{tag && ( | ||
<Tag | ||
variant="eyebrow" | ||
backgroundColor={tagBackgroundColor} | ||
color={tagColor} | ||
> | ||
{tag} | ||
</Tag> | ||
)} | ||
{showIcon && <Icon source={getIcon(variant)} />} | ||
<Content> | ||
<Typography variant="heading3" textAlign="center"> | ||
{title} | ||
</Typography> | ||
<Typography textAlign="center">{message}</Typography> | ||
</Content> | ||
</Host> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { useEffect } from 'react' | ||
import { useTranslate } from '../../../hooks/use-translate' | ||
import { useOfflineStore } from '../../../stores/offline-store' | ||
import { ProblemTemplate, ProblemTemplateBaseProps } from './problem-template' | ||
|
||
enum ProblemTypes { | ||
error = 'error', | ||
noData = 'no_data', | ||
} | ||
|
||
type ProblemBaseProps = { | ||
/** | ||
* Type of problem | ||
* @default 'error' | ||
* 'error' is a generic error that is not caused by the user | ||
* 'no_data' is a 200 response, i.e. no data | ||
*/ | ||
type?: `${ProblemTypes}` | ||
error?: Error | ||
title?: string | ||
message?: string | ||
logError?: boolean | ||
} & Pick<ProblemTemplateBaseProps, 'withContainer'> | ||
|
||
interface ErrorProps extends ProblemBaseProps { | ||
type?: 'error' | ||
showIcon?: never | ||
error?: Error | ||
title?: string | ||
message?: string | ||
tag?: string | ||
} | ||
|
||
interface NoDataBaseProps extends ProblemBaseProps { | ||
type: 'no_data' | ||
error?: never | ||
title?: string | ||
message?: string | ||
} | ||
|
||
interface NoDataWithIconProps extends NoDataBaseProps { | ||
showIcon?: boolean | ||
tag?: never | ||
} | ||
|
||
interface NoDataWithTagProps extends NoDataBaseProps { | ||
showIcon?: never | ||
tag?: string | ||
} | ||
|
||
type NoDataProps = NoDataWithIconProps | NoDataWithTagProps | ||
|
||
type ProblemProps = ErrorProps | NoDataProps | ||
|
||
export const Problem = ({ | ||
type = ProblemTypes.error, | ||
error, | ||
title, | ||
message, | ||
tag, | ||
logError = false, | ||
withContainer, | ||
showIcon, | ||
}: ProblemProps) => { | ||
const t = useTranslate() | ||
const { isConnected } = useOfflineStore() | ||
|
||
const defaultProps = { withContainer } | ||
|
||
const fallbackProps = { | ||
...defaultProps, | ||
title: title ?? t('problem.error.title'), | ||
message: message ?? t('problem.error.message'), | ||
tag: tag ?? t('problem.error.tag'), | ||
variant: 'error', | ||
} as const | ||
|
||
useEffect(() => { | ||
if (logError && error) { | ||
console.error(error) | ||
} | ||
}, [logError, error]) | ||
|
||
// When offline prioritize showing offline template | ||
if (!isConnected) { | ||
return ( | ||
<ProblemTemplate | ||
{...defaultProps} | ||
showIcon | ||
variant="warning" | ||
title={title ?? t('problem.offline.title')} | ||
message={message ?? t('problem.offline.message')} | ||
/> | ||
) | ||
} | ||
|
||
const noDataProps = | ||
showIcon || !tag ? { showIcon: !tag ? true : showIcon } : { tag } | ||
|
||
switch (type) { | ||
case ProblemTypes.error: | ||
return <ProblemTemplate {...fallbackProps} /> | ||
|
||
case ProblemTypes.noData: | ||
return ( | ||
<ProblemTemplate | ||
{...defaultProps} | ||
{...noDataProps} | ||
variant="info" | ||
title={title ?? t('problem.noData.title')} | ||
message={message ?? t('problem.noData.message')} | ||
/> | ||
) | ||
|
||
default: | ||
return <ProblemTemplate {...fallbackProps} /> | ||
} | ||
} |