mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-27 19:42:11 +10:00
New form field: star rating
This commit is contained in:
parent
c8f22319f8
commit
993c06f162
7 changed files with 244 additions and 0 deletions
51
src/components/FieldStarRating/FieldStarRating.css
Normal file
51
src/components/FieldStarRating/FieldStarRating.css
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.ratingFieldSet {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* fieldset can't use flexbox hence this wrapper exists */
|
||||
.rating {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.rateInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.star {
|
||||
fill: var(--matterColorNegative);
|
||||
}
|
||||
|
||||
/***** CSS Magic to Highlight Stars on Hover *****/
|
||||
|
||||
/* Star order: reverse expected (5 -> 1) and also input before label */
|
||||
|
||||
/* show actived star when checked */
|
||||
/* and actived star when hovering over a star */
|
||||
/* and show previous stars also as activated */
|
||||
.rating > .rateInput:checked ~ .label > .star,
|
||||
.rating > .label:hover > .star,
|
||||
.rating > .label:hover ~ .label > .star {
|
||||
fill: var(--marketplaceColor);
|
||||
}
|
||||
|
||||
/* Darken hovered star when changing rating i.e it already is active */
|
||||
/* and darken hovered star too when changing rating (hovering inside current selection) */
|
||||
/* and darken current selection inside hovered selection */
|
||||
/* and darken hovered selection inside current selection */
|
||||
.rating > .rateInput:checked + .label:hover > .star,
|
||||
.rating > .rateInput:checked ~ .label:hover > .star,
|
||||
.rating > .label:hover ~ .rateInput:checked ~ .label > .star,
|
||||
.rating > .rateInput:checked ~ .label:hover ~ .label > .star {
|
||||
fill: var(--marketplaceColorDark);
|
||||
}
|
||||
44
src/components/FieldStarRating/FieldStarRating.example.js
Normal file
44
src/components/FieldStarRating/FieldStarRating.example.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* eslint-disable no-console */
|
||||
import React from 'react';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import * as validators from '../../util/validators';
|
||||
import { Button } from '../../components';
|
||||
import FieldStarRating from './FieldStarRating';
|
||||
|
||||
const formName = 'Styleguide.FieldStarRating.Form';
|
||||
|
||||
const FormComponent = props => {
|
||||
const { form, handleSubmit, invalid, pristine, submitting } = props;
|
||||
const required = validators.required('This field is required');
|
||||
const submitDisabled = invalid || pristine || submitting;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FieldStarRating
|
||||
id={`${form}.rate1`}
|
||||
name="rating"
|
||||
label="Rate your experience"
|
||||
validate={required}
|
||||
/>
|
||||
<Button style={{ marginTop: 24 }} type="submit" disabled={submitDisabled}>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
FormComponent.propTypes = formPropTypes;
|
||||
|
||||
const Form = reduxForm({
|
||||
form: formName,
|
||||
})(FormComponent);
|
||||
|
||||
export const StarRating = {
|
||||
component: Form,
|
||||
props: {
|
||||
onSubmit: values => {
|
||||
console.log('submit values:', values);
|
||||
},
|
||||
},
|
||||
group: 'inputs',
|
||||
};
|
||||
140
src/components/FieldStarRating/FieldStarRating.js
Normal file
140
src/components/FieldStarRating/FieldStarRating.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { Field } from 'redux-form';
|
||||
import classNames from 'classnames';
|
||||
import { IconReviewStar, ValidationError } from '../../components';
|
||||
|
||||
import css from './FieldStarRating.css';
|
||||
|
||||
class FieldStarRatingComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.props.clearOnUnmount) {
|
||||
this.props.input.onChange('');
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.props.input.onChange(event.target.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
inputRootClass,
|
||||
clearOnUnmount,
|
||||
customErrorText,
|
||||
id,
|
||||
intl,
|
||||
label,
|
||||
input,
|
||||
meta,
|
||||
...rest
|
||||
} = this.props;
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const { touched, error } = meta;
|
||||
const errorText = customErrorText || error;
|
||||
const fieldMeta = { touched, error: errorText };
|
||||
|
||||
const { value, ...restInputProps } = input;
|
||||
const inputProps = { ...restInputProps, type: 'radio', name: 'rating', ...rest };
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
const createStarRating = starCount => {
|
||||
let inputsAndLabels = [];
|
||||
|
||||
// Star inpu order: reverse order expected (5 -> 1) and also input before label
|
||||
// This is due to CSS selectors.
|
||||
// Sibling combinator (~) selects following siblings, but we want to select previous siblings
|
||||
for (let i = starCount; i > 0; i--) {
|
||||
const inputValue = `${i}`;
|
||||
const starId = `star${i}`;
|
||||
const inputId = `${id}.${starId}`;
|
||||
|
||||
inputsAndLabels.push(
|
||||
<input
|
||||
key={inputId}
|
||||
id={inputId}
|
||||
className={css.rateInput}
|
||||
value={inputValue}
|
||||
checked={value === inputValue}
|
||||
{...inputProps}
|
||||
/>
|
||||
);
|
||||
|
||||
inputsAndLabels.push(
|
||||
<label
|
||||
key={`label.${inputId}`}
|
||||
className={css.label}
|
||||
htmlFor={inputId}
|
||||
title={intl.formatMessage({ id: `FieldStarRating.${starId}` })}
|
||||
>
|
||||
<IconReviewStar rootClassName={css.star} />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
return inputsAndLabels;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<fieldset
|
||||
className={css.ratingFieldSet}
|
||||
ref={c => {
|
||||
this.ratingFieldSet = c;
|
||||
}}
|
||||
>
|
||||
{label ? <legend>{label}</legend> : null}
|
||||
<div className={css.rating}>{createStarRating(5)}</div>
|
||||
</fieldset>
|
||||
<ValidationError fieldMeta={fieldMeta} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FieldStarRatingComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
clearOnUnmount: false,
|
||||
customErrorText: null,
|
||||
label: null,
|
||||
};
|
||||
|
||||
const { string, bool, shape, func, object } = PropTypes;
|
||||
|
||||
FieldStarRatingComponent.propTypes = {
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
clearOnUnmount: bool,
|
||||
id: string.isRequired,
|
||||
label: string,
|
||||
|
||||
// Error message that can be manually passed to input field,
|
||||
// overrides default validation message
|
||||
customErrorText: string,
|
||||
|
||||
// Generated by redux-form's Field component
|
||||
input: shape({
|
||||
onChange: func.isRequired,
|
||||
}).isRequired,
|
||||
meta: object.isRequired,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const FieldStarRating = props => {
|
||||
return <Field component={FieldStarRatingComponent} {...props} />;
|
||||
};
|
||||
|
||||
export default injectIntl(FieldStarRating);
|
||||
|
|
@ -27,6 +27,7 @@ export { default as EditListingWizard } from './EditListingWizard/EditListingWiz
|
|||
export { default as ExpandingTextarea } from './ExpandingTextarea/ExpandingTextarea';
|
||||
export { default as ExternalLink } from './ExternalLink/ExternalLink';
|
||||
export { default as FilterPanel } from './FilterPanel/FilterPanel';
|
||||
export { default as FieldStarRating } from './FieldStarRating/FieldStarRating';
|
||||
export { default as Footer } from './Footer/Footer';
|
||||
export { default as Form } from './Form/Form';
|
||||
export { default as IconBannedUser } from './IconBannedUser/IconBannedUser';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import * as DateInputField from './components/DateInputField/DateInputField.exam
|
|||
import * as DateRangeInputField from './components/DateRangeInputField/DateRangeInputField.example';
|
||||
import * as EditListingWizard from './components/EditListingWizard/EditListingWizard.example';
|
||||
import * as ExpandingTextarea from './components/ExpandingTextarea/ExpandingTextarea.example';
|
||||
import * as FieldStarRating from './components/FieldStarRating/FieldStarRating.example';
|
||||
import * as Footer from './components/Footer/Footer.example';
|
||||
import * as IconBannedUser from './components/IconBannedUser/IconBannedUser.example';
|
||||
import * as IconCheckmark from './components/IconCheckmark/IconCheckmark.example';
|
||||
|
|
@ -82,6 +83,7 @@ export {
|
|||
EditListingWizard,
|
||||
EmailVerificationForm,
|
||||
ExpandingTextarea,
|
||||
FieldStarRating,
|
||||
Footer,
|
||||
IconBannedUser,
|
||||
IconCheckmark,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ ul {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
legend,
|
||||
label {
|
||||
@apply --marketplaceH4FontStyles;
|
||||
font-weight: var(--fontWeightSemiBold);
|
||||
|
|
|
|||
|
|
@ -169,6 +169,11 @@
|
|||
"EmailVerificationForm.verifying": "Verifying…",
|
||||
"EmailVerificationPage.loadingUserInformation": "Loading user information…",
|
||||
"EmailVerificationPage.title": "Verify your email address",
|
||||
"FieldStarRating.star1": "Bad experience - 1 star",
|
||||
"FieldStarRating.star2": "Not so nice - 2 stars",
|
||||
"FieldStarRating.star3": "OK - 3 stars",
|
||||
"FieldStarRating.star4": "Good - 4 stars",
|
||||
"FieldStarRating.star5": "Awesome - 5 stars",
|
||||
"Footer.copyright": "© Sharetribe 2017",
|
||||
"Footer.goToFacebook": "Go to Facebook page",
|
||||
"Footer.goToInstagram": "Go to Instagram page",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue