Compare commits
139 Commits
#102-scrol
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2bc0abbed | ||
|
|
61e07d775b | ||
|
|
5d9e2eb6bc | ||
|
|
df452e67ad | ||
|
|
bd96d8db84 | ||
|
|
2de224c686 | ||
|
|
85bb638d74 | ||
|
|
a7b25d3b17 | ||
|
|
1551661328 | ||
|
|
0acadd3eab | ||
|
|
1c83837e21 | ||
|
|
94e7d003dd | ||
|
|
b9e164f399 | ||
|
|
64e947eb9a | ||
|
|
1c2d6b285f | ||
|
|
d9c5cc3129 | ||
|
|
61440c0369 | ||
|
|
21a5ebbd29 | ||
|
|
87456480be | ||
|
|
80018ab8df | ||
|
|
08601c8974 | ||
|
|
fd195d61b7 | ||
|
|
1c82cea9dd | ||
|
|
09e00b9ae6 | ||
|
|
ab951cb34c | ||
|
|
6ab02ddbf3 | ||
|
|
eff27b52e2 | ||
|
|
362b91d002 | ||
|
|
5d6d3d6008 | ||
|
|
44dd334771 | ||
|
|
3e130e0ce0 | ||
|
|
9506ca2616 | ||
|
|
1cfefe675b | ||
|
|
8d2d0a4459 | ||
|
|
d741491e11 | ||
|
|
f414caaab0 | ||
|
|
a26bd5bc5a | ||
|
|
abfba3ff6c | ||
|
|
8b025737a0 | ||
|
|
4deaa99c97 | ||
|
|
3daad835b1 | ||
|
|
8c7a6a9274 | ||
|
|
a752672f09 | ||
|
|
c778745910 | ||
|
|
9e75a5ff24 | ||
|
|
5c488149d0 | ||
|
|
d8437bc42f | ||
|
|
a59e2a6da8 | ||
|
|
4e84d89320 | ||
|
|
169bda6e09 | ||
|
|
7e81700ea6 | ||
|
|
626b64f328 | ||
|
|
4cc593cb55 | ||
|
|
fd7e4c57dd | ||
|
|
3cb56d1be7 | ||
|
|
5745a5df3e | ||
|
|
9f0b4fb788 | ||
|
|
244258d185 | ||
|
|
ac7b51d382 | ||
|
|
93d033d437 | ||
|
|
1eaf30eda7 | ||
|
|
33a92005a3 | ||
|
|
2e6e86c11b | ||
|
|
23e5f95ba9 | ||
|
|
edde570912 | ||
|
|
17c016cfcf | ||
|
|
5dd298b2a5 | ||
|
|
26f4e30d39 | ||
|
|
78560e41a7 | ||
|
|
0941373a98 | ||
|
|
020a587ac5 | ||
|
|
8cdbcb208d | ||
|
|
7bc8ec30a5 | ||
|
|
365c2c7d11 | ||
|
|
d4196b186c | ||
|
|
ef8ac83f3b | ||
|
|
95f2b99fbb | ||
|
|
3bc004c8f4 | ||
|
|
92c34f30e0 | ||
|
|
7f59e78f87 | ||
|
|
235d1a8efd | ||
|
|
c2875ea452 | ||
|
|
dd00177c16 | ||
|
|
158bcceeb7 | ||
|
|
aa250675b1 | ||
|
|
4e94fc31b5 | ||
|
|
d7cbe9eab8 | ||
|
|
d5f39e71e2 | ||
|
|
1643e99c92 | ||
|
|
0dd8befcc1 | ||
|
|
e973344d7f | ||
|
|
d5ddd6d27b | ||
|
|
f8686ad3ef | ||
|
|
c55cdba695 | ||
|
|
57b69fb8aa | ||
|
|
89b9409d05 | ||
|
|
d9fd875f17 | ||
|
|
23cdd7248d | ||
|
|
700cdac419 | ||
|
|
0247a0ab6f | ||
|
|
af9268e14d | ||
|
|
32582cc9ba | ||
|
|
5d79cd3f89 | ||
|
|
08c5958fc4 | ||
|
|
15578efb73 | ||
|
|
2bba9893cc | ||
|
|
7c2d1776c8 | ||
|
|
4132ca4d9a | ||
|
|
756ea48a23 | ||
|
|
b585a6a070 | ||
|
|
47594b66fb | ||
|
|
f4d77f44ce | ||
|
|
7bbe85c416 | ||
|
|
0f3d0b800b | ||
|
|
620a5ef91f | ||
|
|
d7e30e6eb9 | ||
|
|
99a5bc39f5 | ||
|
|
2e9d88c446 | ||
|
|
0cbf384b02 | ||
|
|
a06b1a4390 | ||
|
|
5251867f25 | ||
|
|
44db4f7882 | ||
|
|
30adeccf83 | ||
|
|
debf4ff8b9 | ||
|
|
afe974f9e3 | ||
|
|
577c7d4aa5 | ||
|
|
ba77e5742a | ||
|
|
1b605f0cba | ||
|
|
0297e38391 | ||
|
|
a4c91834e5 | ||
|
|
3300eff474 | ||
|
|
1b7b1f512a | ||
|
|
b70609c279 | ||
|
|
87f55cb4a0 | ||
|
|
b827ca6db0 | ||
|
|
cdb421265a | ||
|
|
3e4c65ec77 | ||
|
|
e0035433ca | ||
|
|
9f67f84c49 |
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
/examples/**
|
||||
**/metro.config.js
|
||||
.eslintrc.js
|
||||
/jest/**
|
||||
49
.eslintrc.js
@ -1,48 +1,65 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
extends: ['airbnb', 'prettier'],
|
||||
extends: ['prettier', 'airbnb'],
|
||||
plugins: ['react', 'react-native', 'jsx-a11y', 'import', 'prettier'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['app']
|
||||
}
|
||||
}
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
moduleDirectory: ['node_modules', 'app'],
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {
|
||||
jest: true
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js'] }],
|
||||
'react/prop-types': ['error', { ignore: ['navigation', 't', 'i18n'] }],
|
||||
'react/jsx-closing-bracket-location': 0,
|
||||
'react/jsx-no-bind': 0,
|
||||
'react/destructuring-assignment': 'warn',
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/require-default-props': 0,
|
||||
'react/prefer-stateless-function': 0,
|
||||
'react/jsx-props-no-spreading': 0,
|
||||
'react-native/no-unused-styles': 2,
|
||||
'react-native/split-platform-components': 2,
|
||||
'react-native/no-inline-styles': 2,
|
||||
'react-native/no-color-literals': 2,
|
||||
'react-native/no-color-literals': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'import/no-cycle': [1, { maxDepth: 1 }],
|
||||
'import/no-unresolved': ['error', { ignore: ['react', 'react-native'] }],
|
||||
'import/no-extraneous-dependencies': ['error', { packageDir: './' }],
|
||||
'no-unused-vars': [2, { argsIgnorePattern: '^_' }],
|
||||
'max-len': [2, 100],
|
||||
'no-underscore-dangle': 0,
|
||||
'max-len': [1, 100],
|
||||
'operator-assignment': 'warn',
|
||||
'global-require': 0,
|
||||
'prettier/prettier': ['error'],
|
||||
'implicit-arrow-linebreak': 0,
|
||||
'prettier/prettier': [
|
||||
'warn',
|
||||
{},
|
||||
{
|
||||
usePrettierrc: true,
|
||||
},
|
||||
],
|
||||
'no-multiple-empty-lines': [
|
||||
'error',
|
||||
{
|
||||
max: 1,
|
||||
maxEOF: 0,
|
||||
maxBOF: 0
|
||||
}
|
||||
maxBOF: 0,
|
||||
},
|
||||
],
|
||||
semi: ['error', 'never'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'operator-linebreak': 0,
|
||||
'comma-dangle': 0,
|
||||
'no-useless-constructor': 0,
|
||||
'object-curly-newline': 0,
|
||||
'prefer-promise-reject-errors': 0,
|
||||
'newline-before-return': 'error',
|
||||
'import/no-extraneous-dependencies': ['error', { packageDir: './' }]
|
||||
indent: 0,
|
||||
},
|
||||
globals: {
|
||||
fetch: false,
|
||||
@ -51,6 +68,6 @@ module.exports = {
|
||||
render: true,
|
||||
navigator: true,
|
||||
__DEV__: true,
|
||||
requestAnimationFrame: true
|
||||
}
|
||||
}
|
||||
requestAnimationFrame: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
readme_*
|
||||
example
|
||||
examples
|
||||
node_modules
|
||||
yarn-error.log
|
||||
.history
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
"jsxBracketSameLine": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
|
||||
161
README.md
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
# Introduction
|
||||
# Introduction
|
||||
|
||||
<p align="center">
|
||||
Stickyheader.js is a simple React Native library, enabling to create a fully custom header for your iOS and Android apps.
|
||||
@ -34,12 +34,15 @@
|
||||
<h2> Features </h2>
|
||||
Stickyheader.js ships with 3 different use cases for sticky headers and a possibility to create fully custom header!
|
||||
|
||||
| Tabbed Header | Avatar Header | Details Header|
|
||||
| Tabbed Header | Avatar Header | Details Header|
|
||||
| :------: | :------: | :------: |
|
||||
|  || |
|
||||
|
||||
|
||||
## In Use
|
||||
|
||||
**Check the live demo on Expo Snack [here](https://snack.expo.io/@maciejbudzinsking/sticky-parallax-header-by-netguru).**
|
||||
|
||||
Predefined headers can be accessed through `headerType="HeaderName"` property, each header can be configured according to your demands using the wide amount of properties. You can change all of them, or use it right out of the box with as little changes as possible to use it for your needs
|
||||
|
||||
This is how you can add them in your app:
|
||||
@ -62,34 +65,58 @@ export default TestScreen
|
||||
|
||||
Below are examples of those components and description of the props they are accepting.
|
||||
|
||||
## Shared props
|
||||
|
||||
### Tabbed Header, Details Header, Avatar Header
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `contentContainerStyles` | `View.propTypes.style` | Yes | | Set style for content container |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `scrollRef` | `func or object` | Yes | `null` | ScrollView body ref. Allows programmatically scroll body [ScrollView](https://reactnative.dev/docs/scrollview#methods) |
|
||||
| `keyboardShouldPersistTaps` | `'always', 'never', 'handled', false, true` | Yes | `undefined` | Determines when the keyboard should stay visible after a tap.|
|
||||
| `refreshControl` | `RefreshControl` | Yes | `undefined` | Props used to add pull to refresh functionality.|
|
||||
|
||||
### Details Header, Avatar Header
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `hasBorderRadius` | `boolean` | No | `true` | Adds radius to header's right bottom border |
|
||||
| `image` | `ImageSourcePropType` | No | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `leftTopIcon` | `ImageSourcePropType` | No | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `leftTopIconOnPress` | `() => void` | No | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIcon` | `ImageSourcePropType` | No | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `rightTopIconOnPress` | `() => void` | No | `() => {}` | Define action on right top button press |
|
||||
|
||||
## Tabbed Header
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :---------------------------: | :---------------------------------------------------: | :-------:| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------:|
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `title` | `string` | Yes | `"Mornin' Mark! \nReady for a quiz?"` | Sets header title |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `tabs` | `arrayOf(shape({}))` | Yes | `[{title: 'Popular',content: <RenderContent title="Popular Quizes" />},...]` | Array with tabs names and content |
|
||||
| `foregroundImage` |`oneOfType([object, number])` | Yes | | Set image in the foreground |
|
||||
| `header` |`func` | Yes | | Function that renders custom header |
|
||||
| `logo` | `func` | Yes | `require('../../assets/images/logo.png')` | Set header logo |
|
||||
| `logoStyle` | `style` | Yes | `{ height: 24, width: 142 }` | Set header logo style |
|
||||
| `logoContainerStyle` | `style` | Yes | `{ width: '100%', paddingHorizontal: 24, paddingTop: Platform.select({ ios: ifIphoneX(50, 40), android: 55 }), flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'}`| Set header logo container style |
|
||||
| `logoResizeMode` | `"contain", "cover", "stretch", "center", "repeat" ` | Yes | `"contain"` | Set header logo resize mode |
|
||||
| `foregroundImage` |`oneOfType([object, number])` | Yes | | Set image in the foreground |
|
||||
| `titleStyle` |`Text.propTypes.style` | Yes | | Set style for text in foreground |
|
||||
| `tabText` |`Text.propTypes.style` | Yes |`{fontSize: 16, lineHeight: 20, paddingHorizontal: 12, paddingVertical: 8, color: colors.white}` | Set inactive tab style |
|
||||
| `onRef` |`func` | Yes | | Reference callback. You can call goToPage(pageNumber) method through ref to programmatically navigate to given tab |
|
||||
| `rememberTabScrollPosition` |`bool` | Yes |`false` | When switching between tabs remember current scroll position |
|
||||
| `scrollEvent` |`func` | Yes | | Scroll event to apply custom animations |
|
||||
| `tabs` |`{ content: ReactElement;title?: string;icon?: ReactElement` | `(isActive: boolean) => ReactElement);` | Yes | `[{title: 'Popular',content: <RenderContent title="Popular Quizes" />},...]` | Array with tabs names, icons and content |
|
||||
| `tabTextStyle` |`Text.propTypes.style` | Yes |`{fontSize: 16, lineHeight: 20, paddingHorizontal: 12, paddingVertical: 8, color: colors.white}` | Set inactive tab style |
|
||||
| `tabTextActiveStyle` |`Text.propTypes.style` | Yes |`{fontSize: 16, lineHeight: 20, paddingHorizontal: 12, paddingVertical: 8, color: colors.white}` | Set active tab stylee |
|
||||
| `tabTextContainerStyle` |`ViewPropTypes.style` | Yes |`{backgroundColor: colors.transparent, borderRadius: 18}` | Set inactive tab container style |
|
||||
| `tabTextContainerActiveStyle` |`ViewPropTypes.style` | Yes |`{backgroundColor: colors.darkMint}` | Set active tab container style |
|
||||
| `tabWrapperStyle` |`ViewPropTypes.style` | Yes |`{paddingVertical: 12}` | Set single tab container style |
|
||||
| `tabsContainerStyle` |`ViewPropTypes.style` | Yes | | Set whole tab bar container style |
|
||||
| `header` |`func` | Yes | | Fuction that renders custom header |
|
||||
| `scrollEvent` |`func` | Yes | | Scroll event to apply custom animations |
|
||||
| `title` | `string` | Yes | `"Mornin' Mark! \nReady for a quiz?"` | Sets header title |
|
||||
| `titleStyle` |`Text.propTypes.style` | Yes | | Set style for text in foreground |
|
||||
|
||||
[Check how to customise Tabbed Header example](docs/TABBEDHEADER.MD)
|
||||
|
||||
@ -97,55 +124,79 @@ Below are examples of those components and description of the props they are acc
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :--------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `leftTopIconOnPress` | `func` | Yes | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIconOnPress` | `func` | Yes | `() => {}` | Define action on right top button press |
|
||||
| `leftTopIcon` | `number` | Yes | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `rightTopIcon` | `number` | Yes | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `tag` | `string` | Yes | `"Product Designer"` | Sets header tag name |
|
||||
| `title` | `string` | Yes | `"Design System"` | Sets header title |
|
||||
| `image` | `number` | Yes | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `hasBorderRadius` | `bool` | Yes | `true` | Adds radius to header's left bottom border |
|
||||
| `iconNumber` | `number` | Yes | `10` | Set amount of cards shown on icon |
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :-------------------: | :--------------------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `iconNumber` | `number` | No | `10` | Set amount of cards shown on icon |
|
||||
| `tag` | `string` | No | `"Product Designer"` | Sets header tag name |
|
||||
| `title` | `string` | No | `"Design System"` | Sets header title |
|
||||
|
||||
|
||||
## Avatar Header
|
||||
|
||||

|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :--------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `leftTopIconOnPress` | `func` | Yes | `() => {}` | Define action on left top button press |
|
||||
| `rightTopIconOnPress` | `func` | Yes | `() => {}` | Define action on right top button press |
|
||||
| `leftTopIcon` | `number` | Yes | `require('../../assets/icons/iconCloseWhite.png')` | Set icon for left top button |
|
||||
| `rightTopIcon` | `number` | Yes | `require('../../assets/icons/Icon-Menu.png') ` | Set icon for right top button |
|
||||
| `backgroundColor` | `string` | Yes | `#1ca75d` | Header background color |
|
||||
| `headerHeight` | `number` | Yes | `ifIphoneX(92, constants.responsiveHeight(13))` | Sets height of folded header |
|
||||
| `backgroundImage` | `number` | Yes | `null` | Sets header background image |
|
||||
| `title` | `string` | Yes | `"Brandon` | Sets header title |
|
||||
| `subtitle` | `string` | Yes | `"Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer."`| Sets description(subtitle) section |
|
||||
| `image` | `number` | Yes | `require('../../assets/images/photosPortraitBrandon.png')` | Sets header image |
|
||||
| `renderBody` | `func` | Yes | `title => <RenderContent title={title} />` | Function that renders body of the header (can be empty) |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `snapToEdge` | `bool` | Yes | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `hasBorderRadius` | `bool` | Yes | `true` | Adds radius to header's left bottom border |
|
||||
| `parallaxHeight` | `number` | Yes | | Set parallax header height |
|
||||
| `transparentHeader` | `bool` | Yes | `false` | Set header transparency to render custom header |
|
||||
| `snapStartThreshold` | `number` | Yes | | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | Yes | | Set stop value Threshold of snap |
|
||||
| `snapValue` | `number` | Yes | | Set value where header is closed |
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| :-------------------: | :---------------------------------------------------------:| :-------:| :---------------------------------------------------------------------------:| :-------------------------------------------------------:|
|
||||
| `foreground` | `() => ReactElement` | No | - | Function that renders the foreground of the header |
|
||||
| `header` | `() => ReactElement` | No | - | Function that renders custom header |
|
||||
| `parallaxHeight` | `number` | No | - | Set parallax header height |
|
||||
| `scrollEvent` | `(event: NativeSyntheticEvent<NativeScrollEvent>) => void` | No | `require('../../assets/icons/Icon-Menu.png') ` | Scroll event to apply custom animations |
|
||||
| `snapStartThreshold` | `number` | No | - | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | No | - | Set stop value Threshold of snap |
|
||||
| `snapValue` | `number` | No | - | Set value where header is closed |
|
||||
| `subtitle` | `string` | No | `"Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer."`| Sets description(subtitle) section |
|
||||
| `title` | `string` | No | `"Brandon` | Sets header title |
|
||||
| `transparentHeader` | `boolean` | No | `false` | Set header transparency to render custom header |
|
||||
|
||||
## Custom Header
|
||||
## Custom Header
|
||||
|
||||
[Custom header props and example](docs/CUSTOM.md)
|
||||
|
||||
## Handling StickyParallaxHeader body ScrollView reference
|
||||
### As callback function
|
||||
```
|
||||
<StickyParallaxHeader
|
||||
scrollRef={(ref) => {
|
||||
paralaxScrollRef.current = ref;
|
||||
}}
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
>
|
||||
{renderBody()}
|
||||
</StickyParallaxHeader>
|
||||
```
|
||||
|
||||
### As useRef value
|
||||
```
|
||||
const paralaxScrollRef = useRef(null);
|
||||
|
||||
<StickyParallaxHeader
|
||||
scrollRef={paralaxScrollRef}
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
>
|
||||
{renderBody()}
|
||||
</StickyParallaxHeader>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Handling nested scrollables
|
||||
|
||||
[Handling nested flatlist props and example](docs/CUSTOM.md#Tips)
|
||||
## Changing Statusbar style
|
||||
|
||||
[Changing StatusBar style](docs/CUSTOM.md#changing-statusbar-style)
|
||||
|
||||
## Pull to refresh
|
||||
|
||||
[Pull to refresh](docs/CUSTOM.md#Pull-to-Refresh)
|
||||
|
||||
## Rendering icons in tabs
|
||||
|
||||
[Icons in tabs](docs/CUSTOM.md#Icons-in-tabs)
|
||||
|
||||
|
||||
<h1 id="Getting-Started">Getting Started</h1>
|
||||
|
||||
## Prerequisites
|
||||
@ -204,4 +255,4 @@ $ yarn add react-native-sticky-parallax-header
|
||||
|
||||
|
||||
# License
|
||||
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
||||
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
};
|
||||
};
|
||||
|
||||
@ -21,6 +21,7 @@ $ yarn test
|
||||
* adding video or screenshot is very beneficial but it's not mandatory
|
||||
* additionally please remember to add appropriate Pull Request title from following:
|
||||
* `[RNS-XX] short description` - for normal feature branches
|
||||
* remember one pull request should always address one issue or feature
|
||||
|
||||
## Code structure
|
||||
```
|
||||
@ -36,3 +37,4 @@ src/
|
||||
* Name branch according to your ticket following this pattern: RNS-XX-short_description
|
||||
* Imports and exports inside `index.js` files eg. `screens/index.js`, `components/index.js` should be alfabetically
|
||||
* Style names in `ComponentName.styles.js` should be ordered alfabetically
|
||||
* Please use commit lint and follow commit naming convention (https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
|
||||
151
docs/CUSTOM.md
@ -2,30 +2,36 @@
|
||||
|
||||
## Custom Header Props
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :------------------------------: | :-----------------: | :--------: | :-------: | :-------------------------------------------------------------: |
|
||||
| `background` | `node` | No | - | This renders background component |
|
||||
| `backgroundImage` | `number` | No | - | This renders background image instead of background component |
|
||||
| `backgroundColor` | `string` | Yes |`""` | Header background color |
|
||||
| `bounces` | `bool` | Yes | `true` | Bounces on swiping up |
|
||||
| `children` | `node` | No | - | This renders all the children inside the component |
|
||||
| `foreground` | `node` | Yes | - | This renders foreground component |
|
||||
| `header` | `node` | Yes | - | This renders header component |
|
||||
| `headerHeight` | `number` | No | `92` | Sets height of folded header |
|
||||
| `headerSize` | `func` | No | - | Returns size of header for current device |
|
||||
| `initialPage` | `number` | No | `0` | Set initial page of tab bar |
|
||||
| `onChangeTab` | `func` | No | - | Tab change event |
|
||||
| `onEndReached` | `func` | No | - | Tab change event |
|
||||
| `parallaxHeight` | `number` | No | `0` | Sets height of opened header |
|
||||
| `snapToEdge` | `bool` | No | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `scrollEvent` | `func` | No | - | Returns offset of header to apply custom animations |
|
||||
| `tabs` | `arrayOf(string)` | No | - | Array of tab names |
|
||||
| `tabTextStyle` | `shape({})` | No | {} | Text styles of tab |
|
||||
| `tabTextActiveStyle` | `shape({})` | No | {} | Text styles of active tab |
|
||||
| `tabTextContainerStyle` | `shape({})` | No | {} | Container styles of tab |
|
||||
| `tabTextContainerActiveStyle` | `shape({})` | No | {} | Container styles of active tab |
|
||||
| `tabsContainerBackgroundColor` | `string` | No | - | Background color of tab bar container |
|
||||
| `tabsWrapperStyle` | `shape({})` | No | {} | Tabs Wrapper styles |
|
||||
| Property | Type | Required | Default | Description |
|
||||
| :------------------------------: | :-------------------------------------------------------------------: | :--------: | :-------: | :-------------------------------------------------------------: |
|
||||
| `backgroundColor` | `string` | Yes | `""` | Header background color |
|
||||
| `backgroundImage` | `ImageSourcePropType` | No | - | This renders background image instead of background component |
|
||||
| `background` | `ReactElement` | No | - | This renders background component |
|
||||
| `bounces` | `boolean` | Yes | `true` | Bounces on swiping up |
|
||||
| `children` | `ReactElement` | No | - | This renders all the children inside the component |
|
||||
| `foreground` | `ReactElement` | Yes | - | This renders foreground component |
|
||||
| `headerHeight` | `number` | No | `92` | Sets height of folded header |
|
||||
| `headerSize` | `({ x, y, width, height }: HeaderSizeProps) => void` | No | - | Handler that is called when header's size changes |
|
||||
| `header` | `ReactElement` | Yes | - | This renders header component |
|
||||
| `initialPage` | `number` | No | `0` | Set initial page of tab bar |
|
||||
| `onChangeTab` | `({ i, ref, from }: { from: number; i: number; ref: any; }) => void;` | No | - | Tab change event |
|
||||
| `onEndReached` | `() => void` | No | - | Reached end event
|
||||
| `onTopReached` | `() => void` | No | - | Reached top event |
|
||||
| `parallaxHeight` | `number` | No | `0` | Sets height of opened header |
|
||||
| `scrollEvent` | `(event: NativeSyntheticEvent<NativeScrollEvent>) => void` | No | - | Returns offset of header to apply custom animations |
|
||||
| `snapStartThreshold` | `number` | No | - | Set start value Threshold of snap |
|
||||
| `snapStopThreshold` | `number` | No | - | Set stop value Threshold of snap |
|
||||
| `snapToEdge` | `boolean` | No | `true` | Boolean to fire the function for snap To Edge |
|
||||
| `snapValue` | `number` | No | - | Set value where header is closed |
|
||||
| `tabTextActiveStyle` | `TextStyle` | No | {} | Text styles of active tab |
|
||||
| `tabTextContainerActiveStyle` | `ViewStyle` | No | {} | Container styles of active tab |
|
||||
| `tabTextContainerStyle` | `ViewStyle` | No | {} | Container styles of tab |
|
||||
| `tabTextStyle` | `TextStyle` | No | {} | Text styles of tab |
|
||||
| `tabsContainerBackgroundColor` | `ViewStyle` | No | - | Background color of tab bar container |
|
||||
| `tabsWrapperStyle` | `ViewStyle` | No | {} | Tabs Wrapper styles |
|
||||
| `tabs` | `{ content: ReactElement; title: string; }[]` | No | - | Array of tab names |
|
||||
| `tabsContainerStyle` | `ViewStyle` | No | - | Set whole tab bar container style |
|
||||
| `transparentHeader` | `boolean` | No | `false` | Set header transparency to render custom header |
|
||||
|
||||
<h1 id="Usage">Usage</h1>
|
||||
|
||||
@ -180,4 +186,99 @@ class TabScreen extends React.Component {
|
||||
```
|
||||
|
||||
## Tips
|
||||
In order to nest scrollable component use `scrollEnabled={false}` on it and move all the logic to the header eg. by using `onEndReached` prop.
|
||||
In order to nest scrollable component use `scrollEnabled={false}` on it and move all the logic to the header eg. by using `onEndReached` and `onTopReached` props. You can find example in CardScreen.js it's really basic so probably you will want to extend it somehow:
|
||||
|
||||
```jsx
|
||||
|
||||
shouldBeEnabled = () => {
|
||||
const {
|
||||
endReached,
|
||||
stickyHeaderEndReached,
|
||||
topReached,
|
||||
stickyHeaderTopReached
|
||||
} = this.state
|
||||
const bottomCondition =
|
||||
endReached && stickyHeaderEndReached
|
||||
const topCondition =
|
||||
topReached && stickyHeaderTopReached
|
||||
return bottomCondition || !topCondition
|
||||
}
|
||||
|
||||
onScroll = ({nativeEvent}) => {
|
||||
const {contentOffset, layoutMeasurement, contentSize} = nativeEvent;
|
||||
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) {
|
||||
this.setState({endReached: true, topReached: false})
|
||||
}
|
||||
|
||||
if (contentOffset.y <= 0) {
|
||||
this.setState({topReached: true, endReached: false, stickyHeaderTopReached:true})
|
||||
}
|
||||
}
|
||||
|
||||
renderFlatlistContent = (user) => (
|
||||
<View style={styles.flatlistContainer}>
|
||||
<FlatList
|
||||
data={user.cards}
|
||||
renderItem={({item, index}) => (
|
||||
<QuizCard
|
||||
data={item}
|
||||
num={index}
|
||||
key={item.question}
|
||||
cardsAmount={100}
|
||||
/>
|
||||
)}
|
||||
onScroll={this.onScroll}
|
||||
scrollEnabled={
|
||||
Platform.OS === 'android' ? true : this.shouldBeEnabled()
|
||||
}
|
||||
nestedScrollEnabled
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
```
|
||||
###Changing StatusBar style
|
||||
To override status bar, place **`<StatusBar>`** component below **`<StickyParallaxHeader>`**
|
||||
|
||||
Example:
|
||||
```jsx
|
||||
<>
|
||||
<StickyParallaxHeader headerType="AvatarHeader"/>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="red" />
|
||||
</>
|
||||
```
|
||||
|
||||
### Pull to Refresh
|
||||
```
|
||||
<StickyParallaxHeader
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
// z Index is required on IOS, to refresh indicator be visible
|
||||
style={{ zIndex: 1 }}
|
||||
refreshing={refreshing}
|
||||
titleColor="white"
|
||||
tintColor="white"
|
||||
title="Refreshing"
|
||||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
...
|
||||
>
|
||||
...
|
||||
```
|
||||
|
||||
### Icons in tabs
|
||||
You can pass just React component to `icon` property in tabs object. If you need different active icon use function, example below.
|
||||
```
|
||||
<StickyParallaxHeader
|
||||
tabs={[
|
||||
{
|
||||
title: 'Development',
|
||||
icon: (active) => (active ? <ActiveIcon /> : <Icon />),
|
||||
content: this.renderContent('Popular Quizes'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
27
docs/issue_template.md
Normal file
@ -0,0 +1,27 @@
|
||||
**Title**
|
||||
|
||||
* **I'm submitting a ...**
|
||||
- [ ] bug report
|
||||
- [ ] feature request
|
||||
- [ ] support request
|
||||
|
||||
* **If bug, used module version**
|
||||
|
||||
1.0.0
|
||||
|
||||
* **Platform**
|
||||
- [ ] ios
|
||||
- [ ] android
|
||||
|
||||
|
||||
* **What is the current behavior?**
|
||||
|
||||
|
||||
* **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem**
|
||||
|
||||
|
||||
* **If is a feature request, What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)
|
||||
@ -1,51 +0,0 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: 'airbnb',
|
||||
plugins: [
|
||||
'react',
|
||||
'react-native',
|
||||
'jsx-a11y',
|
||||
'import'
|
||||
],
|
||||
settings: {
|
||||
'import/resolver': 'reactnative'
|
||||
},
|
||||
env: {
|
||||
jest: true
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js'] }],
|
||||
"react/prop-types": ["error", { "ignore": ["navigation", "t", "i18n"] }],
|
||||
'react/jsx-no-bind': 0,
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/require-default-props': 0,
|
||||
'react-native/no-unused-styles': 2,
|
||||
'react-native/split-platform-components': 2,
|
||||
'react-native/no-inline-styles': 2,
|
||||
'react-native/no-color-literals': 2,
|
||||
'import/prefer-default-export': 0,
|
||||
'no-unused-vars': [2, { 'argsIgnorePattern': '^_' }],
|
||||
'max-len': [2, 100],
|
||||
'global-require': 0,
|
||||
'no-multiple-empty-lines': ['error', {
|
||||
max: 1,
|
||||
maxEOF: 0,
|
||||
maxBOF: 0,
|
||||
}],
|
||||
'semi': ['error', 'never'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'no-useless-constructor': 0,
|
||||
'object-curly-newline': 0,
|
||||
'newline-before-return': 'error'
|
||||
},
|
||||
globals: {
|
||||
fetch: false,
|
||||
shallow: true,
|
||||
mount: true,
|
||||
render: true,
|
||||
withNamespaces: true,
|
||||
navigator: true,
|
||||
__DEV__: true,
|
||||
requestAnimationFrame: true
|
||||
}
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Metro configuration for React Native
|
||||
* https://github.com/facebook/react-native
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
transformer: {
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
@ -1,116 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "iphone",
|
||||
"filename" : "iconApp-20@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "iphone",
|
||||
"filename" : "iconApp-20@3x.png",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "ipad",
|
||||
"filename" : "iconApp-20.png",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom": "ipad",
|
||||
"filename" : "iconApp-20@2x.png",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "iconApp-60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-29.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "iconApp-83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "iconApp-1024.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import QuizListElement from './QuizListElement/QuizListElement'
|
||||
import UserModal from './UserModal/UserModal'
|
||||
import QuizCard from './QuizCard/QuizCard'
|
||||
import QuizOption from './QuizOption/QuizOption'
|
||||
|
||||
export { QuizListElement, UserModal, QuizCard, QuizOption }
|
||||
@ -1,48 +0,0 @@
|
||||
import { Dimensions, Platform } from 'react-native'
|
||||
|
||||
const { height, width } = Dimensions.get('window')
|
||||
const responsiveHeight = h => height * (h / 100)
|
||||
const responsiveWidth = w => width * (w / 100)
|
||||
const breakpoints = {
|
||||
smallPhoneWidth: 320,
|
||||
smallPhoneHeight: 600,
|
||||
mediumPhoneWidth: 414,
|
||||
bigPhoneWidth: 480
|
||||
}
|
||||
const isSmallScreen = width <= breakpoints.smallPhoneWidth || height <= breakpoints.smallPhoneHeight
|
||||
const isNormalScreen = width > breakpoints.smallPhoneWidth && width < breakpoints.mediumPhoneWidth
|
||||
const isBigScreen = width >= breakpoints.mediumPhoneWidth
|
||||
const isBiggestPhoneScreen = width >= breakpoints.bigPhoneWidth
|
||||
const deviceWidth = width
|
||||
const deviceHeight = height
|
||||
const isAndroid = Platform.OS === 'android'
|
||||
|
||||
const normalizedFontSize = (basicFontSize) => {
|
||||
if (isSmallScreen) {
|
||||
return basicFontSize - 6
|
||||
}
|
||||
if (isNormalScreen) {
|
||||
return basicFontSize
|
||||
}
|
||||
if (isBigScreen) {
|
||||
return basicFontSize + 1
|
||||
}
|
||||
|
||||
return basicFontSize
|
||||
}
|
||||
|
||||
const scrollPosition = (scrollHeight, x) => x * 0.01 * scrollHeight
|
||||
|
||||
export default {
|
||||
deviceHeight,
|
||||
deviceWidth,
|
||||
responsiveHeight,
|
||||
responsiveWidth,
|
||||
isAndroid,
|
||||
isSmallScreen,
|
||||
isNormalScreen,
|
||||
isBigScreen,
|
||||
isBiggestPhoneScreen,
|
||||
normalizedFontSize,
|
||||
scrollPosition
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import colors from './colors'
|
||||
import constants from './constants'
|
||||
import sizes from './sizes'
|
||||
import screenStyles from './screenStyles'
|
||||
|
||||
export { constants, colors, sizes, screenStyles }
|
||||
@ -1,21 +0,0 @@
|
||||
import { Dimensions, Platform } from 'react-native'
|
||||
|
||||
export function isIphoneX() {
|
||||
const dimen = Dimensions.get('window')
|
||||
return (
|
||||
Platform.OS === 'ios' &&
|
||||
!Platform.isPad &&
|
||||
!Platform.isTVOS &&
|
||||
(dimen.height === 812 ||
|
||||
dimen.width === 812 ||
|
||||
dimen.height === 896 ||
|
||||
dimen.width === 896)
|
||||
)
|
||||
}
|
||||
|
||||
export function ifIphoneX(iphoneXStyle, regularStyle) {
|
||||
if (isIphoneX()) {
|
||||
return iphoneXStyle
|
||||
}
|
||||
return regularStyle
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
import HomeScreen from './HomeScreen/HomeScreen'
|
||||
import CardScreen from './CardScreen/CardScreen'
|
||||
|
||||
export { HomeScreen, CardScreen }
|
||||
3
examples/expo/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
metro.config.js
|
||||
|
||||
73
examples/expo/.eslintrc.js
Normal file
@ -0,0 +1,73 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
extends: ['prettier', 'airbnb'],
|
||||
plugins: ['react', 'react-native', 'jsx-a11y', 'import', 'prettier'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
moduleDirectory: ['node_modules', 'app'],
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js'] }],
|
||||
'react/prop-types': ['error', { ignore: ['navigation', 't', 'i18n'] }],
|
||||
'react/jsx-closing-bracket-location': 0,
|
||||
'react/jsx-no-bind': 0,
|
||||
'react/destructuring-assignment': 'warn',
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/require-default-props': 0,
|
||||
'react/prefer-stateless-function': 0,
|
||||
'react/jsx-props-no-spreading': 0,
|
||||
'react-native/no-unused-styles': 2,
|
||||
'react-native/split-platform-components': 2,
|
||||
'react-native/no-inline-styles': 2,
|
||||
'react-native/no-color-literals': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'import/no-cycle': [1, { maxDepth: 1 }],
|
||||
'import/no-unresolved': ['error', { ignore: ['react', 'react-native'] }],
|
||||
'import/no-extraneous-dependencies': ['warn', { packageDir: ['./'] }],
|
||||
'no-unused-vars': [2, { argsIgnorePattern: '^_' }],
|
||||
'no-underscore-dangle': 0,
|
||||
'max-len': [1, 100],
|
||||
'operator-assignment': 'warn',
|
||||
'global-require': 0,
|
||||
'implicit-arrow-linebreak': 0,
|
||||
'prettier/prettier': [
|
||||
'warn',
|
||||
{},
|
||||
{
|
||||
usePrettierrc: true,
|
||||
},
|
||||
],
|
||||
'no-multiple-empty-lines': [
|
||||
'error',
|
||||
{
|
||||
max: 1,
|
||||
maxEOF: 0,
|
||||
maxBOF: 0,
|
||||
},
|
||||
],
|
||||
'operator-linebreak': 0,
|
||||
'comma-dangle': 0,
|
||||
'no-useless-constructor': 0,
|
||||
'object-curly-newline': 0,
|
||||
'prefer-promise-reject-errors': 0,
|
||||
'newline-before-return': 'error',
|
||||
indent: 0,
|
||||
},
|
||||
globals: {
|
||||
fetch: false,
|
||||
shallow: true,
|
||||
mount: true,
|
||||
render: true,
|
||||
navigator: true,
|
||||
__DEV__: true,
|
||||
requestAnimationFrame: true,
|
||||
},
|
||||
};
|
||||
4
examples/expo/.expo-shared/assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
||||
13
examples/expo/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
bracketSpacing: false,
|
||||
jsxBracketSameLine: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
jsxBracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
};
|
||||
18
examples/expo/App.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { useFonts } from 'expo-font';
|
||||
import AppNavigator from './src/navigation/AppNavigator';
|
||||
|
||||
const App = () => {
|
||||
const [loaded] = useFonts({
|
||||
'AvertaStd-Semibold': require('./assets/fonts/AvertaStd-Semibold.otf'),
|
||||
'AvertaStd-Regular': require('./assets/fonts/AvertaStd-Regular.otf'),
|
||||
});
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <AppNavigator />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
27
examples/expo/app.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Sticky Parallax Header by Netguru",
|
||||
"slug": "sticky-parallax-header-by-netguru",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
BIN
examples/expo/assets/favicon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 113 B After Width: | Height: | Size: 113 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 698 B After Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 1005 B After Width: | Height: | Size: 1005 B |
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 671 B |
|
Before Width: | Height: | Size: 948 B After Width: | Height: | Size: 948 B |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 596 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
0
example/src/assets/icons/logo.png → examples/expo/assets/images/logo.png
Normal file → Executable file
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
0
example/src/assets/icons/logo@2x.png → examples/expo/assets/images/logo@2x.png
Normal file → Executable file
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
0
example/src/assets/icons/logo@3x.png → examples/expo/assets/images/logo@3x.png
Normal file → Executable file
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 714 KiB After Width: | Height: | Size: 714 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 629 KiB After Width: | Height: | Size: 629 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 432 KiB |
BIN
examples/expo/assets/splash.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
20
examples/expo/babel.config.js
Normal file
@ -0,0 +1,20 @@
|
||||
const path = require('path');
|
||||
const pak = require('../../package.json');
|
||||
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
alias: {
|
||||
[pak.name]: path.join(__dirname, '../..', pak.source),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
};
|
||||
37
examples/expo/metro.config.js
Normal file
@ -0,0 +1,37 @@
|
||||
const path = require('path');
|
||||
const blacklist = require('metro-config/src/defaults/blacklist');
|
||||
const escape = require('escape-string-regexp');
|
||||
const pak = require('../../package.json');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
|
||||
const modules = Object.keys({
|
||||
...pak.peerDependencies,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
projectRoot: __dirname,
|
||||
watchFolders: [root],
|
||||
|
||||
// We need to make sure that only one version is loaded for peerDependencies
|
||||
// So we blacklist them at the root, and alias them to the versions in example's node_modules
|
||||
resolver: {
|
||||
blacklistRE: blacklist(
|
||||
modules.map((m) => new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`))
|
||||
),
|
||||
|
||||
extraNodeModules: modules.reduce((acc, name) => {
|
||||
acc[name] = path.join(__dirname, 'node_modules', name);
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
|
||||
transformer: {
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
inlineRequires: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
4558
examples/expo/package-lock.json
generated
Normal file
48
examples/expo/package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/native": "^5.7.3",
|
||||
"@react-navigation/stack": "^5.9.0",
|
||||
"expo": "~38.0.8",
|
||||
"expo-blur": "~8.1.2",
|
||||
"expo-font": "~8.2.1",
|
||||
"expo-status-bar": "^1.0.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "~16.11.0",
|
||||
"react-dom": "~16.11.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
|
||||
"react-native-gesture-handler": "~1.6.0",
|
||||
"react-native-reanimated": "~1.9.0",
|
||||
"react-native-safe-area-context": "~3.0.7",
|
||||
"react-native-screens": "~2.9.0",
|
||||
"react-native-web": "~0.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"@babel/core": "^7.8.6",
|
||||
"@react-native-community/eslint-config": "^2.0.0",
|
||||
"babel-preset-expo": "~8.1.0",
|
||||
"eslint": "^7.8.1",
|
||||
"eslint-config-airbnb": "^18.2.0",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-reactnative": "^1.0.2",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-jest": "^24.0.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react-hooks": "^4.1.2",
|
||||
"eslint-plugin-react-native": "^3.9.1",
|
||||
"prettier": "^2.1.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
302
examples/expo/src/assets/data/cards.js
Normal file
@ -0,0 +1,302 @@
|
||||
export const cardsReact = [
|
||||
{
|
||||
question: 'We can go for keys when there is possibility that our user could change the data.',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'Keys',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'Ref',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question: 'Both',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'D',
|
||||
question: 'None of above',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'JSX is typesafe.',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'True',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'False',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Which of the following needs to be updated to achieve dynamic UI updates?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'State',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'Props',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const cardsAgile = [
|
||||
{
|
||||
question: 'When might a Sprint be abnormally cancelled?',
|
||||
cardsAmount: 31,
|
||||
author: 'Ewa',
|
||||
type: 'Agile Basics',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'When the Sprint Goal becomes obsolete',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'When the sales department has an important new opportunity',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question:
|
||||
'When it becomes clear that not everything will be finished by the end of the Sprint',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Who is required to attend the Daily Scrum?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'The Development Team and Scrum Master',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'The Scrum Master and Product Owner',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question: 'The Development Team',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'The Development Team should have all the skills needed to:',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question:
|
||||
'Complete the project as estimated when the date and cost are committed to the Product Owner',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question:
|
||||
'Do all of the development work, except for specialized testing that requires additional tools and environments',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question:
|
||||
'Turn the Product Backlog items it selects into an increment of potentially releasable product functionality',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const cardsDesign = [
|
||||
{
|
||||
question: 'Can design system contain information about copywriting?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'Yes',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'No',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Who is taking care of managing a design system?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'Product Designer',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'UI Designer',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question: 'None of above',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Are there official standards for Design Systems?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'Yes',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'No',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'What kind of animal is the dolphin?',
|
||||
cards: [
|
||||
{
|
||||
number: 'A',
|
||||
question: 'Mammalr',
|
||||
value: true,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'B',
|
||||
question: 'Reptile',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question: 'Fish',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
{
|
||||
number: 'C',
|
||||
question: 'Amphibian',
|
||||
value: false,
|
||||
revealed: false,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const Brandon = {
|
||||
id: '1128349857',
|
||||
cardsAmount: 10,
|
||||
author: 'Brandon',
|
||||
type: 'Product Design',
|
||||
label: 'Design System',
|
||||
cards: cardsDesign,
|
||||
color: 'rgb(78,15,255)',
|
||||
labelColor: 'rgb(89,80,249)',
|
||||
image: require('../../../assets/images/photosPortraitBrandon.png'),
|
||||
about: 'Coffee buff. Web enthusiast. Unapologetic student. Gamer. Avid organizer.',
|
||||
};
|
||||
|
||||
const Ewa = {
|
||||
id: '3832934409',
|
||||
cardsAmount: 31,
|
||||
author: 'Ewa',
|
||||
type: 'Project Management',
|
||||
label: 'Agile Basics',
|
||||
cards: cardsAgile,
|
||||
color: 'rgb(138,85,192)',
|
||||
labelColor: 'rgb(163,109,217)',
|
||||
image: require('../../../assets/images/photosPortraitEwa.png'),
|
||||
about: 'Wannabe entrepreneur. Reader. Devoted organizer. Social media lover. Analyst.',
|
||||
};
|
||||
|
||||
const Jennifer = {
|
||||
id: '2849503859',
|
||||
cardsAmount: 16,
|
||||
author: 'Jennifer',
|
||||
type: 'Development',
|
||||
label: 'React Native 101',
|
||||
cards: cardsReact,
|
||||
color: 'rgb(255,94,107)',
|
||||
labelColor: 'rgb(255,130,140)',
|
||||
image: require('../../../assets/images/photosPortraitJennifer.png'),
|
||||
about: 'Web nerd. Alcohol trailblazer. Organizer. Hipster-friendly explorer.',
|
||||
};
|
||||
|
||||
export { Brandon, Ewa, Jennifer };
|
||||
@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, TouchableOpacity } from 'react-native'
|
||||
import { func, string, shape, bool, number } from 'prop-types'
|
||||
import styles from './QuizCard.styles'
|
||||
import QuizOption from '../QuizOption/QuizOption'
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { func, string, shape, bool, number } from 'prop-types';
|
||||
import styles from './QuizCard.styles';
|
||||
import QuizOption from '../QuizOption/QuizOption';
|
||||
|
||||
const QuizCard = ({ data: { question, cards }, num, onPress, cardsAmount }) => {
|
||||
const [revealed, setRevealed] = useState(false)
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} style={styles.container} activeOpacity={0.95}>
|
||||
@ -17,19 +17,19 @@ const QuizCard = ({ data: { question, cards }, num, onPress, cardsAmount }) => {
|
||||
<View>
|
||||
<Text style={styles.mainText}>{question}</Text>
|
||||
</View>
|
||||
{cards.map(card => (
|
||||
{cards.map((card) => (
|
||||
<QuizOption
|
||||
key={card.question}
|
||||
reveal={() => {
|
||||
setRevealed(true)
|
||||
setRevealed(true);
|
||||
}}
|
||||
revealed={revealed}
|
||||
card={card}
|
||||
/>
|
||||
))}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
QuizCard.propTypes = {
|
||||
onPress: func,
|
||||
@ -38,10 +38,10 @@ QuizCard.propTypes = {
|
||||
question: string,
|
||||
value: bool,
|
||||
revealed: bool,
|
||||
picked: bool
|
||||
picked: bool,
|
||||
}),
|
||||
num: number,
|
||||
cardsAmount: number
|
||||
}
|
||||
cardsAmount: number,
|
||||
};
|
||||
|
||||
export default QuizCard
|
||||
export default QuizCard;
|
||||
@ -1,5 +1,5 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { colors } from '../../constants'
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { colors } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
@ -7,7 +7,7 @@ export default StyleSheet.create({
|
||||
shadowColor: colors.shadowColor,
|
||||
shadowOffset: {
|
||||
width: 2,
|
||||
heght: 2
|
||||
heght: 2,
|
||||
},
|
||||
shadowRadius: 40,
|
||||
shadowOpacity: 0.08,
|
||||
@ -17,17 +17,17 @@ export default StyleSheet.create({
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 24,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16
|
||||
paddingVertical: 16,
|
||||
},
|
||||
labelContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8
|
||||
paddingBottom: 8,
|
||||
},
|
||||
labelTextContainer: {
|
||||
backgroundColor: colors.paleGrey,
|
||||
borderRadius: 16
|
||||
borderRadius: 16,
|
||||
},
|
||||
labelText: {
|
||||
fontSize: 12,
|
||||
@ -36,7 +36,7 @@ export default StyleSheet.create({
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
letterSpacing: 0.8
|
||||
letterSpacing: 0.8,
|
||||
},
|
||||
mainText: {
|
||||
fontSize: 20,
|
||||
@ -44,6 +44,6 @@ export default StyleSheet.create({
|
||||
color: colors.black,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 20,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
}
|
||||
})
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity, Image } from 'react-native';
|
||||
import { func, string, number } from 'prop-types';
|
||||
import styles from './QuizListElement.styles';
|
||||
import { colors } from '../../constants';
|
||||
|
||||
const QuizListElement = ({
|
||||
onPress,
|
||||
authorName,
|
||||
imageSource,
|
||||
mainText,
|
||||
labelText,
|
||||
elements,
|
||||
pressUser,
|
||||
}) => (
|
||||
<TouchableOpacity onPress={onPress} style={styles.container} activeOpacity={0.95}>
|
||||
<View style={styles.labelContainer}>
|
||||
<View style={styles.labelTextContainer}>
|
||||
<Text style={styles.labelText}>{labelText}</Text>
|
||||
</View>
|
||||
<View style={[styles.iconContainer, elements >= 20 && { backgroundColor: colors.coralPink }]}>
|
||||
<Image source={require('../../../assets/icons/cards.png')} style={styles.icon} />
|
||||
<Text style={[styles.number, elements < 10 && styles.iconCardElement]}>{elements}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.mainTextContainer}>
|
||||
<Text style={styles.mainText}>{mainText}</Text>
|
||||
</View>
|
||||
<View style={styles.authorWrapper}>
|
||||
<TouchableOpacity style={styles.authorContainer} onPress={pressUser}>
|
||||
<View style={styles.footerContainer}>
|
||||
<Image source={imageSource} style={styles.authorPhoto} resizeMode="contain" />
|
||||
<Text style={styles.authorName}>{authorName}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.authorBlankContainer} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
QuizListElement.propTypes = {
|
||||
onPress: func,
|
||||
authorName: string,
|
||||
mainText: string,
|
||||
labelText: string,
|
||||
elements: number,
|
||||
imageSource: Image.propTypes.source,
|
||||
pressUser: func,
|
||||
};
|
||||
|
||||
export default QuizListElement;
|
||||
@ -1,5 +1,5 @@
|
||||
import { StyleSheet, Platform } from 'react-native'
|
||||
import { colors, constants } from '../../constants'
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { colors, constants } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
@ -7,7 +7,7 @@ export default StyleSheet.create({
|
||||
shadowColor: colors.shadowColor,
|
||||
shadowOffset: {
|
||||
width: 2,
|
||||
heght: 2
|
||||
heght: 2,
|
||||
},
|
||||
shadowRadius: 40,
|
||||
shadowOpacity: 0.08,
|
||||
@ -17,16 +17,16 @@ export default StyleSheet.create({
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
borderWidth: Platform.select({ ios: 0, android: 2 }),
|
||||
borderColor: colors.paleGrey
|
||||
borderColor: colors.paleGrey,
|
||||
},
|
||||
labelContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start'
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
labelTextContainer: {
|
||||
backgroundColor: colors.paleGrey,
|
||||
borderRadius: 16
|
||||
borderRadius: 16,
|
||||
},
|
||||
labelText: {
|
||||
fontSize: 12,
|
||||
@ -34,7 +34,7 @@ export default StyleSheet.create({
|
||||
color: colors.greyishBrown,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
iconContainer: {
|
||||
flexDirection: 'row',
|
||||
@ -43,24 +43,24 @@ export default StyleSheet.create({
|
||||
shadowColor: colors.shadowColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
heght: 2
|
||||
heght: 2,
|
||||
},
|
||||
shadowRadius: 32,
|
||||
shadowOpacity: 0.016,
|
||||
backgroundColor: colors.purplishBlue,
|
||||
width: 56,
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16
|
||||
height: 16,
|
||||
},
|
||||
number: {
|
||||
color: colors.white,
|
||||
paddingLeft: 5,
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
mainText: {
|
||||
fontSize: 24,
|
||||
@ -68,37 +68,37 @@ export default StyleSheet.create({
|
||||
color: colors.black,
|
||||
letterSpacing: -0.2,
|
||||
paddingTop: 8,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
mainTextContainer: {},
|
||||
footerContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
paddingTop: 22
|
||||
paddingTop: 22,
|
||||
},
|
||||
authorPhoto: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: constants.isAndroid ? 50 : 8
|
||||
borderRadius: constants.isAndroid ? 50 : 8,
|
||||
},
|
||||
authorName: {
|
||||
fontSize: 12,
|
||||
lineHeight: 16,
|
||||
color: colors.black,
|
||||
paddingLeft: 8,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
authorWrapper: {
|
||||
flexDirection: 'row'
|
||||
flexDirection: 'row',
|
||||
},
|
||||
authorContainer: {
|
||||
paddingRight: 40
|
||||
paddingRight: 40,
|
||||
},
|
||||
authorBlankContainer: {
|
||||
width: '38%'
|
||||
width: '38%',
|
||||
},
|
||||
iconCardElement: {
|
||||
paddingLeft: 8
|
||||
}
|
||||
})
|
||||
paddingLeft: 8,
|
||||
},
|
||||
});
|
||||
85
examples/expo/src/components/QuizOption/QuizOption.js
Normal file
@ -0,0 +1,85 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, Image, TouchableOpacity } from 'react-native';
|
||||
import { string, bool, shape, func } from 'prop-types';
|
||||
import styles from './QuizOption.styles';
|
||||
import { colors } from '../../constants';
|
||||
|
||||
const QuizOption = ({ reveal, revealed, card: { number, question, value } }) => {
|
||||
const [picked, setPicked] = useState(false);
|
||||
const [paddingVertical, setPaddingVertical] = useState(0);
|
||||
|
||||
const calcPaddings = (event) => {
|
||||
const { height } = event.nativeEvent.layout;
|
||||
const circleRadius = 40;
|
||||
const padding = height > circleRadius ? height / 2.5 : 0;
|
||||
setPaddingVertical(padding);
|
||||
};
|
||||
|
||||
const renderValue = () => {
|
||||
if (value) {
|
||||
return <Image source={require('../../../assets/icons/Check.png')} />;
|
||||
}
|
||||
|
||||
return <Image source={require('../../../assets/icons/Close.png')} />;
|
||||
};
|
||||
|
||||
if (revealed) {
|
||||
let backgroundColor = 'white';
|
||||
let color = 'black';
|
||||
if (picked) {
|
||||
color = 'white';
|
||||
}
|
||||
if (picked && value) {
|
||||
backgroundColor = colors.jade;
|
||||
}
|
||||
if (picked && !value) {
|
||||
backgroundColor = colors.coralPink;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor }]}>
|
||||
<View style={[styles.letterContainer, { paddingVertical }]}>{renderValue()}</View>
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
calcPaddings(event);
|
||||
}}
|
||||
style={styles.textContainer}>
|
||||
<Text style={[styles.text, { color }]}>{question}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
reveal();
|
||||
setPicked(true);
|
||||
}}
|
||||
style={styles.container}>
|
||||
<View style={[styles.letterContainer, { paddingVertical }]}>
|
||||
<Text style={styles.letter}>{number}</Text>
|
||||
</View>
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
calcPaddings(event);
|
||||
}}
|
||||
style={styles.textContainer}>
|
||||
<Text style={styles.text}>{question}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
QuizOption.propTypes = {
|
||||
card: shape({
|
||||
number: string,
|
||||
question: string,
|
||||
value: bool,
|
||||
picked: bool,
|
||||
}),
|
||||
reveal: func,
|
||||
revealed: bool,
|
||||
};
|
||||
|
||||
export default QuizOption;
|
||||
@ -1,5 +1,5 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { colors, constants } from '../../constants'
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { colors, constants } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
@ -10,10 +10,10 @@ export default StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 4,
|
||||
marginBottom: 4
|
||||
marginBottom: 4,
|
||||
},
|
||||
letter: {
|
||||
color: colors.black
|
||||
color: colors.black,
|
||||
},
|
||||
letterContainer: {
|
||||
alignItems: 'center',
|
||||
@ -22,19 +22,19 @@ export default StyleSheet.create({
|
||||
width: 36,
|
||||
borderRadius: 17.5,
|
||||
backgroundColor: colors.white,
|
||||
margin: 6
|
||||
margin: 6,
|
||||
},
|
||||
textContainer: {
|
||||
width: '80%',
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 7,
|
||||
paddingVertical: 5
|
||||
paddingVertical: 5,
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
color: colors.black,
|
||||
fontFamily: 'AvertaStd-Regular'
|
||||
}
|
||||
})
|
||||
fontFamily: 'AvertaStd-Regular',
|
||||
},
|
||||
});
|
||||
291
examples/expo/src/components/UserModal/UserModal.js
Normal file
@ -0,0 +1,291 @@
|
||||
import React from 'react';
|
||||
import { Text, View, Image, TouchableOpacity, Animated, StatusBar, Platform } from 'react-native';
|
||||
import StickyParallaxHeader from 'react-native-sticky-parallax-header';
|
||||
import { func, shape, string, oneOfType, object, array, number } from 'prop-types';
|
||||
import { constants, sizes } from '../../constants';
|
||||
import styles from './UserModal.styles';
|
||||
import QuizListElement from '../QuizListElement/QuizListElement';
|
||||
|
||||
const { event, ValueXY } = Animated;
|
||||
|
||||
class UserModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
headerLayout: {
|
||||
height: 0,
|
||||
},
|
||||
contentHeight: 0,
|
||||
};
|
||||
this.scrollY = new ValueXY();
|
||||
}
|
||||
|
||||
setHeaderSize = (headerLayout) => this.setState({ headerLayout });
|
||||
|
||||
renderHeader = () => {
|
||||
const { onPressCloseModal, user } = this.props;
|
||||
|
||||
const [beforeFadeImg, startFadeImg, finishFadeImg] = [
|
||||
this.scrollPosition(30),
|
||||
this.scrollPosition(40),
|
||||
this.scrollPosition(70),
|
||||
];
|
||||
const [beforeFadeName, startFadeName, finishFadeName] = [
|
||||
this.scrollPosition(50),
|
||||
this.scrollPosition(60),
|
||||
this.scrollPosition(75),
|
||||
];
|
||||
|
||||
const imageOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, beforeFadeImg, startFadeImg, finishFadeImg],
|
||||
outputRange: [0, 0, 0.5, 1],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const nameOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, beforeFadeName, startFadeName, finishFadeName],
|
||||
outputRange: [0, 0, 0.5, 1],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={[styles.headerWrapper, styles.userModalHeader, { backgroundColor: user.color }]}>
|
||||
<TouchableOpacity hitSlop={sizes.hitSlop} onPress={onPressCloseModal}>
|
||||
<Image
|
||||
style={styles.icon}
|
||||
resizeMode="contain"
|
||||
source={require('../../../assets/icons/iconCloseWhite.png')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.headerMenu}>
|
||||
<View style={styles.headerTitleContainer}>
|
||||
<Animated.Image
|
||||
source={user.image}
|
||||
style={[styles.headerPic, { opacity: imageOpacity }]}
|
||||
/>
|
||||
<Animated.Text style={[styles.headerTitle, { opacity: nameOpacity }]}>
|
||||
{user.author}
|
||||
</Animated.Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity hitSlop={sizes.hitSlop}>
|
||||
<Image
|
||||
style={styles.icon}
|
||||
resizeMode="contain"
|
||||
source={require('../../../assets/icons/Icon-Menu.png')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderForeground = () => {
|
||||
const { user } = this.props;
|
||||
const startSize = constants.responsiveWidth(18);
|
||||
const endSize = constants.responsiveWidth(12);
|
||||
|
||||
const [startImgAnimation, finishImgAnimation] = [
|
||||
this.scrollPosition(27),
|
||||
this.scrollPosition(31),
|
||||
];
|
||||
const [startAuthorFade, finishAuthorFade] = [this.scrollPosition(40), this.scrollPosition(50)];
|
||||
|
||||
const [startAboutFade, fininshAboutFade] = [this.scrollPosition(60), this.scrollPosition(70)];
|
||||
|
||||
const imageOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startImgAnimation, finishImgAnimation],
|
||||
outputRange: [1, 0.8, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const imageSize = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startImgAnimation, finishImgAnimation],
|
||||
outputRange: [startSize, startSize, endSize],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const authorOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startAuthorFade, finishAuthorFade],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const aboutOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startAboutFade, fininshAboutFade],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.foreground}>
|
||||
<Animated.View style={{ opacity: imageOpacity }}>
|
||||
<Animated.Image
|
||||
source={user.image}
|
||||
style={[styles.profilePic, { width: imageSize, height: imageSize }]}
|
||||
/>
|
||||
</Animated.View>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.messageContainer,
|
||||
styles.userModalMessageContainer,
|
||||
{ opacity: authorOpacity },
|
||||
]}>
|
||||
<Text style={styles.message}>{user.author}</Text>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.infoContainer, { opacity: aboutOpacity }]}>
|
||||
<Text style={styles.infoText}>{user.about}</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderBackground = () => {
|
||||
const {
|
||||
headerLayout: { height },
|
||||
} = this.state;
|
||||
const headerBorderRadius = this.scrollY.y.interpolate({
|
||||
inputRange: [0, height],
|
||||
outputRange: [80, 0],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.background,
|
||||
{
|
||||
borderBottomRightRadius: headerBorderRadius,
|
||||
backgroundColor: user.color,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
calcMargin = () => {
|
||||
const { contentHeight } = this.state;
|
||||
let marginBottom = 0;
|
||||
|
||||
if (contentHeight) {
|
||||
const isBigContent = constants.deviceHeight - contentHeight < 0;
|
||||
|
||||
if (isBigContent) {
|
||||
return marginBottom;
|
||||
}
|
||||
|
||||
marginBottom = constants.deviceHeight - sizes.headerHeight - contentHeight;
|
||||
|
||||
return marginBottom;
|
||||
}
|
||||
|
||||
return marginBottom;
|
||||
};
|
||||
|
||||
onLayoutContent = (e) => {
|
||||
this.setState({
|
||||
contentHeight: e.nativeEvent.layout.height,
|
||||
});
|
||||
};
|
||||
|
||||
scrollPosition(value) {
|
||||
const {
|
||||
headerLayout: { height },
|
||||
} = this.state;
|
||||
|
||||
return constants.scrollPosition(height, value);
|
||||
}
|
||||
|
||||
renderContent = () => {
|
||||
const marginBottom = Platform.select({
|
||||
ios: this.calcMargin(),
|
||||
android: 0,
|
||||
});
|
||||
const { user } = this.props;
|
||||
const title = "Author's Quizes";
|
||||
const cards = [
|
||||
{
|
||||
id: '4850294857',
|
||||
elements: user.cardsAmount,
|
||||
authorName: user.author,
|
||||
mainText: user.label,
|
||||
labelText: user.type,
|
||||
imageSource: user.image,
|
||||
},
|
||||
];
|
||||
const {
|
||||
setModalVisible,
|
||||
navigation: { navigate },
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={this.onLayoutContent}
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
marginBottom,
|
||||
paddingBottom: sizes.userScreenParallaxHeader,
|
||||
},
|
||||
]}>
|
||||
<Text style={styles.contentText}>{title}</Text>
|
||||
{cards.map((card) => (
|
||||
<QuizListElement
|
||||
key={card.id}
|
||||
elements={card.elements}
|
||||
authorName={card.authorName}
|
||||
mainText={card.mainText}
|
||||
labelText={card.labelText}
|
||||
imageSource={card.imageSource}
|
||||
onPress={() => {
|
||||
setModalVisible(false);
|
||||
/**
|
||||
* setTimeout is here to prevent from navigating
|
||||
* to the Quiz Screen before the modal animation is
|
||||
* finished so now it looks like this:
|
||||
* Close Modal Animation -> Open Quiz Screen Animation
|
||||
*/
|
||||
setTimeout(() => {
|
||||
navigate('Card', { user });
|
||||
}, 300);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar backgroundColor={user.color} barStyle="light-content" />
|
||||
<StickyParallaxHeader
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
deviceWidth={constants.deviceWidth}
|
||||
parallaxHeight={sizes.userScreenParallaxHeader}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {
|
||||
useNativeDriver: false,
|
||||
})}
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={sizes.userModalHeaderHeight}
|
||||
background={this.renderBackground()}>
|
||||
{this.renderContent()}
|
||||
</StickyParallaxHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserModal.propTypes = {
|
||||
navigation: shape({}),
|
||||
setModalVisible: func,
|
||||
onPressCloseModal: func,
|
||||
user: shape({
|
||||
author: string,
|
||||
about: string,
|
||||
image: oneOfType([object, array, func, number]),
|
||||
}),
|
||||
};
|
||||
|
||||
export default UserModal;
|
||||
@ -1,66 +1,66 @@
|
||||
import { StyleSheet, Platform } from 'react-native'
|
||||
import { ifIphoneX } from '../../constants/utils'
|
||||
import { colors, constants, screenStyles } from '../../constants'
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { ifIphoneX } from '../../constants/utils';
|
||||
import { colors, constants, screenStyles } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
...screenStyles,
|
||||
userModalHeader: {
|
||||
paddingTop: Platform.select({ ios: ifIphoneX(50, 30), android: 18 })
|
||||
paddingTop: Platform.select({ ios: ifIphoneX(50, 30), android: 18 }),
|
||||
},
|
||||
headerPic: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8
|
||||
borderRadius: 8,
|
||||
},
|
||||
headerMenu: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerTitleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginLeft: -(constants.deviceWidth / 3)
|
||||
marginLeft: -(constants.deviceWidth / 3),
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
color: colors.white,
|
||||
marginLeft: 12,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
infoContainer: {
|
||||
flexDirection: 'row',
|
||||
paddingBottom: 32
|
||||
paddingBottom: 32,
|
||||
},
|
||||
iconContainer: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: colors.white,
|
||||
width: 56
|
||||
width: 56,
|
||||
},
|
||||
infoText: {
|
||||
color: colors.white,
|
||||
fontSize: constants.normalizedFontSize(16),
|
||||
lineHeight: 24,
|
||||
fontFamily: 'AvertaStd-Regular'
|
||||
fontFamily: 'AvertaStd-Regular',
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16
|
||||
height: 16,
|
||||
},
|
||||
footerContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 24
|
||||
marginHorizontal: 24,
|
||||
},
|
||||
authorPhoto: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8
|
||||
borderRadius: 8,
|
||||
},
|
||||
userModalMessageContainer: {
|
||||
paddingBottom: 8
|
||||
}
|
||||
})
|
||||
paddingBottom: 8,
|
||||
},
|
||||
});
|
||||
6
examples/expo/src/components/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import QuizListElement from './QuizListElement/QuizListElement';
|
||||
import UserModal from './UserModal/UserModal';
|
||||
import QuizCard from './QuizCard/QuizCard';
|
||||
import QuizOption from './QuizOption/QuizOption';
|
||||
|
||||
export { QuizListElement, UserModal, QuizCard, QuizOption };
|
||||
@ -12,5 +12,5 @@ export default {
|
||||
paleGrey: 'rgb(246,245,248)',
|
||||
greyishBrown: 'rgb(71,71,71)',
|
||||
coralPink: 'rgb(255,94,107)',
|
||||
jade: 'rgb(29,167,93)'
|
||||
}
|
||||
jade: 'rgb(29,167,93)',
|
||||
};
|
||||
63
examples/expo/src/constants/constants.js
Normal file
@ -0,0 +1,63 @@
|
||||
import { Dimensions, PixelRatio, Platform } from 'react-native';
|
||||
|
||||
const { height, width } = Dimensions.get('window');
|
||||
|
||||
// based on iphone 5s's scale
|
||||
const scale = width / 320;
|
||||
|
||||
const responsiveHeight = (h) => height * (h / 100);
|
||||
const responsiveWidth = (w) => width * (w / 100);
|
||||
const breakpoints = {
|
||||
smallPhoneWidth: 320,
|
||||
smallPhoneHeight: 600,
|
||||
mediumPhoneWidth: 414,
|
||||
bigPhoneWidth: 480,
|
||||
};
|
||||
const isSmallScreen =
|
||||
width <= breakpoints.smallPhoneWidth || height <= breakpoints.smallPhoneHeight;
|
||||
const isNormalScreen = width > breakpoints.smallPhoneWidth && width < breakpoints.mediumPhoneWidth;
|
||||
const isBigScreen = width >= breakpoints.mediumPhoneWidth;
|
||||
const isBiggestPhoneScreen = width >= breakpoints.bigPhoneWidth;
|
||||
const deviceWidth = width;
|
||||
const deviceHeight = height;
|
||||
const isAndroid = Platform.OS === 'android';
|
||||
|
||||
const normalizedFontSize = (basicFontSize) => {
|
||||
if (isSmallScreen) {
|
||||
return basicFontSize - 6;
|
||||
}
|
||||
if (isNormalScreen) {
|
||||
return basicFontSize;
|
||||
}
|
||||
if (isBigScreen) {
|
||||
return basicFontSize + 1;
|
||||
}
|
||||
|
||||
return basicFontSize;
|
||||
};
|
||||
|
||||
const getResponsiveFontSize = (fontSize) => {
|
||||
const newSize = fontSize * scale;
|
||||
if (Platform.OS === 'ios') {
|
||||
return Math.round(PixelRatio.roundToNearestPixel(newSize));
|
||||
}
|
||||
|
||||
return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2;
|
||||
};
|
||||
|
||||
const scrollPosition = (scrollHeight, x) => x * 0.01 * scrollHeight;
|
||||
|
||||
export default {
|
||||
deviceHeight,
|
||||
deviceWidth,
|
||||
responsiveHeight,
|
||||
responsiveWidth,
|
||||
isAndroid,
|
||||
isSmallScreen,
|
||||
isNormalScreen,
|
||||
isBigScreen,
|
||||
isBiggestPhoneScreen,
|
||||
normalizedFontSize,
|
||||
scrollPosition,
|
||||
getResponsiveFontSize,
|
||||
};
|
||||
6
examples/expo/src/constants/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import colors from './colors';
|
||||
import constants from './constants';
|
||||
import sizes from './sizes';
|
||||
import screenStyles from './screenStyles';
|
||||
|
||||
export { constants, colors, sizes, screenStyles };
|
||||
@ -1,20 +1,27 @@
|
||||
import { StyleSheet, Platform } from 'react-native'
|
||||
import { ifIphoneX } from '../constants/utils'
|
||||
import colors from './colors'
|
||||
import constants from './constants'
|
||||
import { StyleSheet, Platform } from 'react-native';
|
||||
import { ifIphoneX } from './utils';
|
||||
import colors from './colors';
|
||||
import constants from './constants';
|
||||
|
||||
const screenStyles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 24
|
||||
paddingBottom: 24,
|
||||
},
|
||||
flatlistContainer: {
|
||||
width: constants.deviceWidth,
|
||||
height: constants.deviceHeight - 80,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 24,
|
||||
},
|
||||
contentText: {
|
||||
fontSize: 24,
|
||||
@ -24,18 +31,18 @@ const screenStyles = StyleSheet.create({
|
||||
letterSpacing: -0.2,
|
||||
paddingTop: 40,
|
||||
paddingBottom: 20,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
foreground: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 24,
|
||||
justifyContent: 'flex-end'
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
background: {
|
||||
width: '100%',
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: colors.primaryGreen,
|
||||
height: '100%'
|
||||
height: '100%',
|
||||
},
|
||||
headerWrapper: {
|
||||
width: '100%',
|
||||
@ -43,31 +50,31 @@ const screenStyles = StyleSheet.create({
|
||||
paddingTop: Platform.select({ ios: ifIphoneX(50, 40), android: 55 }),
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
logo: {
|
||||
height: 24,
|
||||
width: 142
|
||||
width: 142,
|
||||
},
|
||||
message: {
|
||||
color: colors.white,
|
||||
fontSize: constants.responsiveWidth(11),
|
||||
lineHeight: 48,
|
||||
fontSize: constants.getResponsiveFontSize(32),
|
||||
lineHeight: constants.getResponsiveFontSize(32) * 1.2,
|
||||
letterSpacing: -1,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
messageContainer: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 24
|
||||
paddingBottom: 24,
|
||||
},
|
||||
profilePic: {
|
||||
width: constants.responsiveWidth(18),
|
||||
height: constants.responsiveWidth(18),
|
||||
borderRadius: constants.responsiveWidth(4.5)
|
||||
borderRadius: constants.responsiveWidth(4.5),
|
||||
},
|
||||
foregroundText: {
|
||||
color: colors.white
|
||||
}
|
||||
})
|
||||
color: colors.white,
|
||||
},
|
||||
});
|
||||
|
||||
export default screenStyles
|
||||
export default screenStyles;
|
||||
31
examples/expo/src/constants/sizes.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { ifIphoneX } from './utils';
|
||||
import constants from './constants';
|
||||
|
||||
export default {
|
||||
toolbarHeight: 100,
|
||||
headerHeight: ifIphoneX(92, constants.responsiveHeight(13)),
|
||||
cardScreenHeaderHeight: Platform.select({
|
||||
ios: ifIphoneX(95, 85),
|
||||
android: 100,
|
||||
}),
|
||||
userModalHeaderHeight: ifIphoneX(100, 75),
|
||||
homeScreenParallaxHeader: ifIphoneX(
|
||||
constants.responsiveHeight(38),
|
||||
constants.responsiveHeight(48)
|
||||
),
|
||||
cardScreenParallaxHeader: ifIphoneX(
|
||||
constants.responsiveHeight(43),
|
||||
constants.responsiveHeight(53)
|
||||
),
|
||||
userScreenParallaxHeader: ifIphoneX(
|
||||
constants.responsiveHeight(43),
|
||||
constants.responsiveHeight(53)
|
||||
),
|
||||
hitSlop: {
|
||||
top: 15,
|
||||
left: 15,
|
||||
bottom: 15,
|
||||
right: 15,
|
||||
},
|
||||
};
|
||||
20
examples/expo/src/constants/utils.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { Dimensions, Platform } from 'react-native';
|
||||
|
||||
export function isIphoneX() {
|
||||
const dimen = Dimensions.get('window');
|
||||
|
||||
return (
|
||||
Platform.OS === 'ios' &&
|
||||
!Platform.isPad &&
|
||||
!Platform.isTVOS &&
|
||||
(dimen.height === 812 || dimen.width === 812 || dimen.height === 896 || dimen.width === 896)
|
||||
);
|
||||
}
|
||||
|
||||
export function ifIphoneX(iphoneXStyle, regularStyle) {
|
||||
if (isIphoneX()) {
|
||||
return iphoneXStyle;
|
||||
}
|
||||
|
||||
return regularStyle;
|
||||
}
|
||||
17
examples/expo/src/navigation/AppNavigator.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { HomeScreen, CardScreen } from '../screens';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
const AppNavigator = () => (
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator headerMode="none">
|
||||
<Stack.Screen name="Home" component={HomeScreen} />
|
||||
<Stack.Screen name="Card" component={CardScreen} />
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
export default AppNavigator;
|
||||
216
examples/expo/src/screens/CardScreen/CardScreen.js
Normal file
@ -0,0 +1,216 @@
|
||||
import React from 'react';
|
||||
import { Text, View, Image, TouchableOpacity, StatusBar, Animated, FlatList } from 'react-native';
|
||||
import StickyParallaxHeader from 'react-native-sticky-parallax-header';
|
||||
import { shape } from 'prop-types';
|
||||
import { constants, sizes, colors } from '../../constants';
|
||||
import { QuizCard } from '../../components';
|
||||
import styles from './CardScreen.styles';
|
||||
|
||||
const { event, ValueXY } = Animated;
|
||||
class CardScreen extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
headerLayout: {
|
||||
height: 0,
|
||||
},
|
||||
topReached: true,
|
||||
endReached: false,
|
||||
stickyHeaderEndReached: false,
|
||||
stickyHeaderTopReached: true,
|
||||
};
|
||||
|
||||
this.scrollY = new ValueXY();
|
||||
}
|
||||
|
||||
setHeaderSize = (headerLayout) => this.setState({ headerLayout });
|
||||
|
||||
scrollPosition = (value) => {
|
||||
const { headerLayout } = this.state;
|
||||
|
||||
return constants.scrollPosition(headerLayout.height, value);
|
||||
};
|
||||
|
||||
renderHeader = (user) => {
|
||||
const opacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, this.scrollPosition(60), this.scrollPosition(90)],
|
||||
outputRange: [0, 0, 1],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const { navigation } = this.props;
|
||||
|
||||
return (
|
||||
<View style={[styles.headerWrapper, { backgroundColor: user.color }]}>
|
||||
<View style={styles.headerMenu}>
|
||||
<TouchableOpacity hitSlop={sizes.hitSlop} onPress={() => navigation.goBack()}>
|
||||
<Image
|
||||
style={styles.icon}
|
||||
resizeMode="contain"
|
||||
source={require('../../../assets/icons/Icon-Arrow.png')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Animated.View style={[styles.headerTitleContainer, { opacity }]}>
|
||||
<Text style={styles.headerTitle}>{user.label}</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
<Image
|
||||
style={styles.icon}
|
||||
resizeMode="contain"
|
||||
source={require('../../../assets/icons/Icon-Menu.png')}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderForeground = (user) => {
|
||||
const { cardsAmount, labelColor } = user;
|
||||
const labelOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, this.scrollPosition(19), this.scrollPosition(25)],
|
||||
outputRange: [1, 0.8, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const titleOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, this.scrollPosition(45), this.scrollPosition(55)],
|
||||
outputRange: [1, 0.8, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const authorOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, this.scrollPosition(55), this.scrollPosition(70)],
|
||||
outputRange: [1, 0.8, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.foreground}>
|
||||
<Animated.View
|
||||
style={[styles.foregroundTitle, { opacity: labelOpacity, backgroundColor: labelColor }]}>
|
||||
<Text style={styles.foregroundText}>{user.type}</Text>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.messageContainer, { opacity: titleOpacity }]}>
|
||||
<Text style={styles.message}>{user.label}</Text>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.infoContainer, { opacity: authorOpacity }]}>
|
||||
<View style={styles.iconContainer}>
|
||||
<Image source={require('../../../assets/icons/cards_black.png')} style={styles.icon} />
|
||||
<Text style={styles.number}>{cardsAmount}</Text>
|
||||
</View>
|
||||
<View style={styles.footerContainer}>
|
||||
<Image source={user.image} style={styles.authorPhoto} resizeMode="contain" />
|
||||
<Text style={styles.authorName}>{user.author}</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderBackground = (user) => {
|
||||
const {
|
||||
headerLayout: { height },
|
||||
} = this.state;
|
||||
const headerBorderRadius = this.scrollY.y.interpolate({
|
||||
inputRange: [0, height],
|
||||
outputRange: [80, 0],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.background,
|
||||
{
|
||||
borderBottomRightRadius: headerBorderRadius,
|
||||
backgroundColor: user.color,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
shouldBeEnabled = () => {
|
||||
const { endReached, stickyHeaderEndReached, topReached, stickyHeaderTopReached } = this.state;
|
||||
const bottomCondition = endReached && stickyHeaderEndReached;
|
||||
const topCondition = topReached && stickyHeaderTopReached;
|
||||
|
||||
return bottomCondition || !topCondition;
|
||||
};
|
||||
|
||||
onScroll = ({ nativeEvent }) => {
|
||||
const { contentOffset, layoutMeasurement, contentSize } = nativeEvent;
|
||||
if (layoutMeasurement.height + contentOffset.y >= contentSize.height - 20) {
|
||||
this.setState({ endReached: true, topReached: false });
|
||||
}
|
||||
|
||||
if (contentOffset.y <= 0) {
|
||||
this.setState({
|
||||
topReached: true,
|
||||
endReached: false,
|
||||
stickyHeaderTopReached: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderFlatlistContent = (user) => (
|
||||
<View style={styles.flatlistContainer}>
|
||||
<FlatList
|
||||
data={user.cards}
|
||||
renderItem={({ item, index }) => <QuizCard data={item} num={index} cardsAmount={100} />}
|
||||
keyExtractor={(item) => item.question}
|
||||
onScroll={this.onScroll}
|
||||
scrollEnabled={constants.isAndroid ? true : this.shouldBeEnabled()}
|
||||
nestedScrollEnabled
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
renderContent = (user) => (
|
||||
<View style={styles.content}>
|
||||
{user.cards.map((data, i, arr) => (
|
||||
<QuizCard data={data} num={i} key={data.question} cardsAmount={arr.length} />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
stickyHeaderEndReached = () => {
|
||||
this.setState({
|
||||
stickyHeaderEndReached: true,
|
||||
stickyHeaderTopReached: false,
|
||||
});
|
||||
};
|
||||
|
||||
stickyHeaderTopReached = () => {
|
||||
this.setState({
|
||||
stickyHeaderTopReached: true,
|
||||
stickyHeaderEndReached: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { route } = this.props;
|
||||
const { user } = route.params;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar barStyle="light-content" backgroundColor={colors.transparent} translucent />
|
||||
<StickyParallaxHeader
|
||||
foreground={this.renderForeground(user)}
|
||||
header={this.renderHeader(user)}
|
||||
deviceWidth={constants.deviceWidth}
|
||||
parallaxHeight={sizes.cardScreenParallaxHeader}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {
|
||||
useNativeDriver: false,
|
||||
})}
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={sizes.cardScreenHeaderHeight}
|
||||
background={this.renderBackground(user)}
|
||||
onEndReached={this.stickyHeaderEndReached}
|
||||
onTopReached={this.stickyHeaderTopReached}>
|
||||
{this.renderFlatlistContent(user)}
|
||||
</StickyParallaxHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
CardScreen.propTypes = { route: shape({ user: {} }) };
|
||||
CardScreen.defaultProps = { route: {} };
|
||||
|
||||
export default CardScreen;
|
||||
@ -1,12 +1,12 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { colors, screenStyles, constants } from '../../constants'
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { colors, screenStyles, constants } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
...screenStyles,
|
||||
foregroundText: {
|
||||
color: colors.white,
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
paddingHorizontal: 12
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
foregroundTitle: {
|
||||
height: 36,
|
||||
@ -14,60 +14,60 @@ export default StyleSheet.create({
|
||||
borderRadius: 18,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
infoContainer: {
|
||||
flexDirection: 'row',
|
||||
paddingBottom: 32
|
||||
paddingBottom: 32,
|
||||
},
|
||||
iconContainer: {
|
||||
flexDirection: 'row',
|
||||
padding: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: colors.white,
|
||||
width: 56
|
||||
width: 56,
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginTop: 3
|
||||
marginTop: 3,
|
||||
},
|
||||
number: {
|
||||
color: colors.black,
|
||||
paddingLeft: 4,
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
footerContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 24
|
||||
marginHorizontal: 24,
|
||||
},
|
||||
authorPhoto: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: constants.isAndroid ? 50 : 8
|
||||
borderRadius: constants.isAndroid ? 50 : 8,
|
||||
},
|
||||
authorName: {
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
color: colors.white,
|
||||
paddingLeft: 12,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
headerMenu: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerTitleContainer: {
|
||||
marginLeft: 24
|
||||
marginLeft: 24,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
color: colors.white,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
}
|
||||
})
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
});
|
||||
263
examples/expo/src/screens/HomeScreen/HomeScreen.js
Normal file
@ -0,0 +1,263 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
Image,
|
||||
StatusBar,
|
||||
Modal,
|
||||
Animated,
|
||||
Platform,
|
||||
RefreshControl,
|
||||
} from 'react-native';
|
||||
import StickyParallaxHeader from 'react-native-sticky-parallax-header';
|
||||
import { QuizListElement, UserModal } from '../../components';
|
||||
import { constants, colors, sizes } from '../../constants';
|
||||
import styles from './HomeScreen.styles';
|
||||
import { Brandon, Jennifer, Ewa } from '../../assets/data/cards';
|
||||
|
||||
const { event, ValueXY } = Animated;
|
||||
export default class HomeScreen extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
headerLayout: {
|
||||
height: 0,
|
||||
},
|
||||
contentHeight: {},
|
||||
modalVisible: false,
|
||||
refreshing: false,
|
||||
};
|
||||
this.scrollY = new ValueXY();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// eslint-disable-next-line
|
||||
this.scrollY.y.addListener(({ value }) => (this._value = value));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.scrollY.y.removeListener();
|
||||
}
|
||||
|
||||
setModalVisible = (visible) => {
|
||||
this.setState({ modalVisible: visible });
|
||||
};
|
||||
|
||||
setHeaderSize = (headerLayout) => this.setState({ headerLayout });
|
||||
|
||||
openUserModal = (userSelected) => {
|
||||
this.setState({ userSelected }, () => this.setModalVisible(true));
|
||||
};
|
||||
|
||||
scrollPosition = (value) => {
|
||||
const { headerLayout } = this.state;
|
||||
|
||||
return constants.scrollPosition(headerLayout.height, value);
|
||||
};
|
||||
|
||||
renderHeader = () => (
|
||||
<View style={[styles.headerWrapper, styles.homeScreenHeader]}>
|
||||
<Image
|
||||
resizeMode="contain"
|
||||
source={require('../../../assets/images/logo.png')}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
renderForeground = () => {
|
||||
const message = "Mornin' Mark! \nReady for a quiz?";
|
||||
const startSize = constants.responsiveWidth(18);
|
||||
const endSize = constants.responsiveWidth(10);
|
||||
const [startImgFade, finishImgFade] = [this.scrollPosition(22), this.scrollPosition(27)];
|
||||
const [startImgSize, finishImgSize] = [this.scrollPosition(20), this.scrollPosition(30)];
|
||||
const [startTitleFade, finishTitleFade] = [this.scrollPosition(25), this.scrollPosition(45)];
|
||||
|
||||
const imageOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startImgFade, finishImgFade],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const imageSize = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startImgSize, finishImgSize],
|
||||
outputRange: [startSize, startSize, endSize],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
const titleOpacity = this.scrollY.y.interpolate({
|
||||
inputRange: [0, startTitleFade, finishTitleFade],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.foreground}>
|
||||
<Animated.View style={{ opacity: imageOpacity }}>
|
||||
<Animated.Image
|
||||
source={require('../../../assets/images/photosPortraitMe.png')}
|
||||
style={[styles.profilePic, { width: imageSize, height: imageSize }]}
|
||||
/>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.messageContainer, { opacity: titleOpacity }]}>
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderQuizElements = (title) => {
|
||||
const users = [Brandon, Jennifer, Ewa];
|
||||
const {
|
||||
navigation: { navigate },
|
||||
} = this.props;
|
||||
|
||||
return users.map(
|
||||
(user) =>
|
||||
(title === 'Popular Quizes' || title === user.type) && (
|
||||
<QuizListElement
|
||||
key={user.id}
|
||||
elements={user.cardsAmount}
|
||||
authorName={user.author}
|
||||
mainText={user.label}
|
||||
labelText={user.type}
|
||||
imageSource={user.image}
|
||||
onPress={() => navigate('Card', { user })}
|
||||
pressUser={() => this.openUserModal(user)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
calcMargin = (title) => {
|
||||
const { contentHeight } = this.state;
|
||||
let marginBottom = 50;
|
||||
|
||||
if (contentHeight[title]) {
|
||||
const padding = 24;
|
||||
const isBigContent = constants.deviceHeight - contentHeight[title] < 0;
|
||||
|
||||
if (isBigContent) {
|
||||
return marginBottom;
|
||||
}
|
||||
|
||||
marginBottom =
|
||||
constants.deviceHeight - padding * 2 - sizes.headerHeight - contentHeight[title];
|
||||
|
||||
return marginBottom > 0 ? marginBottom : 0;
|
||||
}
|
||||
|
||||
return marginBottom;
|
||||
};
|
||||
|
||||
onLayoutContent = (e, title) => {
|
||||
const { contentHeight } = this.state;
|
||||
const contentHeightTmp = { ...contentHeight };
|
||||
contentHeightTmp[title] = e.nativeEvent.layout.height;
|
||||
|
||||
this.setState({
|
||||
contentHeight: { ...contentHeightTmp },
|
||||
});
|
||||
};
|
||||
|
||||
renderContent = (title) => {
|
||||
const marginBottom = Platform.select({ ios: this.calcMargin(title), android: 0 });
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={(e) => this.onLayoutContent(e, title)}
|
||||
style={[styles.content, { marginBottom }]}>
|
||||
{this.renderModal()}
|
||||
<Text style={styles.contentText}>{title}</Text>
|
||||
{this.renderQuizElements(title)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderModal = () => {
|
||||
const { modalVisible, userSelected } = this.state;
|
||||
const { navigation } = this.props;
|
||||
|
||||
return (
|
||||
<Modal animationType="slide" transparent visible={modalVisible} style={styles.modalStyle}>
|
||||
<View style={styles.modalContentContainer}>
|
||||
<UserModal
|
||||
setModalVisible={this.setModalVisible}
|
||||
navigation={navigation}
|
||||
onPressCloseModal={() => this.setModalVisible(false)}
|
||||
user={userSelected}
|
||||
/>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
onRefresh = () => {
|
||||
const wait = (timeout) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, timeout);
|
||||
});
|
||||
|
||||
this.setState({ refreshing: true });
|
||||
|
||||
wait(2000).then(() => {
|
||||
this.setState({ refreshing: false });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { refreshing } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar barStyle="light-content" backgroundColor={colors.primaryGreen} translucent />
|
||||
<StickyParallaxHeader
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
// z Index is required on IOS, to refresh indicator be visible
|
||||
/* eslint-disable-next-line react-native/no-inline-styles */
|
||||
style={{ zIndex: 1 }}
|
||||
refreshing={refreshing}
|
||||
titleColor="white"
|
||||
tintColor="white"
|
||||
title="Refreshing"
|
||||
onRefresh={this.onRefresh}
|
||||
/>
|
||||
}
|
||||
foreground={this.renderForeground()}
|
||||
header={this.renderHeader()}
|
||||
tabs={[
|
||||
{
|
||||
title: 'Popular',
|
||||
content: this.renderContent('Popular Quizes'),
|
||||
},
|
||||
{
|
||||
title: 'Product Design',
|
||||
content: this.renderContent('Product Design'),
|
||||
},
|
||||
{
|
||||
title: 'Development',
|
||||
content: this.renderContent('Development'),
|
||||
},
|
||||
{
|
||||
title: 'Project Management',
|
||||
content: this.renderContent('Project Management'),
|
||||
},
|
||||
]}
|
||||
deviceWidth={constants.deviceWidth}
|
||||
parallaxHeight={sizes.homeScreenParallaxHeader}
|
||||
scrollEvent={event([{ nativeEvent: { contentOffset: { y: this.scrollY.y } } }], {
|
||||
useNativeDriver: false,
|
||||
})}
|
||||
headerSize={this.setHeaderSize}
|
||||
headerHeight={sizes.headerHeight}
|
||||
tabTextStyle={styles.tabText}
|
||||
tabTextContainerStyle={styles.tabTextContainerStyle}
|
||||
tabTextContainerActiveStyle={styles.tabTextContainerActiveStyle}
|
||||
tabsContainerBackgroundColor={colors.primaryGreen}
|
||||
tabsWrapperStyle={styles.tabsWrapper}>
|
||||
{this.renderContent('Popular Quizes')}
|
||||
</StickyParallaxHeader>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,21 @@
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { colors, screenStyles } from '../../constants'
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { colors, screenStyles } from '../../constants';
|
||||
|
||||
export default StyleSheet.create({
|
||||
...screenStyles,
|
||||
logo: {
|
||||
height: 24,
|
||||
width: 142
|
||||
width: 142,
|
||||
},
|
||||
tabsWrapper: {
|
||||
paddingVertical: 12
|
||||
paddingVertical: 12,
|
||||
},
|
||||
tabTextContainerStyle: {
|
||||
backgroundColor: colors.transparent,
|
||||
borderRadius: 18
|
||||
borderRadius: 18,
|
||||
},
|
||||
tabTextContainerActiveStyle: {
|
||||
backgroundColor: colors.darkMint
|
||||
backgroundColor: colors.darkMint,
|
||||
},
|
||||
tabText: {
|
||||
fontSize: 16,
|
||||
@ -23,16 +23,16 @@ export default StyleSheet.create({
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
color: colors.white,
|
||||
fontFamily: 'AvertaStd-Semibold'
|
||||
fontFamily: 'AvertaStd-Semibold',
|
||||
},
|
||||
modalStyle: {
|
||||
margin: 0
|
||||
margin: 0,
|
||||
},
|
||||
modalContentContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.white
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
homeScreenHeader: {
|
||||
backgroundColor: colors.primaryGreen
|
||||
}
|
||||
})
|
||||
backgroundColor: colors.primaryGreen,
|
||||
},
|
||||
});
|
||||
4
examples/expo/src/screens/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import HomeScreen from './HomeScreen/HomeScreen';
|
||||
import CardScreen from './CardScreen/CardScreen';
|
||||
|
||||
export { HomeScreen, CardScreen };
|
||||
7007
examples/expo/yarn.lock
Normal file
3
examples/reactNative/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
metro.config.js
|
||||
|
||||
73
examples/reactNative/.eslintrc.js
Normal file
@ -0,0 +1,73 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
extends: ['prettier', 'airbnb'],
|
||||
plugins: ['react', 'react-native', 'jsx-a11y', 'import', 'prettier'],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
moduleDirectory: ['node_modules', 'app'],
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js'] }],
|
||||
'react/prop-types': ['error', { ignore: ['navigation', 't', 'i18n'] }],
|
||||
'react/jsx-closing-bracket-location': 0,
|
||||
'react/jsx-no-bind': 0,
|
||||
'react/destructuring-assignment': 'warn',
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/require-default-props': 0,
|
||||
'react/prefer-stateless-function': 0,
|
||||
'react/jsx-props-no-spreading': 0,
|
||||
'react-native/no-unused-styles': 2,
|
||||
'react-native/split-platform-components': 2,
|
||||
'react-native/no-inline-styles': 2,
|
||||
'react-native/no-color-literals': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'import/no-cycle': [1, { maxDepth: 1 }],
|
||||
'import/no-unresolved': ['error', { ignore: ['react-native-sticky-parallax-header'] }],
|
||||
'import/no-extraneous-dependencies': ['warn', { packageDir: ['./'] }],
|
||||
'no-unused-vars': [2, { argsIgnorePattern: '^_' }],
|
||||
'no-underscore-dangle': 0,
|
||||
'max-len': [1, 100],
|
||||
'operator-assignment': 'warn',
|
||||
'global-require': 0,
|
||||
'implicit-arrow-linebreak': 0,
|
||||
'prettier/prettier': [
|
||||
'warn',
|
||||
{},
|
||||
{
|
||||
usePrettierrc: true,
|
||||
},
|
||||
],
|
||||
'no-multiple-empty-lines': [
|
||||
'error',
|
||||
{
|
||||
max: 1,
|
||||
maxEOF: 0,
|
||||
maxBOF: 0,
|
||||
},
|
||||
],
|
||||
'operator-linebreak': 0,
|
||||
'comma-dangle': 0,
|
||||
'no-useless-constructor': 0,
|
||||
'object-curly-newline': 0,
|
||||
'prefer-promise-reject-errors': 0,
|
||||
'newline-before-return': 'error',
|
||||
indent: 0,
|
||||
},
|
||||
globals: {
|
||||
fetch: false,
|
||||
shallow: true,
|
||||
mount: true,
|
||||
render: true,
|
||||
navigator: true,
|
||||
__DEV__: true,
|
||||
requestAnimationFrame: true,
|
||||
},
|
||||
};
|
||||