Skip to content

Commit

Permalink
New component library (#1)
Browse files Browse the repository at this point in the history
Create a component library, so we can reuse components and maintain consistency across ObsIdentify and the Observation app.

Resolves: observation/app#7
  • Loading branch information
jonathan-waarneming-nl authored Jul 25, 2023
1 parent 24cd35f commit 7960f59
Show file tree
Hide file tree
Showing 118 changed files with 25,178 additions and 1 deletion.
62 changes: 62 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: '@react-native-community',
plugins: ['react', 'jsx-a11y', 'import', 'react-native', 'observation'],
settings: {
'import/resolver': {
node: {
paths: ['.'],
extensions: ['.js', '.jsx', '.android.js', '.ios.js', '.native.js', '.ts', '.tsx'],
},
},
},
rules: {
'observation/no-function-without-logging': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
semi: ['error', 'never'],
'react-native/no-unused-styles': 'error',
'react-native/no-inline-styles': 'off',
'no-console': 'warn',
curly: ['error', 'multi-line'],
'prefer-destructuring': ['error'],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', ['parent', 'sibling']],
pathGroups: [
{
pattern: 'react+(|-native)',
group: 'external',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react+(|-native)'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
},
env: {
jest: true,
browser: true,
},
overrides: [
{
files: ['*.ts', '*.tsx'],
rules: {
'no-undef': 'off',
},
},
{
files: ['*test.ts', '*test.tsx'],
rules: {
'observation/no-function-without-logging': 'off',
},
},
],
}
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: CI
on: [push]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Authenticate with FontAwesome Pro
run: echo "//npm.fontawesome.com/:_authToken=${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}" >> .npmrc && cat .npmrc
- name: Install modules
run: yarn
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
/lib/
coverage
yarn-error.log
.DS_Store
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@fortawesome:registry=https://npm.fontawesome.com/
26 changes: 26 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
// React Native 0.64.2 settings:
// bracketSpacing: false,
// jsxBracketSameLine: true,
// singleQuote: true,
// trailingComma: 'all',
// arrowParens: 'avoid',
printWidth: 120,
bracketSpacing: true,
bracketSameLine: false,
semi: false,
singleQuote: true,
trailingComma: 'all',

overrides: [
{
files: '*.xml',
options: {
printWidth: 80,
tabWidth: 4,
bracketSameLine: true,
xmlWhitespaceSensitivity: 'ignore',
},
},
],
}
25 changes: 25 additions & 0 deletions @types/accordion-collapse-react-native.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
declare module 'accordion-collapse-react-native' {
import { TouchableOpacityProps } from 'react-native'

type CollapseProps = {
isExpanded: boolean
disabled?: boolean
onToggle: (isExpanded: boolean) => void
handleLongPress?: () => void
touchableOpacityProps?: TouchableOpacityProps
children?: React.ReactNode
}
const Collapse = (_props: CollapseProps) => React.ReactNode

type CollapseHeaderProps = {
children?: React.ReactNode
}
const CollapseHeader = (_props: CollapseHeaderProps) => React.ReactNode

type CollapseBodyProps = {
children?: React.ReactNode
}
const CollapseBody = (_props: CollapseBodyProps) => React.ReactNode

export { Collapse, CollapseHeader, CollapseBody }
}
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
# react-native-components
React Native component library
React Native component library for components used by React Native applications of Observation.org.
There are 2 kinds of components:
UI components:
- `Accordion`
- `BackButton`
- `BackgroundImage`
- `BottomSheet`
- `Checkbox`
- `Chip`
- `ContentImage`
- `Date`
- `Disclose`
- `FilterButton`
- `IconButton`
- `IconName`
- `IconText`
- `IconView`
- `Icons`
- `InputField`
- `LargeButton (+ LargeButtonProps)`
- `Icon/Icons`
- `Lightbox`
- `Location`
- `Message`
- `MoreInfo`
- `Notification`
- `NotificationPopup (+ NotificationPopupStaticProps)`
- `PageIndicator`
- `Panel`
- `PhotoStrip`
- `Popup`
- `ProgressBar`
- `ProgressBarList`
- `RenderHtmlWrapper`
- `TextLink`
- `Tooltip`
- `TooltipProps`
- `WebLink`

non-UI components:
- `Log.setLogConfiguration`: A function to change the logging of the component library
- `openUrl`: Opens URLs
- `useShowBlurView`: A safe way to set a blur on the background
- `theme`: A default theme with color and margins
- `font`: A set of font styles
- `text`: A set of text styles
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
}
9 changes: 9 additions & 0 deletions fileTransformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const path = require('path')

