Skip to content

Commit

Permalink
Extend stars component (#36)
Browse files Browse the repository at this point in the history
* Add basic star select component

* Add some animations/colors

* Remove color darkening on click and make default grey

* Fix column widths

* Merge stars and ratingsStars into one whahaha

* Add multicolours and remove on hover effects

* Clean up and fix default styling of static stars

* Add a bit more pop

* Fix listing error

* Add fall back styles for i.e.

* Fix extra space

* Update docs and fix multicolours bug

* Fix indentation

* Adjust text and indentation for docs

* Add 0 value

* Refactor color handling

* Don’t add starsGroup padding to 0 input

* Add arrayOf validation

* Convert keys to bools

* Fix incorrect rounding on full stars

* Fix rounding and refactor

* Add new tests

* Change API in docs

* Change docs colors

* Stricter validation

* Latest docs build
  • Loading branch information
kaytcat authored and feychou committed Jan 3, 2017
1 parent eadc802 commit 916bd5d
Show file tree
Hide file tree
Showing 12 changed files with 1,445 additions and 70 deletions.
169 changes: 154 additions & 15 deletions components/Stars/index.jsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,175 @@
import React, {Component, PropTypes} from 'react';

import styles from './index.scss';

function roundToHalves (number) {
return Math.round(number * 2) / 2;
}

function getClassFrom (value) {
return `rating-${roundToHalves(value).toString().replace('.', '-')}`;
function formatValue (number) {
return roundToHalves(Math.round(number * 100) / 100);
}

export default class Stars extends Component {
static propTypes = {
value: PropTypes.number.isRequired
};
colors: PropTypes.arrayOf(PropTypes.string),
name: PropTypes.string,
selectable: PropTypes.bool,
strokeColor: PropTypes.string,
totalStars: (props, propName) => {
const totalStars = props[propName];
if (totalStars < 1 || totalStars > 10) {
return new Error('Prop totalStars must be a number between 1 and 10');
}
return null;
},
value: (props, propName) => {
if (props[propName] > props.totalStars) {
return new Error('Prop value cannot be greater than prop totalStars');
}
return null;
}
}

static defaultProps = {
colors: ['currentColor'],
selectable: false,
strokeColor: 'currentColor',
totalStars: 5,
value: 0
}

state = {
color: this.props.colors[this.props.value - 1] || this.props.strokeColor,
value: formatValue(this.props.value)
}

onClick = e => {
const newVal = Number(e.target.value);

if (newVal === this.state.value) {
return this.deselectStar();
}

this.setCurrentColor(newVal);

return this.updateValue(newVal);
}

setCurrentColor (value) {
this.setState({
color: this.props.colors[value - 1] || this.props.strokeColor
});
}

getFillValue = starNumber => {
const halfStarFill = 'url(#half)';
const emptyStarFill = 'transparent';

if (this.isFullStar(starNumber)) {
return this.state.color;
}

constructor ({value}) {
super();
this.starValue = getClassFrom(value);
this.value = Math.round(value * 100) / 100;
if (this.isHalfStar(starNumber)) {
return halfStarFill;
}

return emptyStarFill;
}

isWholeNumber = number => number % 1 === 0

isFullStar = starNumber => {
if (this.state.value && this.state.value >= starNumber) {
return true;
}

return false;
}

isHalfStar (starNumber) {
if (!this.props.selectable && this.state.value + 1 >= starNumber && !this.isWholeNumber(this.state.value)) {
return true;
}

return false;
}

deselectStar () {
this.updateValue(0);
}

updateValue (newValue) {
this.setState({
value: newValue
});
}

render () {
const {
selectable,
name,
totalStars,
strokeColor
} = this.props;
const {color} = this.state;

return (
<rating className="rating rate-inline">
<div className={this.starValue}>
<span className="sr-only">Rating {this.value}</span>
{[...Array(5)].map((item, key) =>
<i
<div className={`${styles.starsContainer} ${!selectable && styles.staticStars}`}>
<div className={styles.starsRow}>
{[...Array(totalStars + 1)].map((star, key) =>
<div
key={key}
className={`rating-star rating-star-${key + 1} fa`} />
className={Boolean(key) && styles.starsGroup}>

{selectable &&
<input
className={styles.hiddenInput}
type="radio"
name={name}
value={key}
onChange={this.onClick}
id={`star${key}`} /> }

{Boolean(key) &&
<label htmlFor={selectable && `star${key}`}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
xmlSpace="preserve"
viewBox="-20 -20 541.8 518.6"
width="100%"
stroke={strokeColor}
preserveAspectRatio="none"
className={`${styles.star} ${selectable && styles.ratingStar}`}
x="0" y="0">
{!selectable &&
<defs>
<linearGradient id="half">
<stop offset="0%" stopColor={color} />
<stop offset="50%" stopColor={color} />
<stop offset="50%" stopColor="white" />
<stop offset="100%" stopColor="white" />
</linearGradient>
</defs>
}
<path
className={`${styles.path} ${styles[this.isFullStar(key) && 'fill']}`}
fill={this.getFillValue(key)}
stroke={this.isFullStar(key) ? color : strokeColor}
d="M501.8,185.5c0,4.4-2.6,9.3-7.8,14.5L384.5,306.7l25.9,150.8c0.2,1.4,0.3,3.4,0.3,6c0,4.2-1.1,7.8-3.2,10.7
c-2.1,2.9-5.2,4.4-9.2,4.4c-3.8,0-7.8-1.2-12.1-3.6l-135.4-71.2L115.5,475c-4.4,2.4-8.4,3.6-12.1,3.6c-4.2,0-7.4-1.5-9.5-4.4
c-2.1-2.9-3.2-6.5-3.2-10.7c0-1.2,0.2-3.2,0.6-6l25.9-150.8L7.5,200c-5-5.4-7.5-10.3-7.5-14.5c0-7.4,5.6-12.1,16.9-13.9l151.4-22
l67.9-137.2C240,4.1,244.9,0,250.9,0c6,0,11,4.1,14.8,12.4l67.9,137.2l151.4,22C496.2,173.4,501.8,178,501.8,185.5z" />
</svg>
<span className="sr-only">
{key - 1}
</span>
</label> }
</div>
)}
</div>
</rating>
</div>
);
}
}
88 changes: 88 additions & 0 deletions components/Stars/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@import '~kununu-theme/scss/base/variables';

.hiddenInput {
position: absolute;
opacity: 0;
z-index: -1;
}

.hiddenInput:checked + label {
.star {
animation: pulse 1s cubic-bezier(.215, .61, .355, 1);
}
}

.starsContainer {
max-width: 250px;
width: 100%;
display: table;
table-layout: fixed;

@supports (display: flex) {
display: flex;
}

label {
margin: 0;
line-height: 0;
}
}

.staticStars {
max-width: 90px;
}

.starsRow {
display: table-row;

@supports (display: flex) {
display: flex;
}
}

.starsGroup {
display: table-cell;
padding: 0 5px;

@supports (display: flex) {
display: flex;
}
}

.staticStars .starsGroup {
padding: 0 2px;
}

.path {
stroke-linejoin: round;
stroke-width: 40px;
}

.ratingStar {
cursor: pointer;
transition: transform .4s;

&:hover {
transform: scale(1.2);
}

.path {
fill-opacity: 0;
transition: all .2s ease;

&.fill {
fill-opacity: 1;
}
}
}


@keyframes pulse {
0% {
transform: scale(1);
}

17% {
transform: scale(1.3);
}
}
2 changes: 1 addition & 1 deletion docs/app/components/InfoTextDocs/example.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<InfoText
text="I am an info text, commonly used below inputs." />
text="I am an info text." />
2 changes: 1 addition & 1 deletion docs/app/components/InfoTextDocs/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class InfoTextDocs extends Component {

{this.state.infoTextVisible &&
<div>
<InfoText text="I am an info text, commonly used below inputs." />
<InfoText text="I am an info text." />
</div>
}
</div>
Expand Down
9 changes: 9 additions & 0 deletions docs/app/components/StarsDocs/extended-example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Stars
value={2}
selectable
colors={[
'red',
'orange',
'yellow',
'green',
'blue']} />
37 changes: 31 additions & 6 deletions docs/app/components/StarsDocs/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,39 @@ import DocsRoot from 'components/DocsRoot';
import {default as example} from './example.txt';
import {default as propsDefinition} from './props-definition.txt';
import {default as propsDefault} from './props-default.txt';
import {default as extendedExample} from './extended-example.txt';

const StarsDocs = () => (
<DocsRoot
title="Stars"
component={<Stars value={2.5} />}
example={example}
propsDefinition={propsDefinition}
propsDefault={propsDefault} />
<div>
<DocsRoot
title="Static stars"
component={<Stars value={2.5} />}
example={example}
propsDefinition={propsDefinition}
propsDefault={propsDefault} />

<br /><br />
<DocsRoot
title="Selectable stars"
component={
<div className="row">
<div className="col-md-3">
<Stars
value={2}
selectable
colors={[
'#ff464e',
'#fe8e17',
'#fec327',
'#7cb532',
'#96d04a']} />
</div>
</div>
}
example={extendedExample}
propsDefinition={propsDefinition}
propsDefault={propsDefault} />
</div>
);

export default StarsDocs;
8 changes: 7 additions & 1 deletion docs/app/components/StarsDocs/props-default.txt
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
None.
defaultProps: {
colors: ['currentColor'],
selectable: false,
strokeColor: 'currentColor',
totalStars: 5,
value: 0
}
5 changes: 5 additions & 0 deletions docs/app/components/StarsDocs/props-definition.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
propTypes: {
colors: PropTypes.arrayOf(PropTypes.string),
name: PropTypes.string,
selectable: PropTypes.bool,
strokeColor: PropTypes.string,
totalStars: PropTypes.number,
value: PropTypes.number.isRequired
}
51 changes: 26 additions & 25 deletions docs/build/docs.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions playground/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ const App = ({location: {pathname, query}}) => (
<div className="panel">
<div className="panel-body">

<div className="row">
<div className="col-xs-12 col-sm-5 col-md-2">
<Stars
value={3}
name="test"
selectable
colors={['red', 'green']} />
</div>
</div>

<br />

<div className="row">
<div className="col-md-8">
<Logo
Expand Down
Loading

0 comments on commit 916bd5d

Please sign in to comment.