@neovici/cosmoz-rating 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/src/hooks/use-rating.d.ts +9 -0
- package/dist/src/hooks/use-rating.js +95 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/rating.css.d.ts +1 -0
- package/dist/src/rating.css.js +36 -0
- package/dist/src/rating.d.ts +4 -0
- package/dist/src/rating.js +19 -0
- package/dist/types/cosmoz-rating.types.d.ts +12 -0
- package/dist/types/cosmoz-rating.types.js +1 -0
- package/package.json +92 -0
- package/types/cosmoz-rating.types.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CosmozRatingElement } from '../../types/cosmoz-rating.types';
|
|
2
|
+
declare const useRating: (host: CosmozRatingElement) => {
|
|
3
|
+
rating: number | null;
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
maxRating: number;
|
|
6
|
+
handleComponentLeave: () => void;
|
|
7
|
+
renderStar: (index: number) => import("lit-html").TemplateResult<1>;
|
|
8
|
+
};
|
|
9
|
+
export default useRating;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useState, html, useEffect } from '@pionjs/pion';
|
|
2
|
+
import { svg } from 'lit-html';
|
|
3
|
+
const useRating = (host) => {
|
|
4
|
+
const [hoveredRating, setHoveredRating] = useState(null);
|
|
5
|
+
const ratingAttr = host.getAttribute('rating');
|
|
6
|
+
const rating = ratingAttr ? parseFloat(ratingAttr) : null;
|
|
7
|
+
const disabled = host.hasAttribute('disabled');
|
|
8
|
+
const maxRatingAttr = host.getAttribute('max-rating');
|
|
9
|
+
const maxRating = maxRatingAttr ? parseInt(maxRatingAttr, 10) : 5;
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
// Set appropriate tabIndex for accessibility
|
|
12
|
+
if (!disabled && host.tabIndex === -1) {
|
|
13
|
+
host.tabIndex = 0;
|
|
14
|
+
}
|
|
15
|
+
else if (disabled) {
|
|
16
|
+
host.tabIndex = -1;
|
|
17
|
+
}
|
|
18
|
+
}, [disabled]);
|
|
19
|
+
const getStarClass = (starIndex) => {
|
|
20
|
+
const currentRating = hoveredRating ?? rating;
|
|
21
|
+
if (currentRating === null || currentRating === undefined) {
|
|
22
|
+
return 'star';
|
|
23
|
+
}
|
|
24
|
+
if (starIndex <= Math.floor(currentRating)) {
|
|
25
|
+
return 'star filled';
|
|
26
|
+
}
|
|
27
|
+
else if (starIndex === Math.ceil(currentRating) &&
|
|
28
|
+
currentRating % 1 !== 0) {
|
|
29
|
+
return 'star partial';
|
|
30
|
+
}
|
|
31
|
+
return 'star';
|
|
32
|
+
};
|
|
33
|
+
const handleStarClick = (starRating) => {
|
|
34
|
+
if (disabled)
|
|
35
|
+
return;
|
|
36
|
+
host.dispatchEvent(new CustomEvent('change', {
|
|
37
|
+
detail: { rating: starRating },
|
|
38
|
+
bubbles: true,
|
|
39
|
+
composed: true,
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
const handleStarHover = (starRating) => {
|
|
43
|
+
if (disabled)
|
|
44
|
+
return;
|
|
45
|
+
setHoveredRating(starRating);
|
|
46
|
+
};
|
|
47
|
+
const handleComponentLeave = () => {
|
|
48
|
+
if (disabled)
|
|
49
|
+
return;
|
|
50
|
+
setHoveredRating(null);
|
|
51
|
+
};
|
|
52
|
+
const renderStar = (index) => {
|
|
53
|
+
const starRating = index + 1;
|
|
54
|
+
const starClass = getStarClass(starRating);
|
|
55
|
+
const isPartial = starClass.includes('partial');
|
|
56
|
+
let fillPercentage = 0;
|
|
57
|
+
if (isPartial && rating !== null) {
|
|
58
|
+
fillPercentage = Math.round((rating % 1) * 100);
|
|
59
|
+
}
|
|
60
|
+
const starPath = 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z';
|
|
61
|
+
const partialPaths = svg `<defs>
|
|
62
|
+
<clipPath id="clip-${index}">
|
|
63
|
+
<rect x="0" y="0" width="${fillPercentage}%" height="100%" />
|
|
64
|
+
</clipPath>
|
|
65
|
+
</defs>
|
|
66
|
+
<!-- Background (empty) star -->
|
|
67
|
+
<path d="${starPath}" fill="var(--rating-star-color-empty)"></path>
|
|
68
|
+
<!-- Partial fill (clipped) star -->
|
|
69
|
+
<path
|
|
70
|
+
d="${starPath}"
|
|
71
|
+
fill="var(--rating-star-color)"
|
|
72
|
+
clip-path="url(#clip-${index})"
|
|
73
|
+
></path>`;
|
|
74
|
+
return html `
|
|
75
|
+
<svg
|
|
76
|
+
class="${starClass}"
|
|
77
|
+
@click="${() => handleStarClick(starRating)}"
|
|
78
|
+
@mouseenter="${() => handleStarHover(starRating)}"
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
81
|
+
>
|
|
82
|
+
${isPartial
|
|
83
|
+
? partialPaths
|
|
84
|
+
: svg `<path
|
|
85
|
+
d="${starPath}"
|
|
86
|
+
fill="${starClass.includes('filled')
|
|
87
|
+
? 'var(--rating-star-color)'
|
|
88
|
+
: 'var(--rating-star-color-empty)'}"
|
|
89
|
+
></path>`}
|
|
90
|
+
</svg>
|
|
91
|
+
`;
|
|
92
|
+
};
|
|
93
|
+
return { rating, disabled, maxRating, handleComponentLeave, renderStar };
|
|
94
|
+
};
|
|
95
|
+
export default useRating;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rating';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rating';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { css } from '@pionjs/pion';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: inline-block;
|
|
5
|
+
--rating-star-color: #ffd700;
|
|
6
|
+
--rating-star-color-empty: #d3d3d3;
|
|
7
|
+
--rating-star-color-hover: #ffed4a;
|
|
8
|
+
--rating-star-size: 24px;
|
|
9
|
+
--rating-star-gap: 2px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:host([disabled]) {
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.rating-container {
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: var(--rating-star-gap);
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.star {
|
|
23
|
+
width: var(--rating-star-size);
|
|
24
|
+
height: var(--rating-star-size);
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
transition: fill 0.2s ease;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:host([disabled]) .star {
|
|
30
|
+
cursor: default;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.star:hover path {
|
|
34
|
+
color: var(--rating-star-color-hover) !important;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { component, html } from '@pionjs/pion';
|
|
2
|
+
import useRating from './hooks/use-rating';
|
|
3
|
+
import { styles } from './rating.css';
|
|
4
|
+
const Rating = (host) => {
|
|
5
|
+
const { maxRating, renderStar, handleComponentLeave } = useRating(host);
|
|
6
|
+
return html `
|
|
7
|
+
<div class="rating-container" @mouseleave=${handleComponentLeave}>
|
|
8
|
+
${Array.from({ length: maxRating }, (_, index) => renderStar(index))}
|
|
9
|
+
</div>
|
|
10
|
+
`;
|
|
11
|
+
};
|
|
12
|
+
const CosmozRating = component(Rating, {
|
|
13
|
+
observedAttributes: ['rating', 'disabled', 'max-rating'],
|
|
14
|
+
useShadowDOM: true,
|
|
15
|
+
styleSheets: [styles],
|
|
16
|
+
});
|
|
17
|
+
customElements.define('cosmoz-rating', CosmozRating);
|
|
18
|
+
export { CosmozRating };
|
|
19
|
+
export default CosmozRating;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RatingEvent extends CustomEvent {
|
|
2
|
+
detail: {
|
|
3
|
+
rating: number;
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
export interface CosmozRatingProps {
|
|
7
|
+
rating?: number | null;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
maxRating?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CosmozRatingElement extends HTMLElement, CosmozRatingProps {
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neovici/cosmoz-rating",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simple rating web component",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"web-components",
|
|
7
|
+
"rating",
|
|
8
|
+
"stars",
|
|
9
|
+
"pion",
|
|
10
|
+
"lit-html"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/neovici/cosmoz-rating#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/neovici/cosmoz-rating/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/neovici/cosmoz-rating.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"author": "",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"directories": {
|
|
24
|
+
"test": "test"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"lint": "tsc && eslint --cache .",
|
|
28
|
+
"build": "tsc -p tsconfig.build.json",
|
|
29
|
+
"start": "wds",
|
|
30
|
+
"test": "wtr --coverage",
|
|
31
|
+
"test:watch": "wtr --watch",
|
|
32
|
+
"dev": "npm run storybook:start",
|
|
33
|
+
"storybook:start": "storybook dev -p 8000",
|
|
34
|
+
"storybook:build": "storybook build",
|
|
35
|
+
"storybook:deploy": "storybook-to-ghpages",
|
|
36
|
+
"storybook:preview": "npm run storybook:build && http-server -d ./storybook-static/",
|
|
37
|
+
"prepare": "husky"
|
|
38
|
+
},
|
|
39
|
+
"release": {
|
|
40
|
+
"plugins": [
|
|
41
|
+
"@semantic-release/commit-analyzer",
|
|
42
|
+
"@semantic-release/release-notes-generator",
|
|
43
|
+
"@semantic-release/changelog",
|
|
44
|
+
"@semantic-release/github",
|
|
45
|
+
"@semantic-release/npm",
|
|
46
|
+
"@semantic-release/git"
|
|
47
|
+
],
|
|
48
|
+
"branch": "master",
|
|
49
|
+
"preset": "conventionalcommits"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist/",
|
|
56
|
+
"types"
|
|
57
|
+
],
|
|
58
|
+
"exports": {
|
|
59
|
+
".": "./dist/index.js"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@pionjs/pion": "^2.5.2",
|
|
63
|
+
"lit-html": "^3.3.1"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@commitlint/cli": "^19.2.1",
|
|
67
|
+
"@commitlint/config-conventional": "^19.1.0",
|
|
68
|
+
"@eslint/eslintrc": "^2.0.0",
|
|
69
|
+
"@neovici/cfg": "^2.0.0",
|
|
70
|
+
"@open-wc/testing": "^4.0.0",
|
|
71
|
+
"@semantic-release/changelog": "^6.0.0",
|
|
72
|
+
"@semantic-release/git": "^10.0.0",
|
|
73
|
+
"@storybook/addon-essentials": "^7.0.0",
|
|
74
|
+
"@storybook/addon-links": "^7.0.0",
|
|
75
|
+
"@storybook/web-components": "7.6.17",
|
|
76
|
+
"@types/mocha": "^10.0.6",
|
|
77
|
+
"@types/node": "^22.10.2",
|
|
78
|
+
"@web/storybook-builder": "^0.1.9",
|
|
79
|
+
"@web/storybook-framework-web-components": "^0.1.1",
|
|
80
|
+
"esbuild": "^0.24.0",
|
|
81
|
+
"http-server": "^14.1.1",
|
|
82
|
+
"husky": "^9.0.11",
|
|
83
|
+
"rollup-plugin-esbuild": "^6.1.1",
|
|
84
|
+
"semantic-release": "^24.0.0",
|
|
85
|
+
"sinon": "^19.0.0",
|
|
86
|
+
"storybook": "^7.0.0",
|
|
87
|
+
"typescript": "^5.4.3"
|
|
88
|
+
},
|
|
89
|
+
"overrides": {
|
|
90
|
+
"conventional-changelog-conventionalcommits": ">= 8.0.0"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RatingEvent extends CustomEvent {
|
|
2
|
+
detail: {
|
|
3
|
+
rating: number;
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CosmozRatingProps {
|
|
8
|
+
rating?: number | null;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
maxRating?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CosmozRatingElement extends HTMLElement, CosmozRatingProps {}
|