module.exports = {
process(sourceText, sourcePath, options) {
return {
code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`,
}
},
}
20 changes: 20 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
preset: 'react-native',
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@observation.org/react-native-image-viewing|i18n-js|react-native-scalable-image)/)',
],
setupFiles: ['./jest.mocks.js'],
collectCoverageFrom: ['**/src/**/*.{js,ts,tsx}'],
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
diagnostics: {
exclude: ['**/node_modules/**']
},
}
],
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/fileTransformer.js',
},
}
30 changes: 30 additions & 0 deletions jest.mocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable observation/no-function-without-logging */
import React from 'react'
import { Image } from 'react-native'

// Mock font awesome
const Icon = 'Icon'
const getIconType = (prefix) => {
switch (prefix) {
case 'fas':
return 'solid'
case 'fal':
return 'light'
default:
throw new Error()
}
}
jest.mock('@fortawesome/react-native-fontawesome', () => ({
FontAwesomeIcon: (faIcon) => (
<Icon
testID={faIcon.testID}
color={faIcon.color}
name={faIcon.icon.iconName}
size={faIcon.size}
style={faIcon.style}
type={getIconType(faIcon.icon.prefix)}
/>
),
}))

Image.getSizeWithHeaders = jest.fn(() => Promise.resolve({ width: 0, height: 0 }))
64 changes: 64 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "@observation.org/react-native-components",
"version": "1.0.0",
"main": "src/index.ts",
"repository": "[email protected]:observation/react-native-components.git",
"author": "Observation.org",
"license": "MIT",
"devDependencies": {
"@react-native-community/eslint-config": "^3.2.0",
"@testing-library/react-native": "^12.1.2",
"@tsconfig/react-native": "^3.0.2",
"@types/color": "^3.0.3",
"@types/jest": "^29.5.2",
"@types/react": "^18.2.14",
"@types/react-native": "^0.72.2",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-observation": "https://github.com/observation/eslint-rules.git",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-native": "^4.0.0",
"jest": "^29.5.0",
"prettier": "^2.8.8",
"react": "^18.2.0",
"react-native": "^0.72.3",
"react-test-renderer": "^18.2.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
},
"react-native": "src/index.ts",
"scripts": {
"lint": "tsc --noEmit && eslint src",
"test": "jest --silent",
"verify": "$npm_execpath lint && $npm_execpath test"
},
"files": [
"src"
],
"peerDependencies": {
"react": ">=18.2.0",
"react-native": ">=0.72.3"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/pro-light-svg-icons": "^6.3.0",
"@fortawesome/pro-solid-svg-icons": "^6.3.0",
"@fortawesome/react-native-fontawesome": "^0.3.0",
"@observation.org/react-native-image-viewing": "https://github.com/observation/react-native-image-viewing",
"@react-native-community/blur": "^4.2.0",
"@react-navigation/native": "^6.0.8",
"accordion-collapse-react-native": "^1.0.0",
"color": "^4.2.3",
"i18n-iso-countries": "^7.2.0",
"i18n-js": "^4.0.2",
"moment": "2.29.4",
"react-native-localize": "^2.1.9",
"react-native-logs": "^5.0.1",
"react-native-render-html": "^6.3.4",
"react-native-scalable-image": "^1.1.0"
}
}
60 changes: 60 additions & 0 deletions src/components/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useState } from 'react'
import { View } from 'react-native'

// @ts-ignore
import { Collapse, CollapseHeader, CollapseBody } from 'accordion-collapse-react-native'

import Log from '../lib/Log'
import { unsafeLayoutAnimation } from '../lib/Utils'

type Item = {
title: string
}

type Props<T extends Item> = {
list: T[]
header: (item: T, index: number, isActive: boolean) => void
footer: (item: T, index: number, isActive: boolean) => void
body: (item: T) => JSX.Element
onOpen: (index: number) => void
ListEmptyComponent?: () => JSX.Element
}

const Accordion = <T extends Item>({ list, header, footer, body, onOpen, ListEmptyComponent }: Props<T>) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null)

const onToggle = (index: number, isExpanded: boolean) => {
Log.debug('Accordion:onToggle', index, isExpanded)
unsafeLayoutAnimation('Accordion:onToggle')
if (isExpanded) {
setActiveIndex(index)
onOpen(index)
} else if (activeIndex === index) {
setActiveIndex(null)
}
}

return (
<View>
{list.length === 0
? ListEmptyComponent?.()
: list.map((item, index: number) => (
<Collapse
key={item.title}
isExpanded={index === activeIndex}
onToggle={(isExpanded: boolean) => onToggle(index, isExpanded)}
>
<CollapseHeader>
<>
{header(item, index, index === activeIndex)}
{footer(item, index, index === activeIndex)}
</>
</CollapseHeader>
<CollapseBody>{body(item)}</CollapseBody>
</Collapse>
))}
</View>
)
}

export default Accordion
Loading

0 comments on commit 7960f59

Please sign in to comment.