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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@observation.org/react-native-components",
"version": "1.78.0",
"version": "1.79.0",
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
Expand Down
17 changes: 17 additions & 0 deletions src/components/CapitalizeText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { Text, TextProps } from 'react-native'

import { capitalize } from '../lib/Utils'

/**
* Renders the children, capitalizing the first child when this is a string
*/
const CapitalizeText = ({ children, ...props }: TextProps) => (
<Text {...props}>
{React.Children.map(children, (child, index) =>
index === 0 && typeof child === 'string' ? capitalize(child) : child,
)}
</Text>
)

export default CapitalizeText
91 changes: 91 additions & 0 deletions src/components/InputPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react'
import { StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native'

import CapitalizeText from './CapitalizeText'
import { Theme, useStyles, useTheme } from '../theme'
import { Icon } from './Icon'

type Props = {
label?: string
value?: string
onPress: () => void
containerStyle?: StyleProp<ViewStyle>
valueStyle?: StyleProp<TextStyle>
disabled?: boolean
capitalize?: boolean
showChevron?: boolean
}

const InputPanel = ({
label,
value,
containerStyle,
valueStyle,
onPress,
disabled = false,
capitalize = false,
showChevron = true,
}: Props) => {
const theme = useTheme()
const styles = useStyles(createStyles)

// TODO: 48 and 36 should be input heights and we should make them dynamically themed
const paddingVertical = label
? (48 - (styles.headerTextStyle.lineHeight! + styles.value.lineHeight!)) / 2
: (36 - styles.value.lineHeight!) / 2

return (
<TouchableOpacity disabled={disabled} style={[styles.container, containerStyle]} onPress={onPress}>
<View style={[styles.contentContainer, { paddingVertical }]}>
{label && (
<Text numberOfLines={1} ellipsizeMode="tail" style={styles.headerTextStyle}>
{label}
</Text>
)}
{capitalize && value ? (
<CapitalizeText numberOfLines={1} ellipsizeMode="tail" style={[styles.value, valueStyle]}>
{value}
</CapitalizeText>
) : (
<Text numberOfLines={1} ellipsizeMode="tail" style={[styles.value, valueStyle]}>
{value || ' '}
</Text>
)}
</View>
{showChevron && (
<View style={styles.icon}>
<Icon name="angle-down" color={theme.color.grey500} size={theme.icon.size.m} />
</View>
)}
</TouchableOpacity>
)
}

export default InputPanel

const createStyles = (theme: Theme) =>
StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.margin.common,
},
headerTextStyle: {
...theme.font.extraSmall,
lineHeight: theme.font.extraSmall.fontSize,
letterSpacing: 0.03 * theme.font.extraSmall.fontSize,
color: theme.color.text.system.subtler,
},
contentContainer: {
flex: 1,
flexDirection: 'column',
},
value: {
...theme.font.medium,
color: theme.color.text.system.strong,
},
icon: {
marginLeft: theme.margin.half,
},
})
31 changes: 31 additions & 0 deletions src/components/ItemSeparator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'

import { Theme, useStyles } from '../theme'

type Orientation = 'horizontal' | 'vertical'

type Props = {
style?: StyleProp<ViewStyle>
orientation?: Orientation
}

const ItemSeparator = ({ style, orientation = 'horizontal' }: Props) => {
const styles = useStyles(createStyles)
const separatorStyle = orientation === 'horizontal' ? styles.horizontalSeparator : styles.verticalSeparator
return <View style={[separatorStyle, style]} />
}

export default ItemSeparator

const createStyles = (theme: Theme) =>
StyleSheet.create({
horizontalSeparator: {
borderBottomWidth: 1,
borderBottomColor: theme.color.background.system.surfaceRaised,
},
verticalSeparator: {
borderRightWidth: 1,
borderRightColor: theme.color.background.system.surfaceRaised,
},
})
88 changes: 88 additions & 0 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react'
import { StyleSheet, TouchableOpacity, View } from 'react-native'

import SingleLine from './SingleLine'
import { Theme, useStyles, useTheme } from '../theme'
import { Icon, IconProps } from './Icon'

type Props = {
icon?: IconProps
label: string
subLabel?: string
extraSubLabel?: string
onPress?: () => void
selected?: boolean
}

