diff --git a/README.md b/README.md index c0a2b4d..e78b77a 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,12 @@ [![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/galio-org/galio.svg)](https://github.com/galio-org/galio/pulls?q=is%3Apr+is%3Aclosed) [![Gitter](https://badges.gitter.im/NIT-dgp/General.svg)](https://gitter.im/galio-community) [![npm](https://img.shields.io/npm/dm/galio-framework.svg)](https://www.npmjs.com/package/galio-framework) +[![Backers on Open Collective](https://opencollective.com/galio/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/galio/sponsors/badge.svg)](#sponsors) - - +

+ +

Galio is a 100% free and open source project, licensed under MIT License. You'll be building Android and iOS apps in style. Galio will always remain free to use and it is powered by a massive world-wide community. It comes with a lot of carefully crafted, ready to be used components and a beautiful typography. Galio has a gorgeous base theme that adapts to each project. @@ -17,33 +20,41 @@ Built with real app examples, component demos, guides, and how-to's to get you u ## Table of Contents * [Quick start](#quick-start) * [Components](#components) -* [Examples](#examples) * [Documentation](#documentation) * [Resources](#resources) * [Reporting Issues](#reporting-issues) * [Licensing](#licensing) -* [Useful Links](#useful-links) + +

+ +

## Quick Start #### 1. Project Setup + +Go ahead and install the app version of Galio in order to play around with our components and screens! + ```bash git clone https://github.com/galio-org/galio.git cd galio +git checkout examples npm install or yarn install ``` #### 2. Project testing -Terminal cli: -`npm run ios` or `yarn run ios` +Terminal cli: ```expo start``` + +After initializing your local server you are now able to test the app inside your simulator by running: `npm run ios` or `yarn run ios` (or try an Android simulator) -User our iOS or Android app to directly view Expo projects on your phone. +Use our iOS or Android app directly on your physical device by running it inside Expo! [Expo iOS app](https://itunes.apple.com/us/app/expo-client/id982107779?mt=8) [Expo Android app](https://play.google.com/store/apps/details?id=host.exp.exponent&hl=en) #### 3. SDK library instructions +Use our awesome components inside your own projects by running: ```bash npm install galio-framework ``` @@ -51,7 +62,7 @@ or ```sh yarn add galio-framework ``` -Import UI components to new screens: +Import our UI components to your screens: ```js import { Block, Button, Card, Icon, Input, NavBar, Text } from 'galio-framework'; ``` @@ -60,112 +71,18 @@ import { Block, Button, Card, Icon, Input, NavBar, Text } from 'galio-framework' Under Galio's belt: -:white_check_mark: NavBar - -:white_check_mark: Block - -:white_check_mark: Card - -:white_check_mark: Button - -:white_check_mark: Icon - -:white_check_mark: Input - -:white_check_mark: Text - -:construction: Will follow: :construction: - -TBA - -## Examples - -Here we will showcase some screens and some sample code of how we've used Galio in order to create them. - - - -```js -renderCard = (props, index) => { - const gradientColors = index % 2 ? GRADIENT_PINK : GRADIENT_BLUE; - - return ( - - - - - - - {props.title} - {props.subtitle} - - - - ); -} -``` - - - -```js - - props.navigation.openDrawer()} /> - - - - - 25.2k - - - - 936 - - - {/*...more code in the open source files...*/} - - -``` - - -```js - - this.handleChange('email', text)} - /> - this.handleChange('password', text)} - /> - Alert.alert('Not implemented')} - style={{ alignSelf: 'flex-end', lineHeight: theme.SIZES.FONT * 2 }} - > - Forgot your password? - - -``` +* Block +* Button +* Card +* Checkbox +* Icon +* Input +* NavBar +* Radio +* Slider +* Text +* Switch +* GalioTheme ## Documentation @@ -188,43 +105,38 @@ We use GitHub Issues as the official bug tracker for Galio. Here are some advice ## Contributors -[//]: contributor-faces - - - - - - - - - - - - - - - - - - - - - - - - - - -[//]: contributor-faces +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + + +## Backers + +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/galio#backer)] + + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/galio#sponsor)] + + + + + + + + + + + ## Licensing * Licensed under MIT () -## Useful Links +© 2019 [Galio](https://galio.io), made with 💚 for the community. + -Tutorials: coming soon... -Freebies from Galio Team: coming soon... -© 2018 [Galio](https://galio.io), made with love for apps. diff --git a/package.json b/package.json index bd71553..cec42be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "galio-framework", "main": "src/index.js", - "version": "0.4.4", + "version": "0.5.0", "files": [ "src/" ], @@ -25,7 +25,12 @@ ], "scripts": { "precommit": "yarn lint", - "lint": "eslint src/**.js" + "lint": "eslint src/**.js", + "test": "jest", + "postinstall": "opencollective-postinstall" + }, + "dependencies": { + "opencollective-postinstall": "^2.0.1" }, "devDependencies": { "expo": "^32.0.0", @@ -42,6 +47,10 @@ "react-native": "*", "react-native-vector-icons": "*" }, + "collective": { + "type": "opencollective", + "url": "https://opencollective.com/galio" + }, "eslintConfig": { "extends": "universe/native" } diff --git a/src/Checkbox.js b/src/Checkbox.js new file mode 100644 index 0000000..6627137 --- /dev/null +++ b/src/Checkbox.js @@ -0,0 +1,189 @@ +import React from "react"; +import { View, TouchableOpacity, StyleSheet, Image } from "react-native"; +import PropTypes from "prop-types"; +// galio dependency +import { Icon, Text } from "."; +import GalioTheme, { withGalio } from "./theme"; + +class Checkbox extends React.Component { + constructor(props) { + super(props); + this.state = { + checked: props.initialValue + }; + + this.renderChecked = this.renderChecked.bind(this); + this.spaceAround = this.spaceAround.bind(this); + } + + // adding the necessary margins depending on the flexDirection + spaceAround(direction) { + switch (direction) { + case "row-reverse": + return { marginRight: 10 }; + case "column": + return { marginTop: 10 }; + case "column-reverse": + return { marginBottom: 10 }; + default: + return { marginLeft: 10 }; + } + } + + // rendering the image/text for the checkbox + renderLabel() { + const { + label, + disabled, + flexDirection, + image, + labelStyle, + imageStyle, + styles + } = this.props; + + const labelStyles = [ + styles.textStyles, + disabled && styles.disabledLabel, + labelStyle, + flexDirection && this.spaceAround(flexDirection) + ]; + const imageStyles = [ + styles.imgStyles, + imageStyle, + flexDirection && this.spaceAround(flexDirection) + ]; + + if (image && !label) + return ; + if (!image && label) return {label}; + if (!label && !image) return null; + } + + // adding the check icon + renderChecked() { + const { iconName, iconFamily, iconColor, iconSize } = this.props; + + if (this.state.checked) + return ; + + return null; + } + + // onPress function that changes the component's state and callbacks the onChange prop + _onPress() { + this.setState({ checked: !this.state.checked }, () => + this.props.onChange(this.state.checked) + ); + } + + render() { + const { props, state } = this; + const { style, styles, disabled, flexDirection, checkboxStyle } = props; + + const checkBoxContainerStyle = [ + styles.container, + flexDirection && { flexDirection }, + style + ]; + const checkBoxViewStyles = [ + styles.checkBoxView, + styles.uncheckedBoxView, + state.checked && styles.checked, //apply the ckecked styling + disabled && styles.disabled, + checkboxStyle + ]; + + return ( + this._onPress()} + style={checkBoxContainerStyle} + activeOpacity={0.8} + disabled={disabled} + > + {this.renderChecked()} + {this.renderLabel()} + + ); + } +} + +const styles = theme => + StyleSheet.create({ + container: { + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-start" + }, + checkBoxView: { + width: theme.SIZES.CHECKBOX_WIDTH, + height: theme.SIZES.CHECKBOX_HEIGHT, + borderWidth: theme.SIZES.BORDER_WIDTH, + alignItems: "center", + justifyContent: "center", + borderRadius: theme.SIZES.BORDER_RADIUS + }, + uncheckedBoxView: { + backgroundColor: theme.COLORS.TRANSPARENT, + borderColor: theme.COLORS.GREY + }, + checked: { + backgroundColor: theme.COLORS.THEME, + borderColor: theme.COLORS.THEME + }, + disabled: { + borderColor: theme.COLORS.MUTED, + }, + textStyles: { + color: theme.COLORS.BLACK + }, + disabledLabel: { + color: theme.COLORS.MUTED, + opacity: theme.SIZES.OPACITY + }, + imgStyles: { + width: 200, + height: 200 + } + }); + +Checkbox.defaultProps = { + checkboxStyle: null, + disabled: false, + flexDirection: "row", + iconColor: '#fff', + iconName: "check", + iconSize: 15, + iconFamily: 'FontAwesome', + image: null, + imageStyle: null, + initialValue: false, + label: null, + labelStyle: null, + onChange: () => {}, + styles: {}, + theme: GalioTheme +}; + +Checkbox.propTypes = { + checkboxStyle: PropTypes.any, + disabled: PropTypes.bool, + flexDirection: PropTypes.oneOfType([ + PropTypes.oneOf(["row", "row-reverse", "column", "column-reverse"]), + PropTypes.string + ]), + iconColor: PropTypes.string, + iconName: PropTypes.string, + iconSize: PropTypes.number, + iconFamily: PropTypes.string, + image: PropTypes.string, + imageStyle: PropTypes.any, //style the image + initialValue: PropTypes.bool, + label: PropTypes.string.isRequired, + labelStyle: PropTypes.any, //style the text + onChange: PropTypes.func, + styles: PropTypes.any, //style the whole View element, + theme: PropTypes.any +}; + +export default withGalio(Checkbox, styles); diff --git a/src/Radio.js b/src/Radio.js new file mode 100644 index 0000000..39a831d --- /dev/null +++ b/src/Radio.js @@ -0,0 +1,167 @@ +import React from 'react'; +import { View, TouchableOpacity, StyleSheet } from 'react-native'; +import PropTypes from 'prop-types'; +// G A L I O - D E P E N D E N C Y +import { Text } from '.'; +import GalioTheme, { withGalio } from './theme'; + +class Radio extends React.Component { + constructor(props) { + super(props); + this.state = { + checked: props.initialValue, + }; + + this.spaceAround = this.spaceAround.bind(this); + } + + // A D D I N G - R E Q U I R E D - S P A C E (S) - B A S E D - O N - F L E X - D I R E C T I O N + spaceAround(direction) { + switch (direction) { + case 'row-reverse': + return { marginRight: 10 }; + case 'column': + return { marginTop: 10 }; + case 'column-reverse': + return { marginBottom: 10 }; + default: + return { marginLeft: 10 }; + } + } + + // R E N D E R - L A B E L + renderLabel() { + const { label, disabled, flexDirection, labelStyle, styles } = this.props; + + const labelStyles = [ + styles.textStyles, + disabled && styles.disabledLabel, + labelStyle, + flexDirection && this.spaceAround(flexDirection), + ]; + + if (label) { + return {label}; + } else { + return null; + } + } + + // O N - P R E S S - H A N D L E R + radioPressHandler() { + this.setState({ checked: !this.state.checked }, () => this.props.onChange(this.state.checked)); + } + + render() { + const { props, state } = this; + const { + color, + styles, + disabled, + flexDirection, + containerStyle, + radioOuterStyle, + radioInnerStyle, + theme, + } = props; + + const containerStyles = [styles.container, flexDirection && { flexDirection }, containerStyle]; + + const whichColor = + color && theme.COLORS[color.toUpperCase()] ? theme.COLORS[color.toUpperCase()] : color; + + const radioButtonOuterStyles = [ + styles.radioOuterStyles, + { borderColor: whichColor }, + disabled && styles.disabledRadioOuter, + radioOuterStyle, + ]; + + const radioButtonInnerStyles = [ + styles.radioInnerStyles, + { backgroundColor: whichColor }, + disabled && styles.disabledRadioInner, + radioInnerStyle, + ]; + + return ( + this.radioPressHandler()} + style={containerStyles} + activeOpacity={0.8} + disabled={disabled}> + + {state.checked ? : null} + + {this.renderLabel()} + + ); + } +} + +const styles = theme => + StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }, + radioOuterStyles: { + height: theme.SIZES.RADIO_HEIGHT, + width: theme.SIZES.RADIO_WIDTH, + borderRadius: theme.SIZES.RADIO_HEIGHT * 0.5, + borderWidth: theme.SIZES.RADIO_THICKNESS, + alignItems: 'center', + justifyContent: 'center', + }, + radioInnerStyles: { + height: theme.SIZES.RADIO_HEIGHT * 0.5, + width: theme.SIZES.RADIO_WIDTH * 0.5, + borderRadius: theme.SIZES.RADIO_HEIGHT * 0.25, + }, + disabledRadioOuter: { + borderColor: theme.COLORS.MUTED, + }, + disabledRadioInner: { + backgroundColor: theme.COLORS.MUTED, + }, + textStyles: { + color: theme.COLORS.BLACK, + }, + disabledLabel: { + color: theme.COLORS.MUTED, + opacity: theme.SIZES.OPACITY, + }, + }); + +Radio.defaultProps = { + color: 'primary', + disabled: false, + flexDirection: 'row', + initialValue: false, + label: null, + labelStyle: null, + onChange: () => {}, + styles: {}, + theme: GalioTheme, +}; + +Radio.propTypes = { + color: PropTypes.string, + containerStyle: PropTypes.any, + radioOuterStyle: PropTypes.any, + radioInnerStyle: PropTypes.any, + disabled: PropTypes.bool, + flexDirection: PropTypes.oneOfType([ + PropTypes.oneOf(['row', 'row-reverse', 'column', 'column-reverse']), + PropTypes.string, + ]), + initialValue: PropTypes.bool, + label: PropTypes.string.isRequired, + labelStyle: PropTypes.any, + onChange: PropTypes.func, + styles: PropTypes.any, + theme: PropTypes.any, +}; + +export default withGalio(Radio, styles); diff --git a/src/Slider.js b/src/Slider.js new file mode 100644 index 0000000..d97486b --- /dev/null +++ b/src/Slider.js @@ -0,0 +1,235 @@ +import React, { PureComponent } from "react"; +import { View, Animated, StyleSheet, PanResponder } from "react-native"; +import PropTypes from "prop-types"; +import GalioTheme, { withGalio } from './theme'; + + +class Slider extends PureComponent { + constructor(props) { + super(props); + this.state = { + containerSize: { width: 0, height: 0 }, + trackSize: { width: 0, height: 0 }, + thumbSize: { width: 0, height: 0 }, + measured: false, //hide the UI until we measure the View + }; + + this.position = new Animated.Value(props.value); //recieve value from user + this._panResponder = PanResponder.create({ + onMoveShouldSetPanResponderCapture: () => true, + onPanResponderGrant: (e, gestureState) => { + this._previousLeft = this._getThumbLeft(this._getCurrentVal()); + this._fireChangeEvent('onSlidingStart'); + }, + onPanResponderMove: (e, gestureState) => { + if (props.disabled) { + return; + } + + this._setCurrentValue(this._getValue(gestureState)); + this._fireChangeEvent('onValueChange') + }, + onPanResponderRelease: (e, gestureState) => { + if (props.disabled) { + return; + } + + this._setCurrentValue(this._getValue(gestureState)); + this._fireChangeEvent('onSlidingComplete'); + } + }); + } + + _getRatio = (value) => + (value - this.props.minimumValue) / + (this.props.maximumValue - this.props.minimumValue); + + _getThumbLeft = (value) => this._getRatio(value) * (this.state.containerSize.width - this.state.thumbSize.width) + + _getCurrentVal = () => this.position.__getValue(); + + _setCurrentValue = (value) => this.position.setValue(value); + + _getValue = (gestureState) => { + const length = this.state.containerSize.width - this.state.thumbSize.width; + const thumbLeft = this._previousLeft + gestureState.dx; + + const ratio = thumbLeft / length; + + if (this.props.step) { + return Math.max( + this.props.minimumValue, + Math.min( + this.props.maximumValue, + this.props.minimumValue + + Math.round( + ratio * + (this.props.maximumValue - this.props.minimumValue) / + this.props.step, + ) * + this.props.step, + ), + ); + } + return Math.max( + this.props.minimumValue, + Math.min( + this.props.maximumValue, + ratio * (this.props.maximumValue - this.props.minimumValue) + + this.props.minimumValue, + ), + ); + } + // container size + _measureContainer = x => { + this._handleMeasure("containerSize", x); + }; + // track size + _measureTrack = x => { + this._handleMeasure("trackSize", x); + }; + // thumb size + _measureThumb = x => { + this._handleMeasure("thumbSize", x); + }; + // calculate all of them + _handleMeasure = (name, x) => { + const { width, height } = x.nativeEvent.layout; + const size = { width, height }; + + const storeName = `_${name}`; + const currentSize = this[storeName]; + if ( + currentSize && + width === currentSize.width && + height === currentSize.height + ) { + return; + } + this[storeName] = size; // initialize a new var with the current sizes + if (this._containerSize && this._trackSize && this._thumbSize) { + this.setState({ + containerSize: this._containerSize, + trackSize: this._trackSize, + thumbSize: this._thumbSize, + measured: true + }); + } + }; + + _fireChangeEvent = event => { + if (this.props[event]) { + this.props[event](this._getCurrentVal()); + } + }; + + render() { + const { minimumValue, maximumValue, trackStyle, thumbStyle, activeColor, disabled, theme, styles } = this.props; + const { containerSize, thumbSize, measured } = this.state; + + const thumbLeft = this.position.interpolate({ + inputRange: [minimumValue, maximumValue], + outputRange: [0, containerSize.width - thumbSize.width] + }); + + const minimumTrackWidth = this.position.interpolate({ + inputRange: [minimumValue, maximumValue], + outputRange: [0, containerSize.width - thumbSize.width] + }); + + const visibleStyle = {}; + if (!measured) visibleStyle.opacity = 0; + + const minimumTrackStyle = { + position: 'absolute', + width: Animated.add(minimumTrackWidth, thumbSize.width / 2), + backgroundColor: activeColor || theme.COLORS.PRIMARY, + ...visibleStyle + } + return ( + + + + + + ); + } +} + +Slider.defaultProps = { + disabled: false, + minimumValue: 0, + maximumValue: 100, + trackStyle: {}, + thumbStyle: {}, + value: 0, + disabled: false, + step: 0, + theme: GalioTheme, + onSlidingComplete: () => {}, + onSlidingStart: () => {}, + onValueChange: () => {} +}; + +Slider.propTypes = { + value: PropTypes.number, + disabled: PropTypes.bool, + minimumValue: PropTypes.number, + maximumValue: PropTypes.number, + trackStyle: PropTypes.any, + thumbStyle: PropTypes.any, + disabled: PropTypes.bool, + step: PropTypes.number, + styles: PropTypes.any, + onSlidingComplete: PropTypes.func, + onSlidingStart: PropTypes.func, + onValueChange: PropTypes.func +}; + +const styles = theme => StyleSheet.create({ + container: { + height: 40, + justifyContent: "center" + }, + track: { + height: theme.SIZES.TRACK_SIZE, + width: "100%", + borderRadius: theme.SIZES.TRACK_SIZE / 2, + position: "absolute", + backgroundColor: theme.COLORS.GREY + }, + thumb: { + width: theme.SIZES.THUMB_SIZE, + height: theme.SIZES.THUMB_SIZE, + borderRadius: theme.SIZES.THUMB_SIZE / 2, + borderWidth: 2, + borderColor: theme.COLORS.PRIMARY, + backgroundColor: theme.COLORS.WHITE + }, + disabled: { + backgroundColor: theme.COLORS.MUTED + } +}); + +export default withGalio(Slider, styles); diff --git a/src/Switch.js b/src/Switch.js new file mode 100644 index 0000000..693292a --- /dev/null +++ b/src/Switch.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react'; +import { Switch as Switcher } from 'react-native'; +import PropTypes from 'prop-types'; +import GalioTheme, { withGalio } from './theme'; + +class Switch extends Component { + constructor(props) { + super(props); + this.state = { + switchValue: props.initialValue, + }; + } + + onPressSwitch() { + this.setState({ switchValue: !this.state.switchValue }, () => + this.props.onChange(this.state.switchValue) + ); + } + + render() { + const { initialValue, color, disabled, trackColor, ios_backgroundColor, ...rest } = this.props; + + trackColor.true = color === 'primary' ? GalioTheme.COLORS.PRIMARY : color; + + return ( + { + this.onPressSwitch(); + }} + {...rest} + /> + ); + } +} + +Switch.defaultProps = { + color: GalioTheme.COLORS.PRIMARY, + ios_backgroundColor: GalioTheme.COLORS.GREY, + trackColor: { + false: GalioTheme.COLORS.GREY, + true: GalioTheme.COLORS.PRIMARY, + }, + disabled: false, + initialValue: false, +}; + +Switch.propTypes = { + ...Switcher.propTypes, + color: PropTypes.oneOfType([ + PropTypes.oneOf(['primary', 'theme', 'error', 'warning', 'success', 'info']), + PropTypes.string, + ]), + disabled: PropTypes.bool, + initialValue: PropTypes.bool, +}; + +export default withGalio(Switch); diff --git a/src/index.js b/src/index.js index c1fea04..69c1f4b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,30 @@ import Block from './Block'; import Button from './Button'; import Card from './Card'; +import Checkbox from './Checkbox'; import Icon from './Icon'; import Input from './Input'; import NavBar from './NavBar'; +import Radio from './Radio'; +import Slider from './Slider'; +import Switch from './Switch'; import Text from './Text'; import theme, { withGalio, GalioProvider } from './theme'; -export { Block, Button, Card, Icon, Input, NavBar, Text, theme, withGalio, GalioProvider }; +export { + Block, + Button, + Card, + Checkbox, + Icon, + Input, + NavBar, + Radio, + Slider, + Text, + Switch, + theme, + withGalio, + GalioProvider +}; + diff --git a/src/theme/README.md b/src/theme/README.md index 82579c9..a64dbf5 100644 --- a/src/theme/README.md +++ b/src/theme/README.md @@ -107,4 +107,10 @@ NAVBAR_LEFT_HEIGHT | height * 0.07 | NAVBAR_LEFT_MARGIN | 16 | NAVBAR_RIGHT_FLEX | 0.5 | NAVBAR_RIGHT_HEIGHT | height * 0.07 | -NAVBAR_RIGHT_MARGIN | 16 | \ No newline at end of file +NAVBAR_RIGHT_MARGIN | 16 | +**CHECKBOX** | +CHECKBOX_WIDTH | 20 | +CHECKBOX_HEIGHT | 20 | +**SLIDER** | +TRACK_SIZE | 4 | +THUMB_SIZE | 25 | \ No newline at end of file diff --git a/src/theme/sizes.js b/src/theme/sizes.js index b8ae4c3..d55732f 100644 --- a/src/theme/sizes.js +++ b/src/theme/sizes.js @@ -57,6 +57,20 @@ const SIZES = { NAVBAR_RIGHT_FLEX: 0.5, NAVBAR_RIGHT_HEIGHT: height * 0.07, NAVBAR_RIGHT_MARGIN: 16, + + // Checkbox + CHECKBOX_WIDTH: 20, + CHECKBOX_HEIGHT: 20, + + // Slider + TRACK_SIZE: 4, + THUMB_SIZE: 25, + + // Radio Button + RADIO_WIDTH: 24, + RADIO_HEIGHT: 24, + RADIO_THICKNESS: 2, + }; export default SIZES;