feat: add review

This commit is contained in:
2024-08-12 17:44:58 +02:00
parent 89e819ece1
commit e2bacc283d
43 changed files with 3276 additions and 887 deletions

View File

@@ -0,0 +1,25 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "storybook-static"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}

View File

@@ -0,0 +1,34 @@
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import type { StorybookConfig } from '@storybook/web-components-vite';
import { mergeConfig } from 'vite';
// These options were migrated by @nx/storybook:convert-to-inferred from the project.json file.
const configValues = { default: {}, ci: {} };
// Determine the correct configValue to use based on the configuration
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
const options = {
...configValues.default,
...(configValues[nxConfiguration] ?? {}),
};
const config: StorybookConfig = {
stories: ['../src/lib/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/web-components-vite',
options: {},
},
viteFinal: async (config) =>
mergeConfig(config, {
plugins: [nxViteTsPaths()],
}),
};
export default config;
// To customize your Vite configuration you can use the viteFinal field.
// Check https://storybook.js.org/docs/react/builders/vite#configuration
// and https://nx.dev/recipes/storybook/custom-builder-configs

View File

View File

@@ -0,0 +1,11 @@
# reviews-stars
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build reviews-stars` to build the library.
## Running unit tests
Run `nx test reviews-stars` to execute the unit tests via [Jest](https://jestjs.io).

View File

@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'reviews-stars',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/reviews-stars',
};

View File

@@ -0,0 +1,11 @@
{
"name": "@z-elements/reviews-stars",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"private": true
}

View File

@@ -0,0 +1,64 @@
{
"name": "reviews-stars",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/reviews-stars/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/reviews-stars",
"main": "libs/reviews-stars/src/index.ts",
"tsConfig": "libs/reviews-stars/tsconfig.lib.json",
"assets": ["libs/reviews-stars/*.md"]
}
},
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/reviews-stars/jest.config.ts"
}
},
"storybook": {
"options": {
"port": 4400,
"config-dir": ".storybook"
},
"configurations": {
"ci": {
"args": ["--quiet"]
}
}
},
"build-storybook": {
"outputs": [
"{projectRoot}/{options.output-dir}",
"{workspaceRoot}/{projectRoot}/storybook-static",
"{options.output-dir}",
"{options.outputDir}",
"{options.o}"
],
"options": {
"config-dir": ".storybook",
"output-dir": "../../dist/storybook/reviews-stars"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"test-storybook": {
"executor": "nx:run-commands",
"options": {
"command": "test-storybook -c libs/reviews-stars/.storybook --url=http://localhost:4400"
}
}
}
}

View File

@@ -0,0 +1 @@
export * from './lib/reviews-stars';

View File

@@ -0,0 +1,7 @@
import { reviewsStars } from './reviews-stars';
describe('reviewsStars', () => {
it('should work', () => {
expect(reviewsStars()).toEqual('reviews-stars');
});
});

View File

@@ -0,0 +1,53 @@
import { Meta, StoryObj } from '@storybook/web-components';
import { html } from 'lit';
import './reviews-stars';
import { ReviewsStarsComponent } from './reviews-stars';
const meta: Meta<ReviewsStarsComponent> = {
title: 'Design System/Atoms/Reviews Stars',
component: 'z-reviews.stars',
render: (args) => html`
<z-reviews.stars
?allow-fractional="${args.allowFractional}"
rating="${args.rating}"
out-of="${args.outOf}"
.stars=${args.stars}
></z-reviews.stars>
`,
parameters: {
layout: 'centered',
},
argTypes: {
rating: {
control: 'number',
min: 1,
},
outOf: {
control: {
type: 'number',
min: 1,
},
},
stars: {
control: {
type: 'number',
min: 1,
},
},
allowFractional: {
control: 'boolean',
},
},
args: {
rating: 3,
outOf: 5,
stars: 5,
allowFractional: false,
},
};
export default meta;
export const Primary: StoryObj<ReviewsStarsComponent> = {};

View File

@@ -0,0 +1,140 @@
import { css, CSSResultArray, html, LitElement, TemplateResult } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
/**
* @cssprop --filled-star-color - The color of the filled stars.
* @cssprop --empty-star-color - The color of the empty stars.
*/
@customElement('z-reviews.stars')
export class ReviewsStarsComponent extends LitElement {
/// Private variables ///
/// Protected variables ///
@state() protected _filledStarCount = 0;
/// Public variables ///
@property({ type: Number }) public rating: number | undefined;
@property({ type: Number, attribute: 'out-of' }) public outOf: number | undefined;
@property({ type: Number }) public stars: number = 5;
@property({ type: Boolean, attribute: 'allow-fractional' }) public allowFractional = false;
/// constructor & lifecycle ///
override connectedCallback(): void {
super.connectedCallback();
this.validateProperties();
}
override updated(changedProperties: Map<string | number | symbol, unknown>): void {
super.updated(changedProperties);
this._filledStarCount = this.calculateFilledStarCount();
// this.requestUpdate();
}
/// Public methods ///
/// Protected methods ///
protected override render(): TemplateResult {
return html`
<div class="stars">${this.renderStars()}</div>
<div
class="stars stars--filled"
style="${styleMap({
'--filled-star-count': this._filledStarCount,
'--_filled-star-width': `${(this._filledStarCount / this.stars) * 100}%`,
})}"
>
${this.renderStars()}
</div>
`;
}
protected renderStars(): TemplateResult {
const stars: TemplateResult[] = [];
for (let i = 0; i < this.stars; i++) {
stars.push(this.renderStar());
}
return html`${stars}`;
}
protected renderStar(): TemplateResult {
return html`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="icon">
<!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
<path
d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"
/>
</svg>
</svg>`;
}
/// Private methods ///
/**
* Validate the properties for the rules of availability.
*
* @throws {Error} If the properties are not valid.
*/
private validateProperties(): void {
console.log(this.rating, this.stars, this.outOf, this.allowFractional);
if (!this.rating) {
throw new Error('Rating is required.');
}
if (!this.outOf) {
throw new Error('Out of is required when rating is used.');
}
if (this.stars < 1) {
throw new Error('Stars cannot be less than 1.');
}
if (this.rating! > this.outOf) {
throw new Error('Rating cannot be greater than out of.');
}
}
private calculateFilledStarCount(): number {
if (this.allowFractional) {
return (this.rating! / this.outOf!) * this.stars;
}
return Math.round((this.rating! / this.outOf!) * this.stars);
}
/// Statics ///
static override styles: CSSResultArray = [
css`
:host {
display: flex;
position: relative;
}
.icon {
vertical-align: -0.125em;
flex: 0 0 1em;
height: 1em;
width: 1em;
fill: currentColor;
}
.stars {
display: flex;
color: var(--empty-star-color, #e4e5e9);
}
.stars--filled {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
opacity: 1;
right: calc(100% - var(--_filled-star-width));
color: var(--filled-star-color, #f7b731);
}
`,
];
}

View File

@@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./tsconfig.storybook.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts", "**/*.stories.ts", "**/*.stories.js"]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true
},
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"],
"include": [
"src/**/*.stories.ts",
"src/**/*.stories.js",
"src/**/*.stories.jsx",
"src/**/*.stories.tsx",
"src/**/*.stories.mdx",
".storybook/*.js",
".storybook/*.ts"
]
}