-
Notifications
You must be signed in to change notification settings - Fork 62
WIP: feature/ratings2 #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
523e4ba
194f9ac
51d6f04
78dffb3
a480d10
8f9ee02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import React, { useState, useEffect } from 'react'; | ||
|
|
||
| export default function HalfStarRating(props: any) { | ||
| const { count, disabler, size, handleLabel } = props; | ||
|
|
||
| const [starValue, setStarValue] = useState(0); | ||
| const [disableHandler, setDisableHandler] = useState(disabler); | ||
| const [disableCss, setDisableCss] = useState(''); | ||
| const [hoverLabel, setHoverLabel] = useState(''); | ||
|
|
||
| const hoverLabels: any = { | ||
| 0.5: 'Useless', | ||
| 1: 'Useless+', | ||
| 1.5: 'Poor', | ||
| 2: 'Poor+', | ||
| 2.5: 'Ok', | ||
| 3: 'Ok+', | ||
| 3.5: 'Good', | ||
| 4: 'Good+', | ||
| 4.5: 'Excellent', | ||
| 5: 'Excellent+', | ||
| }; | ||
|
|
||
| const grabStarValue = (e: any) => { | ||
| setStarValue(e.target.value); | ||
| setHoverLabel(hoverLabels[e.target.value]); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (disableHandler) { | ||
| setDisableCss('disabled '); | ||
| } | ||
| }); | ||
|
|
||
| const customStarCount = () => { | ||
| let starInputs = []; | ||
| let value1 = 0.5; | ||
| let value2 = 1; | ||
| for (let i = 0; i < count; i++) { | ||
| starInputs.push( | ||
| <input | ||
| className={`${disableCss} ${size}`} | ||
| type="radio" | ||
| value={value1++} | ||
| onMouseMove={(e) => grabStarValue(e)} | ||
| name="stars" | ||
| disabled={disableHandler} | ||
| /> | ||
| ); | ||
| starInputs.push( | ||
| <input | ||
| className={`${disableCss} ${size}`} | ||
| type="radio" | ||
| value={value2++} | ||
| onMouseMove={(e) => grabStarValue(e)} | ||
| name="stars" | ||
| disabled={disableHandler} | ||
| /> | ||
| ); | ||
| } | ||
| starInputs.reverse(); | ||
| return starInputs; | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="rating-wrapper"> | ||
| <link | ||
| href="https://unpkg.com/[email protected]/css/boxicons.min.css" | ||
| rel="stylesheet" | ||
| /> | ||
| <div className="rating-star">{customStarCount()}</div> | ||
| <div> | ||
| <label id="stars" htmlFor="stars"> | ||
| {handleLabel ? `${starValue} ${hoverLabel}` : ''} | ||
| </label> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import React from 'react'; | ||
| import Rating from './Rating'; | ||
| import './_Star.scss'; | ||
| import { action } from '@storybook/addon-actions'; | ||
| import HalfStarRating from './HalfStarRating'; | ||
|
|
||
| export default { | ||
| title: 'Rating', | ||
| component: Rating, | ||
| }; | ||
|
|
||
| export const DefaultRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Default Rating</h5> | ||
| <Rating onChange={action('Rating value is')} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const DisabledRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Disabled Rating</h5> | ||
| <Rating disableHandler={true} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const LabeledRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Labeled Rating</h5> | ||
| <Rating hoverLabel={true} /> | ||
| <h5>Custom Labeled Rating</h5> | ||
| <Rating | ||
| customLabel={true} | ||
| customHoverLabel={{ | ||
| 1: 'Custom Hover Label 1', | ||
| 2: 'Custom Hover Label 2', | ||
| 3: 'Custom Hover Label 3', | ||
| 4: 'Custom Hover Label 4', | ||
| 5: 'Custom Hover Label 5', | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const DiffSizeRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Small</h5> | ||
| <Rating starSize="sm" /> | ||
| <h5>Medium</h5> | ||
| <Rating starSize="md" /> | ||
| <h5>Large</h5> | ||
| <Rating starSize="lg" /> | ||
| <h5>Custom Size (width:5rem)</h5> | ||
| <Rating customSize="5rem" /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const StarCountRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Star Count</h5> | ||
| <Rating count={10} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const ControlValueRating = () => { | ||
| return ( | ||
| <div> | ||
| <h5>Control Value Rating</h5> | ||
| <Rating customRatingValue={3} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const HalfStarRatingNOTDONE = () => { | ||
| return ( | ||
| <div> | ||
| <HalfStarRating count={5} disabler={false} handleLabel={true} /> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import React from 'react'; | ||
| import { render } from '@testing-library/react'; | ||
| import Rating from './Rating'; | ||
|
|
||
| describe('Rating', () => { | ||
| it('should match snapshot', () => { | ||
| const { asFragment } = render(<Rating />); | ||
| expect(asFragment()).toMatchSnapshot(); | ||
| }); | ||
|
|
||
| it('should be able to preview the rating by hovering on the stars', () => { | ||
| const { getAllByRole } = render(<Rating />); | ||
| const starInput = getAllByRole('button'); | ||
| expect(starInput.length).toBe(5); | ||
| // all the child elements should in the document | ||
| for (let i = 0; i < starInput.length; i++) { | ||
| expect(starInput[i]).toBeInTheDocument(); | ||
| } | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| import React, { useState, useEffect, FC } from 'react'; | ||
| import StarIcon from './StarIcon'; | ||
|
|
||
| export interface IRatingProps { | ||
| /**set true or false*/ | ||
| disableHandler?: boolean; | ||
| /**set "sm" | "md" | "lg" */ | ||
| starSize?: string; | ||
| /**set custom classname by sending in a prop*/ | ||
| classNames?: string; | ||
| /**set star count by using array [1,2,3,4,5]*/ | ||
| count?: number; | ||
| /**set customized default value of star. Example: 3*/ | ||
| customRatingValue?: number; | ||
| /**set true or false to activate label on hover*/ | ||
| hoverLabel?: boolean; | ||
| /**set custom icon size by sending prop in. Example: "75px" */ | ||
| customSize?: string; | ||
| /**set true or false to activate custom label on hover*/ | ||
| customLabel?: boolean; | ||
| /**set your own custom hover label text*/ | ||
| customHoverLabel?: any; | ||
| /**send addon-action from storybook as a prop */ | ||
| onChange?: () => any; | ||
| } | ||
|
|
||
| export type patRatingProps = IRatingProps; | ||
|
|
||
| export const Rating: FC<patRatingProps> = (props: any) => { | ||
| const { | ||
| disableHandler, | ||
| starSize, | ||
| classNames, | ||
| count, | ||
| customRatingValue, | ||
| hoverLabel, | ||
| customSize, | ||
| onChange, | ||
| customHoverLabel, | ||
| customLabel, | ||
| } = props; | ||
|
|
||
| const [rating, setRating] = useState<number>(customRatingValue); | ||
| const [hoverRating, setHoverRating] = useState<number>(0); | ||
| const [disableCss, setDisableCss] = useState<string>(''); | ||
| const [ratingLabel, setRatingLabel] = useState<string>(''); | ||
| const [customRatingLabel, setCustomRatingLabel] = useState(''); | ||
|
|
||
| //default hover label | ||
| const starLabel: any = { | ||
| 1: 'Useless', | ||
| 2: 'Poor', | ||
| 3: 'OK', | ||
| 4: 'Good', | ||
| 5: 'Excellent', | ||
| }; | ||
|
|
||
| //custom hover labels | ||
| const customStarLabel: any = customHoverLabel; | ||
|
|
||
| //add color on mouse enter | ||
| const highlite = (index: number) => { | ||
| setHoverRating(index); | ||
| }; | ||
|
|
||
| //remove color on mouse leave | ||
| const removeHighlite = () => { | ||
| setHoverRating(0); | ||
| removeHoverLabel(); | ||
| }; | ||
|
|
||
| //save color/rating on click | ||
| const saveRating = (index: number) => { | ||
| setRating(index); | ||
| }; | ||
|
|
||
| //if disabled add disabled css | ||
| useEffect(() => { | ||
| if (disableHandler) { | ||
| setDisableCss('disabled'); | ||
| } | ||
| }); | ||
|
|
||
| //loop through starlabel or customStarLabel object to grab label value | ||
| useEffect(() => { | ||
| if (hoverLabel) { | ||
| for (let i = 0; i < stars().length; i++) { | ||
| if (hoverRating >= stars()[i]) { | ||
| setRatingLabel(starLabel[hoverRating]); | ||
| } | ||
| } | ||
| } | ||
| if (customLabel) { | ||
| for (let i = 0; i < stars().length; i++) { | ||
| if (hoverRating >= stars()[i]) { | ||
| setCustomRatingLabel(customStarLabel[hoverRating]); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| //remove hover label on mouse leave and save current index label | ||
| const removeHoverLabel = () => { | ||
| if (hoverLabel) { | ||
| for (let i = 0; i < stars().length; i++) { | ||
| if (hoverRating >= stars()[i]) { | ||
| setRatingLabel(starLabel[rating]); | ||
| } | ||
| } | ||
| } | ||
| if (customLabel) { | ||
| for (let i = 0; i < stars().length; i++) { | ||
| if (hoverRating >= stars()[i]) { | ||
| setCustomRatingLabel(customStarLabel[rating]); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| //make stars array function | ||
| const stars = () => { | ||
| let starArr = []; | ||
| for (let i = 1; i <= count; i++) { | ||
| starArr.push(i); | ||
| } | ||
| return starArr; | ||
| }; | ||
|
|
||
| //render stars | ||
| const renderStars = stars().map((index: number) => { | ||
| return ( | ||
| <button | ||
| key={index} | ||
| name="star-input" | ||
| id="star-input" | ||
| className={`${classNames} star-btn ${disableCss}`} | ||
| onMouseEnter={() => highlite(index)} | ||
| onMouseLeave={() => { | ||
| removeHighlite(); | ||
| }} | ||
| onClick={() => { | ||
| saveRating(index); | ||
| onChange(index); //storybook action addon | ||
| }} | ||
| disabled={disableHandler} | ||
| value={index} | ||
| role="button" | ||
| > | ||
| <StarIcon | ||
| starSize={starSize} | ||
| hoverRating={hoverRating} | ||
| rating={rating} | ||
| index={index} | ||
| customSize={customSize} | ||
| count={count} | ||
| /> | ||
| </button> | ||
| ); | ||
| }); | ||
|
|
||
| return ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wonderful, so far everything is perfect and full of logic. Only thing we may need is a function to handle precision of the rating (or simply, half star). I believe the use of the <style> element is a good way to implement this requirement. Also, you can use MouseEvent.clientX and Element.getBoundingClientRect() to get the current hovering index of star, which help me a lot and I believe it will help you too.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you I will try that out too. |
||
| <div> | ||
| {renderStars} | ||
| <label htmlFor="star-input"> | ||
| {hoverLabel ? ratingLabel : customLabel ? customRatingLabel : ''} | ||
| </label> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| //default setting | ||
| Rating.defaultProps = { | ||
| count: 5, | ||
| }; | ||
|
|
||
| export default Rating; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like what you did with labels here, but it would be better if you could allow users to customize labels. Maybe we could use prop, maybe something else. Also, I would like to see these labels applied to fraction of stars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok! i am still having some trouble with the half-star logic so I will continue my research until I can find a solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the custom hover label feature without the half star logic for now otherwise it works for the whole stars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, your custom label work perfectly! look forward to merge the half star feature into your component!