├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── codeql-analysis.yml │ └── main.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── __tests__ ├── App.test.tsx ├── __snapshots__ │ └── App.test.tsx.snap └── setup.js ├── babel.config.js ├── jest.config.js ├── package.json ├── renovate.json ├── samples ├── ReactNativeSample │ ├── .buckconfig │ ├── .eslintrc.js │ ├── .flowconfig │ ├── .gitattributes │ ├── .gitignore │ ├── .watchmanconfig │ ├── App.js │ ├── _editorconfig │ ├── android │ │ ├── app │ │ │ ├── _BUCK │ │ │ ├── build.gradle │ │ │ ├── build_defs.bzl │ │ │ ├── debug.keystore │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── reactnativesample │ │ │ │ │ └── ReactNativeFlipper.java │ │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── reactnativesample │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ │ └── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── app.json │ ├── babel.config.js │ ├── index.js │ ├── ios │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── ReactNativeSample.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── ReactNativeSample.xcscheme │ │ ├── ReactNativeSample.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── ReactNativeSample │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Images.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ ├── LaunchScreen.storyboard │ │ │ └── main.m │ │ └── ReactNativeSampleTests │ │ │ ├── Info.plist │ │ │ └── ReactNativeSampleTests.m │ ├── metro.config.js │ ├── package.json │ └── yarn.lock └── expo-sample │ ├── .expo-shared │ └── assets.json │ ├── App.tsx │ ├── app.json │ ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png │ ├── babel.config.js │ ├── package.json │ ├── tsconfig.json │ └── yarn.lock ├── screenshots ├── Android.png └── iOS.png ├── src ├── TimePicker.tsx ├── index.ts └── utils │ ├── index.ts │ └── zeroPad.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | parser: '@typescript-eslint/parser', 5 | }; 6 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '44 18 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v3 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '14' 18 | cache: 'yarn' 19 | - run: yarn install 20 | 21 | - run: yarn eslint && yarn prettier 22 | 23 | - run: yarn test 24 | 25 | publish: 26 | if: github.event_name == 'push' && github.head_ref == 'master' 27 | needs: [build] 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: actions/setup-node@v3 32 | with: 33 | node-version: '14' 34 | cache: 'yarn' 35 | - run: yarn install 36 | - id: publish 37 | uses: JS-DevTools/npm-publish@v1 38 | with: 39 | token: ${{ secrets.NPM_TOKEN }} 40 | 41 | - if: steps.publish.outputs.type != 'none' 42 | run: | 43 | echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}" 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # Visual Studio Code 33 | # 34 | .vscode/ 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | !debug.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | */fastlane/report.xml 56 | */fastlane/Preview.html 57 | */fastlane/screenshots 58 | 59 | # Bundle artifact 60 | *.jsbundle 61 | 62 | # CocoaPods 63 | /ios/Pods/ 64 | 65 | node_modules/ 66 | .expo/ 67 | npm-debug.* 68 | *.jks 69 | *.p8 70 | *.p12 71 | *.key 72 | *.mobileprovision 73 | *.orig.* 74 | web-build/ 75 | 76 | # macOS 77 | .DS_Store 78 | 79 | **/lib/ 80 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 uraway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | | iOS | Android | 3 | | --- | --- | 4 | | | | 5 | 6 | Simple wrapper component on [@react-native-picker/picker](https://www.npmjs.com/package/@react-native-picker/picker). 7 | 8 | ## Install 9 | 10 | ``` 11 | # React Native 12 | npm install react-native-simple-time-picker @react-native-picker/picker 13 | npx pod-install 14 | 15 | # Expo 16 | expo install react-native-simple-time-picker @react-native-picker/picker 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```tsx 22 | import React from 'react'; 23 | import {TimePicker, ValueMap} from 'react-native-simple-time-picker'; 24 | 25 | const YourApp = () => { 26 | const [value, setValue] = useState({ 27 | hours: 1, 28 | minutes: 0, 29 | seconds: 0, 30 | }); 31 | const handleChange = (newValue: ValueMap) => { 32 | setValue(newValue); 33 | }; 34 | return ; 35 | }; 36 | ``` 37 | 38 | ## Props 39 | 40 | 41 | | Property | Type | Default | Description | 42 | | ---------------- | -------------------------- | ---------------------- | -------------------------------------------------------------------------------------------- | 43 | | value | `{ hours: number, minutes: number, seconds: number, ampm?: 'am' \| 'pm' }` | `{ hours: 0, minutes: 0, seconds: 0 }` | Controlled state | 44 | | defaultValue | `{ hours: number, minutes: number, seconds: number, ampm?: 'am' \| 'pm' }` | `{ hours: 0, minutes: 0, seconds: 0 }` | Controlled state | 45 | | onChange | Function | | Callback function for when values are changed `({ hours: number, minutes: number }) => void` | 46 | | pickerShows | Array | `["hours", "minutes"]` | Pickers to display (`e.g. ["hours", "minutes", "seconds"]`) | 47 | | hoursUnit | String | '' | Hours Unit for label | 48 | | minutesUnit | String | '' | Minutes Unit for label | 49 | | secondsUnit | String | '' | Seconds Unit for label | 50 | | zeroPadding | Boolean | false | Whether to pad numeric labels with zero | 51 | | textColor | String | | Color of the picker item's text | 52 | | hoursInterval | Integer | 1 | | 53 | | minutesInterval | Integer | 1 | | 54 | | secondsInterval | Integer | 1 | | 55 | | emptyLabel | String | undefined | Enable empty option with this label | 56 | | isAmpm | Boolean | false | Whether to display am/pm picker | 57 | | ampmLocalization | { am: string, pm: string } | { am: 'am', pm: 'pm' } | Label for am/pm picker items | 58 | 59 | ## Preview 60 | 61 | https://snack.expo.io/@uraway/react-native-simple-time-picker 62 | 63 | ## LICENSE 64 | 65 | MIT 66 | -------------------------------------------------------------------------------- /__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import toJson from 'enzyme-to-json'; 5 | import {TimePicker} from '../src'; 6 | 7 | const onChange = jest.fn(); 8 | 9 | describe('ReactNativeSimpleTimePicker', () => { 10 | it('should render', () => { 11 | const wrapper = shallow( 12 | , 13 | ); 14 | expect(wrapper.length).toBe(1); 15 | expect(toJson(wrapper)).toMatchSnapshot(); 16 | }); 17 | 18 | it('should render with defaultValue', () => { 19 | const wrapper = shallow( 20 | , 27 | ); 28 | 29 | expect(wrapper.find({testID: 'hoursPicker'}).props().selectedValue).toBe( 30 | 20, 31 | ); 32 | expect(wrapper.find({testID: 'minutesPicker'}).props().selectedValue).toBe( 33 | 10, 34 | ); 35 | expect(wrapper.find({testID: 'secondsPicker'}).props().selectedValue).toBe( 36 | 0, 37 | ); 38 | }); 39 | 40 | it('should render all hours and minutes items', () => { 41 | const wrapper = shallow(); 42 | expect(wrapper.find({testID: 'hoursItem'}).length).toBe(24); 43 | expect(wrapper.find({testID: 'minutesItem'}).length).toBe(60); 44 | expect(wrapper.find({testID: 'secondsItem'}).length).toBe(0); 45 | }); 46 | 47 | it('should render pickers', () => { 48 | const wrapper = shallow(); 49 | expect(wrapper.find({testID: 'hoursPicker'}).isEmptyRender()).toBe(false); 50 | expect(wrapper.find({testID: 'minutesPicker'}).isEmptyRender()).toBe(false); 51 | expect(wrapper.find({testID: 'secondsPicker'}).isEmptyRender()).toBe(true); 52 | expect(wrapper.find({testID: 'ampmPicker'}).isEmptyRender()).toBe(true); 53 | }); 54 | 55 | it('should render pickers with pickerShows prop', () => { 56 | const wrapper = shallow( 57 | , 58 | ); 59 | expect(wrapper.find({testID: 'hoursPicker'}).isEmptyRender()).toBe(false); 60 | expect(wrapper.find({testID: 'minutesPicker'}).isEmptyRender()).toBe(false); 61 | expect(wrapper.find({testID: 'secondsPicker'}).isEmptyRender()).toBe(false); 62 | }); 63 | 64 | it('should render pickers with pickerProps e.g. enabled', () => { 65 | const wrapper = shallow( 66 | , 70 | ); 71 | expect(wrapper.find({testID: 'hoursPicker'}).props().enabled).toBe(false); 72 | expect(wrapper.find({testID: 'minutesPicker'}).props().enabled).toBe(false); 73 | expect(wrapper.find({testID: 'secondsPicker'}).props().enabled).toBe(false); 74 | }); 75 | 76 | it('should render empty slot with emptyLabel empty string', () => { 77 | const wrapper = shallow( 78 | , 82 | ); 83 | expect(wrapper.find({testID: 'hoursItem'}).length).toBe(25); 84 | expect(wrapper.find({testID: 'hoursItem'}).first().props().label).toBe(''); 85 | expect(wrapper.find({testID: 'minutesItem'}).length).toBe(61); 86 | expect(wrapper.find({testID: 'minutesItem'}).first().props().label).toBe( 87 | '', 88 | ); 89 | expect(wrapper.find({testID: 'secondsItem'}).length).toBe(61); 90 | expect(wrapper.find({testID: 'secondsItem'}).first().props().label).toBe( 91 | '', 92 | ); 93 | }); 94 | 95 | it('should not render empty slot with emptyLabel undefined', () => { 96 | const wrapper = shallow( 97 | , 101 | ); 102 | expect(wrapper.find({testID: 'hoursItem'}).length).toBe(24); 103 | expect(wrapper.find({testID: 'hoursItem'}).first().props().label).toBe( 104 | '0 ', 105 | ); 106 | expect(wrapper.find({testID: 'minutesItem'}).length).toBe(60); 107 | expect(wrapper.find({testID: 'minutesItem'}).first().props().label).toBe( 108 | '0 ', 109 | ); 110 | expect(wrapper.find({testID: 'secondsItem'}).length).toBe(60); 111 | expect(wrapper.find({testID: 'secondsItem'}).first().props().label).toBe( 112 | '0 ', 113 | ); 114 | }); 115 | 116 | it('should render all hours, minutes and seconds items with interval props', () => { 117 | const wrapper = shallow( 118 | , 124 | ); 125 | expect(wrapper.find({testID: 'hoursItem'}).length).toBe(5); 126 | expect(wrapper.find({testID: 'minutesItem'}).length).toBe(4); 127 | expect(wrapper.find({testID: 'secondsItem'}).length).toBe(30); 128 | }); 129 | 130 | it('should fail to render with invalid interval prop', () => { 131 | expect(() => 132 | shallow(), 133 | ).toThrowError(); 134 | 135 | expect(() => 136 | shallow(), 137 | ).toThrowError(); 138 | 139 | expect(() => 140 | shallow(), 141 | ).toThrowError(); 142 | }); 143 | 144 | it('should render label with units', () => { 145 | const wrapper = shallow( 146 | , 152 | ); 153 | 154 | expect(wrapper.find({testID: 'hoursItem'}).first().props().label).toBe( 155 | '0 h', 156 | ); 157 | expect(wrapper.find({testID: 'minutesItem'}).first().props().label).toBe( 158 | '0 m', 159 | ); 160 | expect(wrapper.find({testID: 'secondsItem'}).first().props().label).toBe( 161 | '0 s', 162 | ); 163 | }); 164 | 165 | it('should update state', async () => { 166 | const wrapper = shallow( 167 | , 171 | ); 172 | 173 | wrapper.find({testID: 'hoursPicker'}).props().onValueChange(20, 20); 174 | expect(wrapper.find({testID: 'hoursPicker'}).props().selectedValue).toBe( 175 | 20, 176 | ); 177 | expect(onChange).toBeCalledWith({ 178 | hours: 20, 179 | minutes: 0, 180 | seconds: 0, 181 | }); 182 | 183 | wrapper.find({testID: 'minutesPicker'}).props().onValueChange(30, 30); 184 | expect(wrapper.find({testID: 'minutesPicker'}).props().selectedValue).toBe( 185 | 30, 186 | ); 187 | expect(onChange).toBeCalledWith({ 188 | hours: 20, 189 | minutes: 30, 190 | seconds: 0, 191 | }); 192 | 193 | wrapper.find({testID: 'minutesPicker'}).props().onValueChange(5, 5); 194 | expect(wrapper.find({testID: 'minutesPicker'}).props().selectedValue).toBe( 195 | 5, 196 | ); 197 | expect(onChange).toBeCalledWith({ 198 | hours: 20, 199 | minutes: 5, 200 | seconds: 0, 201 | }); 202 | 203 | wrapper.find({testID: 'secondsPicker'}).props().onValueChange(5, 5); 204 | expect(wrapper.find({testID: 'secondsPicker'}).props().selectedValue).toBe( 205 | 5, 206 | ); 207 | expect(onChange).toBeCalledWith({ 208 | hours: 20, 209 | minutes: 5, 210 | seconds: 5, 211 | }); 212 | 213 | wrapper.find({testID: 'hoursPicker'}).props().onValueChange(10, 10); 214 | expect(wrapper.find({testID: 'hoursPicker'}).props().selectedValue).toBe( 215 | 10, 216 | ); 217 | expect(onChange).toBeCalledWith({ 218 | hours: 10, 219 | minutes: 5, 220 | seconds: 5, 221 | }); 222 | }); 223 | 224 | it('should render with zeroPadding', () => { 225 | const wrapper = shallow( 226 | , 227 | ); 228 | 229 | expect(wrapper.find({testID: 'hoursItem'}).first().props().label).toBe( 230 | '00 ', 231 | ); 232 | expect(wrapper.find({testID: 'minutesItem'}).first().props().label).toBe( 233 | '00 ', 234 | ); 235 | expect(wrapper.find({testID: 'secondsItem'}).first().props().label).toBe( 236 | '00 ', 237 | ); 238 | }); 239 | 240 | it('should render pickers with isAmpm', () => { 241 | const wrapper = shallow(); 242 | expect(wrapper.find({testID: 'hoursPicker'}).isEmptyRender()).toBe(false); 243 | expect(wrapper.find({testID: 'minutesPicker'}).isEmptyRender()).toBe(false); 244 | expect(wrapper.find({testID: 'ampmPicker'}).isEmptyRender()).toBe(false); 245 | 246 | expect(wrapper.find({testID: 'hoursItem'}).length).toBe(12); 247 | expect(wrapper.find({testID: 'minutesItem'}).length).toBe(60); 248 | 249 | wrapper.find({testID: 'ampmPicker'}).props().onValueChange('pm', 'pm'); 250 | expect(wrapper.find({testID: 'ampmPicker'}).props().selectedValue).toBe( 251 | 'pm', 252 | ); 253 | 254 | expect(wrapper.find({testID: 'amItem'}).props().label).toBe('am'); 255 | expect(wrapper.find({testID: 'pmItem'}).props().label).toBe('pm'); 256 | }); 257 | 258 | it('should render pickers with isAmpm & ampmLocalization', () => { 259 | const wrapper = shallow( 260 | , 261 | ); 262 | expect(wrapper.find({testID: 'hoursPicker'}).isEmptyRender()).toBe(false); 263 | expect(wrapper.find({testID: 'minutesPicker'}).isEmptyRender()).toBe(false); 264 | expect(wrapper.find({testID: 'ampmPicker'}).isEmptyRender()).toBe(false); 265 | 266 | wrapper.find({testID: 'ampmPicker'}).props().onValueChange('pm', '午後'); 267 | expect(wrapper.find({testID: 'ampmPicker'}).props().selectedValue).toBe( 268 | 'pm', 269 | ); 270 | 271 | expect(wrapper.find({testID: 'amItem'}).props().label).toBe('午前'); 272 | expect(wrapper.find({testID: 'pmItem'}).props().label).toBe('午後'); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/App.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReactNativeSimpleTimePicker should render 1`] = ` 4 | 11 | 22 | 29 | 36 | 43 | 50 | 57 | 64 | 71 | 78 | 85 | 92 | 99 | 106 | 113 | 120 | 127 | 134 | 141 | 148 | 155 | 162 | 169 | 176 | 183 | 190 | 191 | 202 | 209 | 216 | 223 | 230 | 237 | 244 | 251 | 258 | 265 | 272 | 279 | 286 | 293 | 300 | 307 | 314 | 321 | 328 | 335 | 342 | 349 | 356 | 363 | 370 | 377 | 384 | 391 | 398 | 405 | 412 | 419 | 426 | 433 | 440 | 447 | 454 | 461 | 468 | 475 | 482 | 489 | 496 | 503 | 510 | 517 | 524 | 531 | 538 | 545 | 552 | 559 | 566 | 573 | 580 | 587 | 594 | 601 | 608 | 615 | 622 | 623 | 634 | 641 | 648 | 655 | 662 | 669 | 676 | 683 | 690 | 697 | 704 | 711 | 718 | 725 | 732 | 739 | 746 | 753 | 760 | 767 | 774 | 781 | 788 | 795 | 802 | 809 | 816 | 823 | 830 | 837 | 844 | 851 | 858 | 865 | 872 | 879 | 886 | 893 | 900 | 907 | 914 | 921 | 928 | 935 | 942 | 949 | 956 | 963 | 970 | 977 | 984 | 991 | 998 | 1005 | 1012 | 1019 | 1026 | 1033 | 1040 | 1047 | 1054 | 1055 | 1056 | `; 1057 | -------------------------------------------------------------------------------- /__tests__/setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | Enzyme.configure({adapter: new Adapter()}); 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | testRegex: '/__tests__/.*.test.(js|ts|tsx)?$', 4 | transformIgnorePatterns: ['node_modules/?!(@react-native-picker/picker)'], 5 | setupFiles: ['./__tests__/setup.js'], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-simple-time-picker", 3 | "version": "1.3.11", 4 | "private": false, 5 | "license": "MIT", 6 | "author": { 7 | "name": "uraway" 8 | }, 9 | "homepage": "https://github.com/uraway/react-native-simple-time-picker", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/uraway/react-native-simple-time-picker.git" 13 | }, 14 | "keywords": [ 15 | "react-native", 16 | "picker", 17 | "time-picker", 18 | "react-native-picker", 19 | "hours", 20 | "minutes", 21 | "seconds" 22 | ], 23 | "peerDependencies": { 24 | "@react-native-picker/picker": "1 || 2", 25 | "react": "^16.0 || ^17.0.0", 26 | "react-native": ">=0.57" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "7.17.2", 30 | "@babel/runtime": "7.17.2", 31 | "@react-native-community/eslint-config": "3.0.1", 32 | "@react-native-picker/picker": "2.4.0", 33 | "@types/enzyme": "3.10.11", 34 | "@types/jest": "27.4.1", 35 | "@types/react": "17.0.43", 36 | "@types/react-native": "0.67.3", 37 | "@types/react-test-renderer": "17.0.1", 38 | "@typescript-eslint/eslint-plugin": "5.16.0", 39 | "@typescript-eslint/parser": "5.16.0", 40 | "babel-jest": "27.5.1", 41 | "enzyme": "3.11.0", 42 | "enzyme-adapter-react-16": "1.15.6", 43 | "enzyme-to-json": "3.6.2", 44 | "eslint": "8.11.0", 45 | "eslint-plugin-prettier": "4.0.0", 46 | "husky": "7.0.4", 47 | "jest": "27.5.1", 48 | "jsdom": "19.0.0", 49 | "lint-staged": "12.3.7", 50 | "metro-react-native-babel-preset": "0.69.1", 51 | "prettier": "2.6.1", 52 | "react": "17.0.2", 53 | "react-dom": "17.0.2", 54 | "react-native": "0.67.4", 55 | "react-test-renderer": "17.0.2", 56 | "typescript": "4.6.3" 57 | }, 58 | "files": [ 59 | "lib" 60 | ], 61 | "main": "lib/index.js", 62 | "types": "lib/index.d.ts", 63 | "scripts": { 64 | "eslint": "eslint 'src/*{js,tsx}'", 65 | "eslint:fix": "eslint --fix 'src/*{js,tsx}'", 66 | "prettier": "prettier -c 'src/*.{js,tsx}'", 67 | "prettier:fix": "prettier -w 'src/*.{js,tsx}'", 68 | "build": "tsc", 69 | "prepublish": "yarn build", 70 | "start": "tsc --watch", 71 | "test": "jest", 72 | "expo:android": "cd samples/expo-sample && yarn watch & yarn android", 73 | "expo:ios": "cd samples/expo-sample && yarn watch & yarn ios", 74 | "expo:start": "cd samples/expo-sample && yarn watch & yarn start", 75 | "rn:android": "cd samples/ReactNativeSample && yarn watch & yarn android", 76 | "rn:ios": "cd samples/ReactNativeSample && yarn watch & yarn ios", 77 | "rn:start": "cd samples/ReactNativeSample && yarn watch & yarn start" 78 | }, 79 | "jest": { 80 | "preset": "jest-expo" 81 | }, 82 | "dependencies": { 83 | "@react-stately/utils": "3.3.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | }; 5 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; Flow doesn't support platforms 12 | .*/Libraries/Utilities/LoadingView.js 13 | 14 | [untyped] 15 | .*/node_modules/@react-native-community/cli/.*/.* 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/interface.js 21 | node_modules/react-native/flow/ 22 | 23 | [options] 24 | emoji=true 25 | 26 | esproposal.optional_chaining=enable 27 | esproposal.nullish_coalescing=enable 28 | 29 | exact_by_default=true 30 | 31 | module.file_ext=.js 32 | module.file_ext=.json 33 | module.file_ext=.ios.js 34 | 35 | munge_underscores=true 36 | 37 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 38 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 39 | 40 | suppress_type=$FlowIssue 41 | suppress_type=$FlowFixMe 42 | suppress_type=$FlowFixMeProps 43 | suppress_type=$FlowFixMeState 44 | 45 | [lints] 46 | sketchy-null-number=warn 47 | sketchy-null-mixed=warn 48 | sketchy-number=warn 49 | untyped-type-import=warn 50 | nonstrict-import=warn 51 | deprecated-type=warn 52 | unsafe-getters-setters=warn 53 | unnecessary-invariant=warn 54 | signature-verification-failure=warn 55 | 56 | [strict] 57 | deprecated-type 58 | nonstrict-import 59 | sketchy-null 60 | unclear-type 61 | unsafe-getters-setters 62 | untyped-import 63 | untyped-type-import 64 | 65 | [version] 66 | ^0.137.0 67 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | yarn-error.log 37 | 38 | # BUCK 39 | buck-out/ 40 | \.buckd/ 41 | *.keystore 42 | !debug.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | -------------------------------------------------------------------------------- /samples/ReactNativeSample/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /samples/ReactNativeSample/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, View, Button, StyleSheet} from 'react-native'; 3 | import {TimePicker} from 'react-native-simple-time-picker'; 4 | 5 | const YourApp = () => { 6 | const [hours, setHours] = React.useState(0); 7 | const [minutes, setMinutes] = React.useState(0); 8 | const handleChange = value => { 9 | setHours(value.hours); 10 | setMinutes(value.minutes); 11 | }; 12 | const handleReset = () => { 13 | setHours(0); 14 | setMinutes(0); 15 | }; 16 | return ( 17 | 18 | 19 | {hours} : {minutes} 20 | 21 |