Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
692 changes: 612 additions & 80 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11"
"@fortawesome/react-fontawesome": "^0.1.11",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.9.0"
},
"peerDependencies": {
"react": "^16.8.0",
Expand Down
79 changes: 79 additions & 0 deletions src/components/Ratings/HalfStarRating.tsx
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>
);
}
89 changes: 89 additions & 0 deletions src/components/Ratings/Rating.stories.tsx
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>
);
};
20 changes: 20 additions & 0 deletions src/components/Ratings/Rating.test.tsx
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();
}
});
});
176 changes: 176 additions & 0 deletions src/components/Ratings/Rating.tsx
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 = {
Copy link

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.

Copy link
Author

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.

Copy link
Author

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.

Copy link

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!

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 (
Copy link

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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;
Loading