const ListItem = ({ icon, onPress, label, subLabel, extraSubLabel, selected = false }: Props) => {
const theme = useTheme()
const styles = useStyles(createStyles)

const iconMarginRight = subLabel ? theme.margin.common : theme.margin.half
const containerPaddingVertical = subLabel ? 7 : 13

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deze hard-coded padding is niet zo mooi maar ik kon geen betere manier verzinnen.

const containerBackgroundColor = selected ? theme.color.primary50 : undefined

const renderIcon = (() => {
switch (true) {
case !!icon:
return (
<Icon
name={icon!.name}
style={icon!.style ?? 'solid'}
color={icon!.color ?? theme.color.grey500}
size={icon!.size ?? theme.icon.size.s}
/>
)
case selected:
return (
<Icon name={'circle-check'} style={'solid'} color={theme.color.icon.system.brand} size={theme.icon.size.xl} />
)
default:
return (
<Icon name={'circle'} style={'light'} color={theme.color.icon.system.disabled} size={theme.icon.size.xl} />
)
}
})()

return (
<TouchableOpacity activeOpacity={0.5} onPress={onPress} disabled={!onPress}>
<View
style={[
styles.containerStyle,
{ paddingVertical: containerPaddingVertical, backgroundColor: containerBackgroundColor },
]}
>
<View style={{ marginRight: iconMarginRight }}>{renderIcon}</View>
<View style={{ flex: 1 }}>
<SingleLine style={styles.labelTextStyle}>{label}</SingleLine>
{subLabel && (
<View style={{ flexDirection: 'row', gap: theme.margin.half }}>
<SingleLine style={styles.subLabelTextStyle}>{subLabel}</SingleLine>
{extraSubLabel && <SingleLine style={styles.subLabelTextStyle}>{extraSubLabel}</SingleLine>}
</View>
)}
</View>
</View>
</TouchableOpacity>
)
}

export default ListItem

const createStyles = (theme: Theme) =>
StyleSheet.create({
containerStyle: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.margin.common,
},
labelTextStyle: {
...theme.font.medium,
lineHeight: theme.lineHeight.small,
color: theme.color.text.system.strong,
},
subLabelTextStyle: {
...theme.font.extraSmall,
color: theme.color.text.system.subtler,
},
})
35 changes: 35 additions & 0 deletions src/components/SectionHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'

import { Theme, useStyles } from '../theme'

type Props = {
title?: string
containerStyle?: StyleProp<ViewStyle>
}

const SectionHeader = ({ title, containerStyle }: Props) => {
const styles = useStyles(createStyles)
return (
<View style={[styles.header, title ? styles.headerPadding : null, containerStyle]}>
{title && <Text style={styles.contentHeaderStyle}>{title}</Text>}
</View>
)
}

export default SectionHeader

const createStyles = (theme: Theme) =>
StyleSheet.create({
header: {
paddingHorizontal: theme.margin.common,
marginTop: theme.margin.common,
},
headerPadding: {
paddingVertical: theme.margin.quarter,
},
contentHeaderStyle: {
...theme.font.smallBold,
color: theme.color.text.system.subtler,
},
})
15 changes: 15 additions & 0 deletions src/components/SingleLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import { StyleProp, Text, TextStyle } from 'react-native'

type Props = {
children?: React.ReactNode
style?: StyleProp<TextStyle>
}

const SingleLine = ({ children, style }: Props) => (
<Text numberOfLines={1} ellipsizeMode="tail" style={style}>
{children}
</Text>
)

export default SingleLine
32 changes: 32 additions & 0 deletions src/components/__tests__/CapitalizeText.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import { Text } from 'react-native'

import { describe, expect, test } from '@jest/globals'
import { render } from '@testing-library/react-native'

import CapitalizeText from '../CapitalizeText'

describe('CapitalizeText', () => {
test('Capitalizes the text', () => {
const { queryByText } = render(<CapitalizeText>hello world</CapitalizeText>)
expect(queryByText('Hello world')).toBeTruthy()
})

test('With multiple children, the first one being a string, the text is capitalized', () => {
const { queryByText } = render(
<CapitalizeText>
hello <Text>world</Text>
</CapitalizeText>,
)
expect(queryByText('Hello world')).toBeTruthy()
})

test('When the first child is not a string, no text is capitalized', () => {
const { queryByText } = render(
<CapitalizeText>
<Text>hello</Text> world
</CapitalizeText>,
)
expect(queryByText('hello world')).toBeTruthy()
})
})
Loading