Compare commits

...

139 Commits

Author SHA1 Message Date
Patryk Szczygło
a2bc0abbed
Merge pull request #178 from krunalbad/patch-1
docs: updated readme
2021-02-02 11:38:30 +01:00
maciejBudzinskiNG
61e07d775b
Merge pull request #179 from netguru/BN-2904-single-element-scroll-fix
[BN-2904] Single element horizontal scroll fix
2020-12-08 14:41:06 +01:00
Maciej Budziński
5d9e2eb6bc fix: single element horizontal scroll fix 2020-12-08 11:18:26 +01:00
Krunal Badami
df452e67ad
Update README.md
i found minor readme issue thought i could update it
2020-12-06 19:31:15 +05:30
Piotr Pietruszewski
bd96d8db84
Merge pull request #168 from bobsany16/#155-create_snapshot_tests_DetailsHeader
#155 Create snapshot test for DetailsHeader.js
2020-10-15 08:46:46 +02:00
Linh Nguyen
2de224c686 test: updated DetailsHeader.test.js 2020-10-09 09:52:58 -04:00
Linh Nguyen
85bb638d74 test: created snapshot test for DetailsHeader.js 2020-10-08 22:50:52 -04:00
Patryk Szczygło
a7b25d3b17
Merge pull request #166 from bobsany16/#164-Wrong_Props_AvatarHeader_DetailsHeader
#164 wrong icons propTypes for AvatarHeader and DetailsHeader
2020-10-07 08:49:59 +02:00
Patryk Szczygło
1551661328
Merge pull request #161 from bobsany16/#147-AvatarHeader-missing-props
#147 AvatarHeader is missing props available in other types of header
2020-10-07 08:49:32 +02:00
Linh Nguyen
0acadd3eab fix: changed DetailsHeader icon propTypes 2020-10-02 09:26:25 -04:00
Linh Nguyen
1c83837e21 fix: changed AvatarHeader icon propTypes 2020-10-02 09:25:10 -04:00
Linh Nguyen
94e7d003dd Merge remote-tracking branch 'upstream/master' into #147-AvatarHeader-missing-props 2020-10-02 08:45:35 -04:00
Linh Nguyen
b9e164f399 Merge branch 'master' of https://github.com/netguru/sticky-parallax-header into #147-AvatarHeader-missing-props 2020-10-01 18:53:33 -04:00
maciejBudzinskiNG
64e947eb9a
Merge pull request #159 from netguru/BN-2452_Add_expo_example
[BN-2452] Add expo snack example
2020-10-01 12:05:51 +02:00
Linh Nguyen
1c2d6b285f fix: added missing sharedProps to index.d.ts
added `contentContainerStyles` and `refreshControl` to `SharedProps`
2020-09-30 22:52:05 -04:00
Linh Nguyen
d9c5cc3129 fix: added missing props to AvatarHeader 2020-09-30 22:50:20 -04:00
Maciej Budziński
61440c0369 Merge branch 'master' into BN-2452_Add_expo_example 2020-09-30 15:19:55 +02:00
Maciej Budziński
21a5ebbd29 docs: update docs 2020-09-30 15:14:48 +02:00
Maciej Budziński
87456480be docs: update readme 2020-09-30 15:12:59 +02:00
Maciej Budziński
80018ab8df fix: remove duplicated assets 2020-09-30 12:55:24 +02:00
Maciej Budziński
08601c8974 feat: move assets to the root assets folder 2020-09-30 12:46:23 +02:00
Maciej Budziński
fd195d61b7 refactor: remove unnecessery assets 2020-09-30 12:43:57 +02:00
Piotr Pietruszewski
1c82cea9dd
Merge pull request #151 from netguru/BN-2450-setup_test_env
feat: test env setup
2020-09-29 14:19:03 +02:00
Piotr Pietruszewski
09e00b9ae6 Merge remote-tracking branch 'origin/master' into BN-2450-setup_test_env
# Conflicts:
#	yarn.lock
2020-09-29 14:11:57 +02:00
Piotr Pietruszewski
ab951cb34c
Merge pull request #152 from netguru/release_0.4.0
chore: release 0.4.0
2020-09-29 14:09:42 +02:00
Piotr Pietruszewski
6ab02ddbf3 chore: release 0.4.0 2020-09-29 10:53:30 +02:00
Piotr Pietruszewski
eff27b52e2
Merge pull request #133 from netguru/dependabot/npm_and_yarn/node-fetch-2.6.1
chore(deps): bump node-fetch from 2.6.0 to 2.6.1
2020-09-29 10:32:23 +02:00
Piotr Pietruszewski
362b91d002 Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/node-fetch-2.6.1' into dependabot/npm_and_yarn/node-fetch-2.6.1 2020-09-29 09:13:54 +02:00
dependabot[bot]
5d6d3d6008 chore(deps): bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-29 09:13:49 +02:00
Piotr Pietruszewski
44dd334771 feat: added setup tests file 2020-09-29 08:57:35 +02:00
Piotr Pietruszewski
3e130e0ce0 fix: changed default test name to empty 2020-09-29 08:57:16 +02:00
Piotr Pietruszewski
9506ca2616 fix: extracted jest config to separate file, created prepush script 2020-09-29 08:56:47 +02:00
Piotr Pietruszewski
1cfefe675b feat: test env setup 2020-09-28 16:48:08 +02:00
Piotr Pietruszewski
8d2d0a4459
Merge pull request #150 from yicru/#145-shared_props_type
Fix wrong type definition in TabsSharedProps closes #145
2020-09-28 14:26:07 +02:00
Piotr Pietruszewski
d741491e11
Merge pull request #139 from Relax594/patch-1
Fix wrong rightTopIcon containerstyle
2020-09-28 14:25:02 +02:00
miyamae
f414caaab0 fix: fixed wrong property name "tabText" in README 2020-09-28 18:05:57 +09:00
miyamae
a26bd5bc5a fix: changed TabsSharedProps to optional 2020-09-28 18:04:43 +09:00
Piotr Pietruszewski
abfba3ff6c
Merge pull request #144 from netguru/#138-title_cutt_off
fix: fixed title cut off on larger resolutions
2020-09-28 09:07:36 +02:00
Piotr Pietruszewski
8b025737a0 fix: added responsive font size helper 2020-09-25 12:47:27 +02:00
Piotr Pietruszewski
4deaa99c97
Merge pull request #143 from netguru/#99-tabs_icons
#99 tabs icons
2020-09-25 10:54:41 +02:00
Piotr Pietruszewski
3daad835b1 fix: fixed title cut off on larger resolutions 2020-09-25 10:51:15 +02:00
Piotr Pietruszewski
8c7a6a9274 fix: changed tab title to optional 2020-09-25 09:31:49 +02:00
Piotr Pietruszewski
a752672f09
Merge pull request #136 from netguru/new-issue-template
feat: added issue template
2020-09-25 09:18:39 +02:00
Piotr Pietruszewski
c778745910
Merge pull request #142 from netguru/#125-override_statusbar_docs
docs: added info about overriding statusbar style
2020-09-25 09:14:41 +02:00
Piotr Pietruszewski
9e75a5ff24 docs: added readme info about rendering icons in tabs 2020-09-25 08:58:48 +02:00
Piotr Pietruszewski
5c488149d0 feat: added possibility to render icon in tabs 2020-09-25 08:58:15 +02:00
Piotr Pietruszewski
d8437bc42f docs: added missing pull to refresh tips link 2020-09-25 08:54:53 +02:00
Piotr Pietruszewski
a59e2a6da8 docs: added module version question to issue template 2020-09-24 14:44:12 +02:00
Piotr Pietruszewski
4e84d89320 docs: removed duplicated question from template 2020-09-24 13:39:41 +02:00
Piotr Pietruszewski
169bda6e09
Merge pull request #141 from netguru/#89-refresh_control
feat: added refresh control prop
2020-09-24 13:34:42 +02:00
Piotr Pietruszewski
7e81700ea6 docs: added info about overriding statusbar style 2020-09-24 13:32:04 +02:00
Piotr Pietruszewski
626b64f328 Merge remote-tracking branch 'origin/patch-1' into patch-1 2020-09-24 11:40:50 +02:00
Piotr Pietruszewski
4cc593cb55 fix: eslint module resolver rules fixes 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
fd7e4c57dd fix: added missing metro config and eslintignore 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
3cb56d1be7 fix: module in example app is imported locally 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
5745a5df3e fix: package.json sync 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
9f0b4fb788 fix: expo example refactor, eslint prettier setup 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
244258d185 fix: react native example refactor, eslint prettier setup 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
ac7b51d382 fix: code refactor, eslint settings 2020-09-24 11:39:49 +02:00
Piotr Pietruszewski
93d033d437 fix: formatted code, changed settings of eslint and prettier 2020-09-24 11:39:45 +02:00
Dominik Tenelsen
1eaf30eda7 fix: wrong rightTopIcon containerstyle
the style "styles.rightHeaderBtn" doesn't exist. Has to be "styles.rightHeaderButton".
2020-09-24 11:39:27 +02:00
Piotr Pietruszewski
33a92005a3
Merge branch 'master' into patch-1 2020-09-24 10:46:42 +02:00
Piotr Pietruszewski
2e6e86c11b feat: added refresh control prop 2020-09-24 10:22:06 +02:00
Piotr Pietruszewski
23e5f95ba9
Merge pull request #140 from netguru/BN-2572-eslint-prettier-fix
Bn 2572 eslint prettier fix
2020-09-24 08:29:28 +02:00
Piotr Pietruszewski
edde570912 fix: eslint module resolver rules fixes 2020-09-23 15:31:27 +02:00
Piotr Pietruszewski
17c016cfcf fix: added missing metro config and eslintignore 2020-09-23 15:07:10 +02:00
Piotr Pietruszewski
5dd298b2a5 fix: module in example app is imported locally 2020-09-23 15:05:33 +02:00
Piotr Pietruszewski
26f4e30d39 fix: package.json sync 2020-09-23 10:52:26 +02:00
Piotr Pietruszewski
78560e41a7 fix: expo example refactor, eslint prettier setup 2020-09-23 10:29:00 +02:00
Piotr Pietruszewski
0941373a98 fix: react native example refactor, eslint prettier setup 2020-09-23 09:20:26 +02:00
Piotr Pietruszewski
020a587ac5 fix: code refactor, eslint settings 2020-09-22 15:36:34 +02:00
Dominik Tenelsen
8cdbcb208d
Fix wrong rightTopIcon containerstyle
the style "styles.rightHeaderBtn" doesn't exist. Has to be "styles.rightHeaderButton".
2020-09-22 13:55:38 +02:00
Piotr Pietruszewski
7bc8ec30a5 fix: formatted code, changed settings of eslint and prettier 2020-09-22 13:10:25 +02:00
Piotr Pietruszewski
365c2c7d11
Merge pull request #132 from netguru/BN-2452_Add_expo_example
[BN-2452] Add expo example
2020-09-22 10:33:38 +02:00
Piotr Pietruszewski
d4196b186c
Merge pull request #134 from netguru/#126-scrollview_reference
feat: added body scrollview ref
2020-09-22 08:48:38 +02:00
Piotr Pietruszewski
ef8ac83f3b Merge remote-tracking branch 'origin/master' into #126-scrollview_reference
# Conflicts:
#	README.md
#	src/StickyParallaxHeader.js
#	src/index.d.ts
2020-09-22 08:42:02 +02:00
Piotr Pietruszewski
95f2b99fbb
Merge pull request #135 from netguru/#129-keyboardShouldPersistTabs_prop
feat: added keyboardShouldPersistTaps props
2020-09-22 08:38:48 +02:00
Piotr Pietruszewski
3bc004c8f4 fix: added missing keyboardShouldPersistTaps type for typescript 2020-09-22 08:36:59 +02:00
Piotr Pietruszewski
92c34f30e0 feat: added issue template 2020-09-21 16:08:54 +02:00
Piotr Pietruszewski
7f59e78f87 feat: added more info to readme about scroll view handling 2020-09-21 15:40:16 +02:00
Piotr Pietruszewski
235d1a8efd fix: added object type for useRef value 2020-09-21 15:38:09 +02:00
Piotr Pietruszewski
c2875ea452 fix: added missing space before comma in import 2020-09-21 15:37:44 +02:00
Piotr Pietruszewski
dd00177c16 feat: added keyboardShouldPersistTaps props 2020-09-21 15:02:58 +02:00
Piotr Pietruszewski
158bcceeb7 feat: added body scrollview ref 2020-09-21 14:32:36 +02:00
dependabot[bot]
aa250675b1
chore(deps): bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-12 07:28:19 +00:00
Maciej Budziński
4e94fc31b5 fix: add examples to .npmignore 2020-09-11 09:09:07 +02:00
Maciej Budziński
d7cbe9eab8 feat: add expo example 2020-09-11 08:19:39 +02:00
Natalia
d5f39e71e2
Merge pull request #130 from netguru/bn-2449_prop_types
docs: add shared props
2020-09-07 13:39:47 +02:00
NataliaMuryn
1643e99c92 docs: Fix typo 2020-09-07 12:04:51 +02:00
NataliaMuryn
0dd8befcc1 docs: add shared props 2020-09-06 17:38:55 +02:00
Natalia
e973344d7f
Merge pull request #127 from netguru/BN-2471-add-containerStyles
[BN-2471] add container styles
2020-09-03 12:13:29 +02:00
NataliaMuryn
d5ddd6d27b Remove console.warn 2020-09-03 11:29:22 +02:00
NataliaMuryn
f8686ad3ef docs: add contentContainerStyles to docs 2020-09-03 11:27:34 +02:00
NataliaMuryn
c55cdba695 feat: bump library version in Example app 2020-09-03 11:12:48 +02:00
NataliaMuryn
57b69fb8aa feat: add contentContainerStyles possibility 2020-09-03 11:11:54 +02:00
Piotr Pietruszewski
89b9409d05
Merge pull request #123 from netguru/release_0.3.0
chore: release 0.3.0
2020-08-17 16:11:49 +02:00
Paweł Wysowski
d9fd875f17
Merge pull request #117 from netguru/#113-flatlist-not-scrollable-on-ios
#113 Flatlist not scrollable on iOS
2020-08-17 10:26:03 +02:00
Piotr Pietruszewski
23cdd7248d docs: updated example implementation info 2020-08-17 09:28:18 +02:00
Piotr Pietruszewski
700cdac419
Merge pull request #119 from netguru/#90-scroll-position-on-tab-change
#90 Scroll position on tab change
2020-08-17 09:10:43 +02:00
Piotr Pietruszewski
0247a0ab6f fix: reverted removed commented code 2020-08-17 08:17:20 +02:00
Piotr Pietruszewski
af9268e14d fix: changed scroll enabled condition for flat list in card screen 2020-08-17 08:05:25 +02:00
Piotr Pietruszewski
32582cc9ba fix: removed commented code 2020-08-17 08:04:07 +02:00
Piotr Pietruszewski
5d79cd3f89 fix: replaced platform check with is android in card screen 2020-08-14 13:50:58 +02:00
Piotr Pietruszewski
08c5958fc4 fix: removed unnecessary space in card screen 2020-08-14 13:44:58 +02:00
Piotr Pietruszewski
15578efb73 fix: fixed screen styles formatting 2020-08-14 13:42:59 +02:00
Piotr Pietruszewski
2bba9893cc docs: added info about rememberTabScrollPosition prop 2020-08-07 16:09:32 +02:00
Piotr Pietruszewski
7c2d1776c8 fix: saved position of scroll for tab 2020-08-07 16:09:06 +02:00
Piotr Pietruszewski
4132ca4d9a
Merge pull request #118 from netguru/#63-tab-scroll-view-height-issue
#63 #92 Content is scrollable when one tab is longer
2020-08-07 15:38:51 +02:00
Piotr Pietruszewski
756ea48a23 fix: variable typo minViewportHeight and styling 2020-08-07 14:21:37 +02:00
Piotr Pietruszewski
b585a6a070 fix: moved prevState currentPage to variable 2020-08-07 14:18:02 +02:00
Piotr Pietruszewski
47594b66fb fix: tabs content scrollable when one tab is longer than others 2020-08-07 11:40:45 +02:00
pwysowski
f4d77f44ce docs: updated flatlist implementation info 2020-08-05 12:28:39 +02:00
pwysowski
7bbe85c416 feat: added flatlist example implementation 2020-08-05 10:46:24 +02:00
pwysowski
0f3d0b800b feat: added onTopReached callback 2020-08-05 10:06:40 +02:00
Daniel Idaszak
620a5ef91f
Merge pull request #115 from vitordino/patch-1
docs: change `left bottom border` to `right bottom border`
2020-07-28 13:30:19 +02:00
Vitor Dino
d7e30e6eb9
docs: change left bottom border to right bottom border
at least on the demos it shows `right bottom` border curved, and not `left bottom` one
2020-07-24 16:37:56 -03:00
pwysowski
99a5bc39f5 chore: release 0.3.0 2020-07-02 09:44:10 +02:00
Paweł Wysowski
2e9d88c446
Merge pull request #112 from netguru/contributing-guidelines-update
docs: updated contributing guidelines
2020-07-01 13:48:00 +02:00
Paweł Wysowski
0cbf384b02
Merge pull request #111 from netguru/#104-set-active-page-feature
feat: created onRef property to call goToPage function
2020-07-01 13:37:50 +02:00
Paweł Wysowski
a06b1a4390
docs: updated contributing guidelines 2020-07-01 13:11:55 +02:00
Paweł Wysowski
5251867f25
Merge branch 'master' into #104-set-active-page-feature 2020-07-01 12:25:00 +02:00
pwysowski
44db4f7882 Merge branch 'master' into #104-set-active-page-feature 2020-07-01 12:23:43 +02:00
pwysowski
30adeccf83 docs: updated readme with onRef property 2020-07-01 12:16:15 +02:00
pwysowski
debf4ff8b9 feat: created onRef property to call goToPage function 2020-07-01 11:19:24 +02:00
Paweł Wysowski
afe974f9e3
Merge pull request #98 from netguru/RNS-1621_Add_type_definitions
[RNS-1621] Add type definitions
2020-06-30 14:30:50 +02:00
Paweł Wysowski
577c7d4aa5
Merge branch 'master' into RNS-1621_Add_type_definitions 2020-06-30 14:28:31 +02:00
Paweł Wysowski
ba77e5742a
Merge pull request #109 from netguru/#102-scroll-node-issue-scrollable-tab-view
fix: fixed scrollNode issue inside ScrollableTabView
2020-06-30 10:32:28 +02:00
pwysowski
1b605f0cba refactor: changed scrollNode check also in scrollToTop 2020-06-30 10:27:09 +02:00
Paweł Wysowski
0297e38391
Merge pull request #108 from netguru/#107-image-proptypes-issue
fix: changed all images propTypes to Image.propTypes.source
2020-06-30 10:02:12 +02:00
pwysowski
a4c91834e5 fix: fixed scrollNode issue inside ScrollableTabView 2020-06-30 09:36:08 +02:00
pwysowski
3300eff474 fix: changed all images propTypes to Image.propTypes.source 2020-06-30 08:31:51 +02:00
Paweł Wysowski
1b7b1f512a
Merge pull request #106 from netguru/#102-scroll-node-issue-fix
fix: fixed issue with scrollNode
2020-06-29 14:53:16 +02:00
Maciej Budziński
b70609c279 feat: move index.d.ts file to src directory 2020-06-07 21:39:05 +02:00
Maciej Budziński
87f55cb4a0 fix: change prop name from type to headerType 2020-06-07 21:38:03 +02:00
Maciej Budziński
b827ca6db0 fix: lint issues 2020-06-07 21:18:58 +02:00
Maciej Budziński
cdb421265a feat: clean up import in type declaration file 2020-06-07 21:18:02 +02:00
Maciej Budziński
3e4c65ec77 fix: add type to header props type 2020-06-07 21:16:54 +02:00
Maciej Budziński
e0035433ca feat: update readme with typescript types 2020-06-07 21:04:53 +02:00
Maciej Budziński
9f67f84c49 feat: add type definitions 2020-06-07 19:54:01 +02:00
324 changed files with 26197 additions and 4526 deletions

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
/examples/**
**/metro.config.js
.eslintrc.js
/jest/**

View File

@ -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,
},
};

View File

@ -1,5 +1,5 @@
readme_*
example
examples
node_modules
yarn-error.log
.history

View File

@ -1,6 +1,7 @@
{
"semi": false,
"trailingComma": "none",
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 120
"jsxBracketSameLine": true,
"trailingComma": "es5"
}

161
README.md
View File

@ -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|
| :------: | :------: | :------: |
| ![Tabbed Header Gif](./assets/readme_TabbedHeader.gif) |![Avatar Header Gif](./assets/readme_AvatarHeader.gif)| ![Details Header Gif](./assets/readme_DetailsHeader.gif)|
## 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
![Tabbed Header Gif](./assets/readme_Tabbed.gif)
| 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` &#124; `(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
![Details Header Gif](./assets/readme_Details.gif)
| 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
![Avatar Header Gif](./assets/readme_Avatar.gif)
| 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).

View File

@ -1,3 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
};
};

View File

@ -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/)

View File

@ -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
View 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)

View File

@ -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
}
};

View File

@ -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,
},
}),
},
};

View File

@ -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"
}
}

View File

@ -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 }

View File

@ -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
}

View File

@ -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 }

View File

@ -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
}

View File

@ -1,4 +0,0 @@
import HomeScreen from './HomeScreen/HomeScreen'
import CardScreen from './CardScreen/CardScreen'
export { HomeScreen, CardScreen }

View File

@ -0,0 +1,3 @@
metro.config.js

View 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,
},
};

View File

@ -0,0 +1,4 @@
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}

13
examples/expo/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store

View File

@ -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
View 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
View 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": ""
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View File

Before

Width:  |  Height:  |  Size: 802 B

After

Width:  |  Height:  |  Size: 802 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

View File

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 534 B

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

View File

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 172 B

View File

Before

Width:  |  Height:  |  Size: 256 B

After

Width:  |  Height:  |  Size: 256 B

View File

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

View File

Before

Width:  |  Height:  |  Size: 113 B

After

Width:  |  Height:  |  Size: 113 B

View File

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 199 B

View File

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 283 B

View File

Before

Width:  |  Height:  |  Size: 386 B

After

Width:  |  Height:  |  Size: 386 B

View File

Before

Width:  |  Height:  |  Size: 698 B

After

Width:  |  Height:  |  Size: 698 B

View File

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1005 B

View File

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

View File

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 948 B

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

Before

Width:  |  Height:  |  Size: 242 B

After

Width:  |  Height:  |  Size: 242 B

View File

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 433 B

View File

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 596 B

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 488 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 376 KiB

View File

Before

Width:  |  Height:  |  Size: 714 KiB

After

Width:  |  Height:  |  Size: 714 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 335 KiB

View File

Before

Width:  |  Height:  |  Size: 629 KiB

After

Width:  |  Height:  |  Size: 629 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

View File

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View 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),
},
},
],
],
};
};

View 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

File diff suppressed because it is too large Load Diff

View 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
}

View 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 };

View File

@ -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;

View File

@ -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',
},
});

View File

@ -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;

View File

@ -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,
},
});

View 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;

View File

@ -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',
},
});

View 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;

View File

@ -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,
},
});

View 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 };

View File

@ -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)',
};

View 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,
};

View 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 };

View File

@ -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;

View 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,
},
};

View 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;
}

View 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;

View 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;

View File

@ -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',
},
});

View 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>
</>
);
}
}

View File

@ -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,
},
});

View 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
metro.config.js

View 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,
},
};

Some files were not shown because too many files have changed in this diff Show More