├── .npmignore ├── CHANGES.md ├── __mocks__ └── styleMock.js ├── src ├── index.js ├── types │ ├── Option.js │ └── CronExpression.js ├── data │ └── constants.js ├── components │ ├── components │ │ ├── Select.js │ │ ├── DateComponent │ │ │ ├── index.js │ │ │ ├── components │ │ │ │ ├── DayOfMonth.js │ │ │ │ ├── Month.js │ │ │ │ └── DayOfWeek.js │ │ │ ├── DateComponent.test.js │ │ │ └── DateComponent.js │ │ ├── TimeInput.js │ │ └── TimeInput.test.js │ ├── types │ │ ├── PresetTabProps.js │ │ └── PresetTabState.js │ ├── MultipleSwitcher.test.js │ ├── Tab.js │ ├── MultipleSwitcher.js │ ├── PresetTab.test.js │ ├── FixedTimeTab.test.js │ ├── FixedTimeTab.js │ ├── PresetTab.js │ ├── PeriodicallyFrameTab.test.js │ ├── PeriodicallyTab.js │ ├── PeriodicallyTab.test.js │ └── PeriodicallyFrameTab.js ├── constants.styl ├── utils │ ├── utils.test.js │ └── index.js ├── stories │ └── CronBuilder.stories.js ├── cron-builder.styl ├── CronBuilder.js └── Cronbuilder.test.js ├── .gitignore ├── storybook-static └── favicon.ico ├── postcss.config.js ├── .babelrc ├── .storybook ├── config.js └── webpack.config.js ├── index.html ├── .flowconfig ├── README.md ├── LICENSE.txt ├── webpack.config.js ├── .eslintrc ├── package.json └── dist ├── bundle.css └── bundle.js /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | coverage -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 2 | fixed bug with clear select -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export {default} from './CronBuilder' 4 | -------------------------------------------------------------------------------- /src/types/Option.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type Option = { 4 | value: string, 5 | label: string 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .storybook/build/storybook_static 2 | package-lock.json 3 | node_modules 4 | coverage 5 | *.log 6 | .idea -------------------------------------------------------------------------------- /storybook-static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/one-more/react-cron-builder/HEAD/storybook-static/favicon.ico -------------------------------------------------------------------------------- /src/data/constants.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const MINUTES = 'MINUTES'; 4 | export const HOURS = 'HOURS'; 5 | export const EVERY = '*'; 6 | -------------------------------------------------------------------------------- /src/components/components/Select.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import 'react-select/dist/react-select.css' 4 | 5 | export {default} from 'react-select' 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({ 4 | browsers: ['> 5%', 'IE > 9'], 5 | cascade: false 6 | }) 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /src/types/CronExpression.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type CronExpression = { 4 | minutes: any, 5 | hours: any, 6 | dayOfWeek: any, 7 | dayOfMonth: any, 8 | month: any 9 | } 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "react" ], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-object-rest-spread", 6 | "transform-class-properties" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/components/types/PresetTabProps.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type {CronExpression} from 'types/CronExpression' 4 | 5 | export type PresetTabProps = { 6 | styleNameFactory: any, 7 | expression: CronExpression 8 | } 9 | -------------------------------------------------------------------------------- /src/constants.styl: -------------------------------------------------------------------------------- 1 | $blue=#3CAED6 2 | $darkBlue=#289CC7 3 | 4 | $darkGrey=#818181 5 | $grey=#cecece 6 | $grey100=#d3d3d3 7 | $lightGrey=#e6e6e6 8 | 9 | $white=#fff 10 | 11 | $black=#303741 12 | 13 | $button-bg=linear-gradient(to bottom, $white, $lightGrey) -------------------------------------------------------------------------------- /src/components/components/DateComponent/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export {default} from './DateComponent' 4 | export {default as DayOfWeek} from './components/DayOfWeek' 5 | export {default as DayOfMonth} from './components/DayOfMonth' 6 | export {default as Month} from './components/Month' 7 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | // automatically import all files ending in *.stories.js 4 | const req = require.context('../src/stories', true, /.stories.js$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /src/components/types/PresetTabState.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type PresetTabState = { 4 | minutes: any, 5 | hours: any, 6 | dayOfWeek: any, 7 | dayOfMonth: any, 8 | month: any, 9 | activeTime: string, 10 | minutesMultiple: boolean, 11 | hoursMultiple: boolean, 12 | hoursFrom?: string, 13 | hoursTo?: string 14 | } 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [options] 2 | esproposal.decorators=ignore 3 | unsafe.enable_getters_and_setters=true 4 | 5 | module.system.node.resolve_dirname=src 6 | module.system.node.resolve_dirname=node_modules 7 | 8 | module.name_mapper='^.*\.styl$' -> 'css-module-flow' 9 | 10 | module.ignore_non_literal_requires=true 11 | 12 | suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore 13 | 14 | [ignore] 15 | .*/stylelint/.* 16 | -------------------------------------------------------------------------------- /src/components/MultipleSwitcher.test.js: -------------------------------------------------------------------------------- 1 | import {mount} from 'enzyme' 2 | import React from 'react' 3 | import MultipleSwitcher from './MultipleSwitcher' 4 | import Tab from './Tab' 5 | 6 | describe('MultipleSwitcher', () => { 7 | const styleNameFactory = jest.fn(); 8 | 9 | it('initial rendering', () => { 10 | const wrapper = mount(); 13 | expect(wrapper.find(Tab)).toHaveLength(2) 14 | }) 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/components/DateComponent/components/DayOfMonth.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import {PureComponent} from 'react' 4 | import {toOptions} from 'utils' 5 | import range from 'lodash/range' 6 | 7 | const options = [ 8 | { 9 | label: 'every month day', 10 | value: '*' 11 | } 12 | ].concat(toOptions(range(1, 32))); 13 | 14 | export default class DayOfMonth extends PureComponent { 15 | static getOptions() { 16 | return options 17 | } 18 | 19 | static className: string = 'DayOfMonth'; 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/utils.test.js: -------------------------------------------------------------------------------- 1 | import {MINUTES, HOURS} from 'data/constants' 2 | import {ensureMultiple, toggleDateType} from './index' 3 | 4 | describe('utils', () => { 5 | it('ensure multiple', () => { 6 | expect(ensureMultiple([1], false)).toEqual(1); 7 | expect(ensureMultiple([1], true)).toEqual([1]); 8 | expect(ensureMultiple(1, true)).toEqual([1]); 9 | expect(ensureMultiple(1, false)).toEqual(1); 10 | }); 11 | 12 | it('toggleDateType', () => { 13 | expect(toggleDateType(MINUTES)).toEqual(HOURS); 14 | expect(toggleDateType(HOURS)).toEqual(MINUTES); 15 | }) 16 | }); 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Cron Builder 2 | React component to build [cron](https://ru.wikipedia.org/wiki/Cron) expression 3 | 4 | ## installation 5 | ```` bash 6 | npm install --save react-cron-builder 7 | ```` 8 | ## demo 9 | [Live demo](https://one-more.github.io/react-cron-builder/) 10 | 11 | ## usage 12 | ```` ecmascript 6 13 | import CronBuilder from 'react-cron-builder 14 | import 'react-cron-builder/dist/bundle.css' 15 | 16 | 21 | ```` 22 | 23 | component was inspired by [this util](https://abunchofutils.com/u/computing/cron-format-helper/) -------------------------------------------------------------------------------- /src/components/components/DateComponent/components/Month.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import {PureComponent} from 'react' 4 | 5 | const monthOptions = [ 6 | 'January', 'February', 'March', 'April', 7 | 'May', 'June', 'July', 'August', 'September', 8 | 'October', 'November', 'December' 9 | ].map((month: string, i: number) => ({ 10 | label: month, 11 | value: String(i + 1) 12 | })); 13 | 14 | const options = [ 15 | { 16 | label: 'every month', 17 | value: '*' 18 | } 19 | ].concat(monthOptions); 20 | 21 | export default class Month extends PureComponent { 22 | static getOptions() { 23 | return options 24 | } 25 | 26 | static className: string = 'Month'; 27 | } 28 | -------------------------------------------------------------------------------- /src/stories/CronBuilder.stories.js: -------------------------------------------------------------------------------- 1 | import {storiesOf, action} from '@storybook/react' 2 | import React from 'react' 3 | import CronBuilder from '../CronBuilder' 4 | 5 | storiesOf('CronBuilder', module) 6 | .add('default', () => ) 9 | .add('do not show result', () => ) 13 | .add('with predefined expression', () => ) 17 | .add('with predefined expression time frame', () => ); 21 | -------------------------------------------------------------------------------- /src/components/Tab.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, {PureComponent} from 'react' 4 | import noop from 'lodash/noop' 5 | 6 | type Props = { 7 | children?: any, 8 | isActive: boolean, 9 | styleNameFactory: any, 10 | onClick: Function 11 | }; 12 | 13 | export default class Tab extends PureComponent { 14 | static defaultProps = { 15 | children: null, 16 | onClick: noop 17 | }; 18 | 19 | props: Props; 20 | 21 | render() { 22 | const {isActive, children, styleNameFactory, onClick} = this.props; 23 | return ( 24 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/components/DateComponent/components/DayOfWeek.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import {PureComponent} from 'react' 4 | 5 | const weekDaysOptions = [ 6 | 'Mondays', 'Tuesdays', 'Wednesdays', 7 | 'Thursdays', 'Fridays', 'Saturdays', 8 | 'Sundays' 9 | ].map((day: string, i: number) => ({ 10 | label: day, 11 | value: String(i + 1) 12 | })); 13 | 14 | const options = [ 15 | { 16 | label: 'every day', 17 | value: '*' 18 | }, 19 | { 20 | label: 'Mondays to Fridays', 21 | value: '1-5' 22 | }, 23 | { 24 | label: 'Saturdays and Sundays', 25 | value: '6-7' 26 | } 27 | ].concat(weekDaysOptions); 28 | 29 | export default class DayOfWeek extends PureComponent { 30 | static getOptions() { 31 | return options 32 | } 33 | 34 | static className: string = 'DayOfWeek'; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/components/TimeInput.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, {PureComponent} from 'react' 4 | import type {Option} from 'types/Option' 5 | import {parseTimeValue, getValues, getValue} from 'utils' 6 | import Select from './Select' 7 | 8 | type Props = { 9 | value: any, 10 | options: Array