From bb533f4e1674bd2c13978c83452469a4c6bdf2e2 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Fri, 6 Dec 2024 11:24:31 +0300 Subject: [PATCH 01/30] fix: add UTF-8 to index.html --- src/index.html | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/index.html b/src/index.html index c8a8e9634..5e6da6851 100644 --- a/src/index.html +++ b/src/index.html @@ -1,11 +1,12 @@ - - My project - - - - -
- + + + + My project + + + +
+ From f60b5402170253a8c70660ca465e8f894c31887f Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Fri, 6 Dec 2024 11:24:49 +0300 Subject: [PATCH 02/30] feat: make about page --- src/app/App.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index dcc0ff8ad..fc8f434ed 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -8,7 +8,14 @@ function App() {
logo

- Текст писать тут + Привет, меня зовут Егор. Я — разработчик интерфейсов. +
+ Пишу на React, Angular с TypeScript. Хочу разобраться с React получше. +
+ Контакт{' '} + + @antytoto +

From 290f15ed3810f2ef35bf9c35148de0821b906210 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Mon, 9 Dec 2024 08:14:00 +0300 Subject: [PATCH 03/30] feat: add deploy script --- package-lock.json | 159 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b1dfa392..053042670 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.2.0", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", @@ -11021,6 +11022,13 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -12511,6 +12519,34 @@ "node": ">=10" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13147,6 +13183,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gh-pages": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.2.0.tgz", + "integrity": "sha512-HMXJ8th9u5wRXaZCnLcs/d3oVvCHiZkaP5KQExQljYGwJjQbSPyTdHe/Gc1IvYUR/rWiZLxNobIqfoMHKTKjHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -21335,6 +21404,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -21964,6 +22046,19 @@ "node": ">=12" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -31513,6 +31608,12 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -32645,6 +32746,23 @@ } } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -33105,6 +33223,29 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.2.0.tgz", + "integrity": "sha512-HMXJ8th9u5wRXaZCnLcs/d3oVvCHiZkaP5KQExQljYGwJjQbSPyTdHe/Gc1IvYUR/rWiZLxNobIqfoMHKTKjHQ==", + "dev": true, + "requires": { + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^11.1.0" + }, + "dependencies": { + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true + } + } + }, "giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -39034,6 +39175,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -39512,6 +39662,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", diff --git a/package.json b/package.json index 492664d1f..0cf02ec1b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react-start-template", + "name": "furtivite.github.io", "version": "1.0.0", "description": "Start repo with required configuration", "main": "src/index.tsx", @@ -10,7 +10,8 @@ "test": "jest src", "lint": "eslint src --fix", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "deploy": "npx gh-pages -d dist" }, "license": "MIT", "devDependencies": { @@ -46,6 +47,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.2.0", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", From 3d3b656eb3aafe9e05e6eb34976b1280d05653ea Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Sun, 15 Dec 2024 12:20:11 +0300 Subject: [PATCH 04/30] feat: type 1_base --- src/homeworks/ts1/{1_base.js => 1_base.ts} | 45 +++++++++++++++------- 1 file changed, 31 insertions(+), 14 deletions(-) rename src/homeworks/ts1/{1_base.js => 1_base.ts} (51%) diff --git a/src/homeworks/ts1/1_base.js b/src/homeworks/ts1/1_base.ts similarity index 51% rename from src/homeworks/ts1/1_base.js rename to src/homeworks/ts1/1_base.ts index 611b3a92f..3be9d4de1 100644 --- a/src/homeworks/ts1/1_base.js +++ b/src/homeworks/ts1/1_base.ts @@ -1,16 +1,16 @@ /** * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения * */ -export const removePlus = (string) => string.replace(/^\+/, ''); +export const removePlus = (string: string): string => string.replace(/^\+/, ''); -export const addPlus = (string) => `+${string}`; +export const addPlus = (string: string): string => `+${string}`; -export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); +export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); -export const getBeautifulNumber = (value, separator = ' ') => +export const getBeautifulNumber = (value?: unknown, separator = ' '): string => value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); -export const round = (value, accuracy = 2) => { +export const round = (value: number, accuracy = 2): number => { const d = 10 ** accuracy; return Math.round(value * d) / d; }; @@ -18,7 +18,12 @@ export const round = (value, accuracy = 2) => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -export const getTransformFromCss = (transformCssString) => { +type TTransform = { + x: number; + y: number; +}; + +export const getTransformFromCss = (transformCssString: string): TTransform => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; return { @@ -27,20 +32,22 @@ export const getTransformFromCss = (transformCssString) => { }; }; -export const getColorContrastValue = ([red, green, blue]) => +export const getColorContrastValue = ([red, green, blue]: [number, number, number]): number => // http://www.w3.org/TR/AERT#color-contrast Math.round((red * 299 + green * 587 + blue * 114) / 1000); -export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); +export const getContrastType = (contrastValue: number): 'black' | 'white' => (contrastValue > 125 ? 'black' : 'white'); export const shortColorRegExp = /^#[0-9a-f]{3}$/i; export const longColorRegExp = /^#[0-9a-f]{6}$/i; -export const checkColor = (color) => { +export const checkColor = (color: string): void | never => { if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); }; -export const hex2rgb = (color) => { +type TColor = [number, number, number]; + +export const hex2rgb = (color: string): TColor => { checkColor(color); if (shortColorRegExp.test(color)) { const red = parseInt(color.substring(1, 2), 16); @@ -54,11 +61,21 @@ export const hex2rgb = (color) => { return [red, green, blue]; }; -export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); +type TSomeArrItem = { value: unknown; number: number }; + +export const getNumberedArray = (arr: TSomeArrItem[]): TSomeArrItem[] => + arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr: TSomeArrItem[]): string[] => arr.map(({ value, number }) => `${value}_${number}`); + +interface ICustomer { + name: string; + age: number; + isSubscribed: boolean; + id?: number; +} -export const transformCustomers = (customers) => { - return customers.reduce((acc, customer) => { +export const transformCustomers = (customers: unknown[]): object | unknown => { + return customers.reduce((acc: ICustomer[], customer: ICustomer): ICustomer[] => { acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; return acc; }, {}); From 9cbe66cf024b6121f0a3740f76919c645922cec4 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Sun, 15 Dec 2024 12:20:45 +0300 Subject: [PATCH 05/30] feat: repair 2_repair --- src/homeworks/ts1/2_repair.ts | 86 +++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 19e98c068..8bee118d1 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -2,46 +2,46 @@ * Здесь код с ошибками типов. Нужно их устранить * */ -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export const getFakeApi = async (): void => { -// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); -// console.log(result); -// }; -// -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export class SomeClass { -// constructor() { -// this.set = new Set([1]); -// this.channel = new BroadcastChannel('test-broadcast-channel'); -// } -// } -// -// export type Data = { -// type: 'Money' | 'Percent'; -// value: DataValue; -// }; -// -// export type DataValue = Money | Percent; -// -// export type Money = { -// currency: string; -// amount: number; -// }; -// -// export type Percent = { -// percent: number; -// }; -// -// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -// const getDataAmount = (data: Data): number => { -// switch (data.type) { -// case 'Money': -// return data.value.amount; -// -// default: { -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться -// throw new Error(`unknown type: ${data.type}`); -// } -// } -// }; +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export const getFakeApi = async (): Promise => { + const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); + console.log(result); +}; + +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export class SomeClass { + set; + channel; + + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } +} + +export type Data = { + type: 'Money' | 'Percent'; + value: DataValue; +}; + +export type DataValue = Money | Percent; + +export type Money = { + currency: string; + amount: number; +}; + +export type Percent = { + percent: number; +}; + +// Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому +const getDataAmount = (data: Data): number | never => { + const { value } = data; + if ('amount' in value) { + return value.amount; + } + + const unhandled: never = data as never; + throw new Error(`unknown type: ${data.type}`); +}; From 05d356a01ffbe329f38f2deaa346e3bfc152265a Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Sun, 15 Dec 2024 12:21:09 +0300 Subject: [PATCH 06/30] feat" create 3_write --- src/homeworks/ts1/3_write.ts | 86 +++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 15f9dcdf2..16bf1677f 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -43,14 +43,96 @@ * - type ('Profit') * */ +interface RequiredData { + id: string; + name: string; +} + +interface Category extends RequiredData { + photo?: string; +} + +interface Product extends RequiredData { + photo: string; + createdAt: string; + price: number; + category: Category; + desc?: string; + oldPrice?: number; +} + +interface OperationRequiredData extends RequiredData { + createdAt: string; + amount: number; + category: Category; + desc?: string; +} + +interface Cost extends OperationRequiredData { + type: 'Cost'; +} + +interface Profit extends OperationRequiredData { + type: 'Profit'; +} + +type Operation = Profit | Cost; + +const randomNumberGenerator = (min: number, max: number): number => Math.floor(Math.random() * (max - min)) + min; +const isTrue = (): boolean => Boolean(Math.round(Math.random())); +const categoryGenerator = (id: number): Category => ({ + id: id.toString(), + name: `Товар #${id}`, + photo: isTrue() ? `assets/category/${id}.png` : undefined, +}); + /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ -// export const createRandomProduct = (createdAt: string) => {}; + +export const createRandomProduct = (createdAt: string): Product => { + const _id = randomNumberGenerator(0, 1000000); + const _price = _id * 1000; + const _oldPrice = _price + 10; + const _catId = randomNumberGenerator(0, 1000000); + + const result: Product = { + id: _id.toString(), + name: `Товар #${_id}`, + photo: `assets/goods/${_id}.png`, + price: _price, + createdAt: createdAt, + category: categoryGenerator(_catId), + desc: !isTrue() ? undefined : `Описание лучшего в мире товара с id ${_id}`, + oldPrice: isTrue() ? _oldPrice : undefined, + }; + + return result; +}; /** * Создает случайную операцию (Operation). * Принимает дату создания (строка) * */ -// export const createRandomOperation = (createdAt: string) => {}; +export const createRandomOperation = (createdAt: string): Operation => { + const _id = randomNumberGenerator(0, 1000000); + const _type = isTrue() ? 'Profit' : 'Cost'; + const _amount = randomNumberGenerator(0, 10); + const _catId = randomNumberGenerator(0, 1000000); + + const result: Operation = { + id: _id.toString(), + name: `Операция #${_id}`, + type: _type, + createdAt: createdAt, + amount: _amount, + category: categoryGenerator(_catId), + desc: !isTrue() ? `Описание операции от ${createdAt}` : undefined, + }; + + return result; +}; + +console.log(createRandomOperation('22-04-2023')); +console.log(createRandomProduct('22-04-2023')); From c748371e96db4e7c92f5558b678310de281b1bcc Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:57:42 +0300 Subject: [PATCH 07/30] feat: create homework 2 (#2) * feat: rename project * feat: create logo * feat: added tailwind * feat: delete custom styles * feat: delete old components from storybook * feat: add Header * feat: add Layout * feat: create Modal * feat: add AddToCart and Counter * feat: update with cdn * feat: add ShortCard * feat: add full card * feat: add BasketGoodsItem and DeleteBtn * feat: edited modal * feat: change deploy rules --- .github/workflows/main.yml | 36 +- .storybook/preview.ts | 5 +- package-lock.json | 1179 +++++++++++++++++--- package.json | 5 + postcss.config.js | 6 + src/assets/goods.ts | 18 + src/components/Basket/BasketGoodsItem.tsx | 21 + src/components/Basket/index.ts | 1 + src/components/Btns/AddToCart.tsx | 22 + src/components/Btns/Btn.tsx | 11 + src/components/Btns/DeleteBtn.tsx | 17 + src/components/Btns/index.ts | 3 + src/components/Btns/interfaces.ts | 10 + src/components/Card/FullCard.tsx | 38 + src/components/Card/ShortCard.tsx | 23 + src/components/Card/index.ts | 2 + src/components/Card/interfaces.ts | 9 + src/components/Counter/Counter.tsx | 26 + src/components/Counter/interfaces.ts | 3 + src/components/Header/Header.tsx | 11 + src/components/Layout/Layout.tsx | 10 + src/components/Layout/interfaces.ts | 5 + src/components/Logo/Logo.tsx | 36 + src/components/Logo/interfaces.ts | 10 + src/components/Modal/Modal.tsx | 24 + src/components/Modal/interfaces.ts | 5 + src/components/StockStatus/StockStatus.tsx | 8 + src/components/StockStatus/interfaces.ts | 3 + src/components/index.tsx | 9 + src/components/interfaces.ts | 7 + src/index.tsx | 1 + src/stories/AddToCart.stories.ts | 31 + src/stories/BasketGoodsItem.stories.ts | 25 + src/stories/Btn.stories.ts | 24 + src/stories/Button.stories.ts | 43 - src/stories/Button.tsx | 42 - src/stories/Counter.stories.ts | 16 + src/stories/DeleteBtn.stories.ts | 24 + src/stories/FullCard.stories.ts | 35 + src/stories/Header.stories.ts | 22 +- src/stories/Header.tsx | 49 - src/stories/Layout.stories.ts | 16 + src/stories/Logo.stories.ts | 32 + src/stories/Modal.stories.ts | 24 + src/stories/Page.stories.ts | 29 - src/stories/Page.tsx | 70 -- src/stories/ShortCard.stories.ts | 35 + src/stories/StockStatus.stories.ts | 22 + src/stories/button.module.sass | 30 - src/stories/header.css | 32 - src/stories/page.css | 69 -- src/tailwind.css | 3 + tailwind.config.js | 24 + webpack.config.js | 13 +- 54 files changed, 1696 insertions(+), 578 deletions(-) create mode 100644 postcss.config.js create mode 100644 src/assets/goods.ts create mode 100644 src/components/Basket/BasketGoodsItem.tsx create mode 100644 src/components/Basket/index.ts create mode 100644 src/components/Btns/AddToCart.tsx create mode 100644 src/components/Btns/Btn.tsx create mode 100644 src/components/Btns/DeleteBtn.tsx create mode 100644 src/components/Btns/index.ts create mode 100644 src/components/Btns/interfaces.ts create mode 100644 src/components/Card/FullCard.tsx create mode 100644 src/components/Card/ShortCard.tsx create mode 100644 src/components/Card/index.ts create mode 100644 src/components/Card/interfaces.ts create mode 100644 src/components/Counter/Counter.tsx create mode 100644 src/components/Counter/interfaces.ts create mode 100644 src/components/Header/Header.tsx create mode 100644 src/components/Layout/Layout.tsx create mode 100644 src/components/Layout/interfaces.ts create mode 100644 src/components/Logo/Logo.tsx create mode 100644 src/components/Logo/interfaces.ts create mode 100644 src/components/Modal/Modal.tsx create mode 100644 src/components/Modal/interfaces.ts create mode 100644 src/components/StockStatus/StockStatus.tsx create mode 100644 src/components/StockStatus/interfaces.ts create mode 100644 src/components/index.tsx create mode 100644 src/components/interfaces.ts create mode 100644 src/stories/AddToCart.stories.ts create mode 100644 src/stories/BasketGoodsItem.stories.ts create mode 100644 src/stories/Btn.stories.ts delete mode 100644 src/stories/Button.stories.ts delete mode 100644 src/stories/Button.tsx create mode 100644 src/stories/Counter.stories.ts create mode 100644 src/stories/DeleteBtn.stories.ts create mode 100644 src/stories/FullCard.stories.ts delete mode 100644 src/stories/Header.tsx create mode 100644 src/stories/Layout.stories.ts create mode 100644 src/stories/Logo.stories.ts create mode 100644 src/stories/Modal.stories.ts delete mode 100644 src/stories/Page.stories.ts delete mode 100644 src/stories/Page.tsx create mode 100644 src/stories/ShortCard.stories.ts create mode 100644 src/stories/StockStatus.stories.ts delete mode 100644 src/stories/button.module.sass delete mode 100644 src/stories/header.css delete mode 100644 src/stories/page.css create mode 100644 src/tailwind.css create mode 100644 tailwind.config.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f5ba90b0..54bcf3b38 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,28 +34,28 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # Собираем приложение - - name: Build Application - run: npm run build + # # Собираем приложение + # - name: Build Application + # run: npm run build - # Публикуем приложение на Github Pages - - name: Deploy to Github Pages - uses: JamesIves/github-pages-deploy-action@4.2.1 - with: - branch: gh-pages - folder: dist - - # # Собираем Storybook - # - name: Build Storybook - # run: npm run build-storybook - # - # # Публикуем Storybook на Github Pages - # - name: Deploy Storybook to Github Pages + # # Публикуем приложение на Github Pages + # - name: Deploy to Github Pages # uses: JamesIves/github-pages-deploy-action@4.2.1 # with: # branch: gh-pages - # folder: storybook-static - # commit-message: "Automatically publish Storybook" + # folder: dist + + # Собираем Storybook + - name: Build Storybook + run: npm run build-storybook + # + # Публикуем Storybook на Github Pages + - name: Deploy Storybook to Github Pages + uses: JamesIves/github-pages-deploy-action@4.2.1 + with: + branch: gh-pages + folder: storybook-static + commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 1c372b694..6bb6e56b1 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,8 +1,9 @@ -import type { Preview } from "@storybook/react"; +import type { Preview } from '@storybook/react'; +import '!style-loader!css-loader!postcss-loader!tailwindcss/tailwind.css'; const preview: Preview = { parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, diff --git a/package-lock.json b/package-lock.json index 053042670..00b27b99e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,17 @@ { - "name": "react-start-template", + "name": "furtivite.github.io", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "react-start-template", + "name": "furtivite.github.io", "version": "1.0.0", "license": "MIT", "dependencies": { + "autoprefixer": "^10.4.20", "clsx": "^1.2.1", + "nanoid": "^5.0.9", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -53,11 +55,14 @@ "jest-environment-jsdom": "^29.5.0", "less-loader": "^11.1.2", "mini-css-extract-plugin": "^2.7.6", + "postcss": "^8.4.49", + "postcss-loader": "^8.1.1", "prettier": "2.8.8", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", "style-loader": "^3.3.3", + "tailwindcss": "^3.4.17", "ts-jest": "^29.1.0", "typescript": "^5.1.3", "webpack": "^5.85.0", @@ -65,6 +70,19 @@ "webpack-dev-server": "^4.15.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -8776,6 +8794,13 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -8795,6 +8820,13 @@ "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", "dev": true }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -8980,6 +9012,43 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -9472,12 +9541,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -9499,10 +9569,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -9517,11 +9586,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -9640,11 +9710,20 @@ "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001554", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", - "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -9658,7 +9737,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -9713,16 +9793,11 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -9735,6 +9810,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -10759,6 +10837,13 @@ "detect-port": "bin/detect-port.js" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -10789,6 +10874,13 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -11017,10 +11109,10 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.567", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz", - "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", - "dev": true + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", + "license": "ISC" }, "node_modules/email-addresses": { "version": "5.0.0", @@ -11110,6 +11202,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -11324,10 +11426,10 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -12368,10 +12470,11 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -12548,10 +12651,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -12959,6 +13063,19 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -13045,10 +13162,14 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -13507,6 +13628,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -14169,12 +14303,16 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14330,6 +14468,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -16578,6 +16717,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16993,6 +17142,19 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -18182,12 +18344,13 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -18430,22 +18593,34 @@ "multicast-dns": "cli.js" } }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -18611,10 +18786,10 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -18646,6 +18821,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -18837,6 +19021,16 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -19362,10 +19556,10 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -19443,10 +19637,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -19461,15 +19654,195 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/postcss-loader/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss-loader/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -19529,11 +19902,38 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -19545,8 +19945,25 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -20095,6 +20512,26 @@ } } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -20452,18 +20889,22 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21025,10 +21466,10 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -21433,6 +21874,39 @@ "webpack": "^5.0.0" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -21482,6 +21956,44 @@ "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", "dev": true }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -21910,6 +22422,29 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -21988,6 +22523,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -22078,6 +22614,13 @@ "node": ">=6.10" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/ts-jest": { "version": "29.1.0", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", @@ -22535,10 +23078,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -22553,9 +23095,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -23649,6 +24192,12 @@ } }, "dependencies": { + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true + }, "@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -29888,6 +30437,12 @@ } } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -29904,6 +30459,12 @@ "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", "dev": true }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -30056,6 +30617,19 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "requires": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + } + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -30433,12 +31007,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-assert": { @@ -30457,15 +31031,14 @@ } }, "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" } }, "bs-logger": { @@ -30546,12 +31119,17 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "caniuse-lite": { - "version": "1.0.30001554", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", - "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==" + }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -30588,9 +31166,9 @@ "dev": true }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -31395,6 +31973,12 @@ "debug": "4" } }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -31416,6 +32000,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -31603,10 +32193,9 @@ } }, "electron-to-chromium": { - "version": "1.4.567", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz", - "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", - "dev": true + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==" }, "email-addresses": { "version": "5.0.0", @@ -31674,6 +32263,12 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -31851,10 +32446,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -32609,9 +33203,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -32764,9 +33358,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -33061,6 +33655,11 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, + "fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -33130,9 +33729,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, "function.prototype.name": { @@ -33458,6 +34057,15 @@ "has-symbols": "^1.0.2" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -33929,12 +34537,12 @@ "dev": true }, "is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.2" } }, "is-date-object": { @@ -35670,6 +36278,12 @@ } } }, + "jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -35980,6 +36594,12 @@ "type-check": "~0.4.0" } }, + "lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -36779,12 +37399,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -36962,11 +37582,21 @@ "thunky": "^1.0.2" } }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==" }, "natural-compare": { "version": "1.4.0", @@ -37106,10 +37736,9 @@ "dev": true }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "normalize-package-data": { "version": "2.5.0", @@ -37137,6 +37766,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -37258,6 +37892,12 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -37649,10 +38289,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -37706,14 +38345,104 @@ } }, "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "dependencies": { + "nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==" + } + } + }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dev": true, "requires": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "requires": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "dependencies": { + "yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "dev": true + } + } + }, + "postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "requires": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } } }, "postcss-modules-extract-imports": { @@ -37752,10 +38481,19 @@ "icss-utils": "^5.0.0" } }, + "postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.1.1" + } + }, "postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -37765,8 +38503,7 @@ "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "prelude-ls": { "version": "1.2.1", @@ -38173,6 +38910,23 @@ "tslib": "^2.0.0" } }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -38448,12 +39202,12 @@ "dev": true }, "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "requires": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -38871,10 +39625,9 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, "source-map-support": { "version": "0.5.13", @@ -39191,6 +39944,29 @@ "dev": true, "requires": {} }, + "sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -39227,6 +40003,36 @@ "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", "dev": true }, + "tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + } + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -39540,6 +40346,24 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -39683,6 +40507,12 @@ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "dev": true }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "ts-jest": { "version": "29.1.0", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", @@ -40009,13 +40839,12 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" } }, "uri-js": { diff --git a/package.json b/package.json index 0cf02ec1b..39c061423 100644 --- a/package.json +++ b/package.json @@ -54,11 +54,14 @@ "jest-environment-jsdom": "^29.5.0", "less-loader": "^11.1.2", "mini-css-extract-plugin": "^2.7.6", + "postcss": "^8.4.49", + "postcss-loader": "^8.1.1", "prettier": "2.8.8", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", "style-loader": "^3.3.3", + "tailwindcss": "^3.4.17", "ts-jest": "^29.1.0", "typescript": "^5.1.3", "webpack": "^5.85.0", @@ -66,7 +69,9 @@ "webpack-dev-server": "^4.15.0" }, "dependencies": { + "autoprefixer": "^10.4.20", "clsx": "^1.2.1", + "nanoid": "^5.0.9", "react": "^18.2.0", "react-dom": "^18.2.0" } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..493c16c7c --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: [require('tailwindcss'), require('autoprefixer'), require('postcss-nested')], +}; + +module.exports = config; diff --git a/src/assets/goods.ts b/src/assets/goods.ts new file mode 100644 index 000000000..1c74291d3 --- /dev/null +++ b/src/assets/goods.ts @@ -0,0 +1,18 @@ +import { IGoodsItem } from '../components/interfaces'; + +const image1 = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/1/small.jpg?raw=true'; + +export const goods: IGoodsItem[] = [ + { + id: 1, + isInStock: true, + title: 'Classic Monochrome Tees', + details: [ + "Elevate your everyday style with our Men's Black T-Shirts, the ultimate wardrobe essential for modern men. Crafted with meticulous attention to detail and designed for comfort, these versatile black tees are a must-have addition to your collection.", + "The classic black color never goes out of style. Whether you're dressing up for a special occasion or keeping it casual, these black t-shirts are the perfect choice, effortlessly complementing any outfit.", + ], + price: 75, + imageListing: image1, + imageFull: [image1, image1], + }, +]; diff --git a/src/components/Basket/BasketGoodsItem.tsx b/src/components/Basket/BasketGoodsItem.tsx new file mode 100644 index 000000000..f336a4085 --- /dev/null +++ b/src/components/Basket/BasketGoodsItem.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ICounter, IGoodsItem } from '../interfaces'; +import { Counter, DeleteBtn } from '../../components'; + +export const BasketGoodsItem = ({ counter, title, price, imageListing }: ICounter & IGoodsItem): React.ReactElement => ( +
+
+
+ +
+

{title}

+
+
+

${price}

+
+ + +
+
+
+); diff --git a/src/components/Basket/index.ts b/src/components/Basket/index.ts new file mode 100644 index 000000000..6c6c7146b --- /dev/null +++ b/src/components/Basket/index.ts @@ -0,0 +1 @@ +export { BasketGoodsItem } from './BasketGoodsItem'; diff --git a/src/components/Btns/AddToCart.tsx b/src/components/Btns/AddToCart.tsx new file mode 100644 index 000000000..7a9e4faed --- /dev/null +++ b/src/components/Btns/AddToCart.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { IBtn, ICounter } from '../interfaces'; +import { Btn, Counter } from '../../components'; + +export const AddToCart = ({ isDisabled, counter, children }: IBtn & ICounter): React.ReactElement => { + if (counter > 0) { + return ; + } + return ( + +
+ {children} + +
+
+ ); +}; diff --git a/src/components/Btns/Btn.tsx b/src/components/Btns/Btn.tsx new file mode 100644 index 000000000..d09825809 --- /dev/null +++ b/src/components/Btns/Btn.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { IBtn } from './interfaces'; + +export const Btn = ({ children, isDisabled }: IBtn): React.ReactElement => ( + +); diff --git a/src/components/Btns/DeleteBtn.tsx b/src/components/Btns/DeleteBtn.tsx new file mode 100644 index 000000000..f2848e34c --- /dev/null +++ b/src/components/Btns/DeleteBtn.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { IBtnDefault } from './interfaces'; + +export const DeleteBtn = ({ isDisabled }: IBtnDefault): React.ReactElement => ( + +); diff --git a/src/components/Btns/index.ts b/src/components/Btns/index.ts new file mode 100644 index 000000000..332cc7e7d --- /dev/null +++ b/src/components/Btns/index.ts @@ -0,0 +1,3 @@ +export { AddToCart } from './AddToCart'; +export { Btn } from './Btn'; +export { DeleteBtn } from './DeleteBtn'; diff --git a/src/components/Btns/interfaces.ts b/src/components/Btns/interfaces.ts new file mode 100644 index 000000000..57170e7f5 --- /dev/null +++ b/src/components/Btns/interfaces.ts @@ -0,0 +1,10 @@ +import React from 'react'; + +export interface IBtnDefault { + isDisabled?: boolean; +} + +export interface IBtn extends IBtnDefault { + // TEMP custom children then extends ILayout + children: React.JSX.Element | React.JSX.Element[] | string; +} diff --git a/src/components/Card/FullCard.tsx b/src/components/Card/FullCard.tsx new file mode 100644 index 000000000..31abcda0a --- /dev/null +++ b/src/components/Card/FullCard.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { nanoid } from 'nanoid'; +import { IGoodsItem } from './interfaces'; +import { Btn, Counter, StockStatus } from '../../components'; + +export const FullCard = ({ title, price, details, imageFull, isInStock }: IGoodsItem): React.ReactElement => ( +
+
+ {/* TEMP ONLY FIRST IMAGE */} + +
+

{title}

+ +

${price}

+
+

QUANTITY

+ +
+
+ Add to cart +

+ — Free shipping on orders $100+ +

+
+
+
+

Details

+ {typeof details === 'string' ? ( +

{details}

+ ) : ( + details.map((item) => ( +

+ {item} +

+ )) + )} +
+); diff --git a/src/components/Card/ShortCard.tsx b/src/components/Card/ShortCard.tsx new file mode 100644 index 000000000..18db09fb1 --- /dev/null +++ b/src/components/Card/ShortCard.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { IGoodsItem } from './interfaces'; +import { StockStatus } from '../StockStatus/StockStatus'; +import { AddToCart } from '../Btns'; + +export const ShortCard = ({ isInStock, title, price, details, imageListing }: IGoodsItem): React.ReactElement => ( +
+
+ +
+ + Add to cart + +
+

{title}

+

{details[0]}

+
+ +

${price}

+
+
+
+); diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts new file mode 100644 index 000000000..a98e5a875 --- /dev/null +++ b/src/components/Card/index.ts @@ -0,0 +1,2 @@ +export { FullCard } from './FullCard'; +export { ShortCard } from './ShortCard'; diff --git a/src/components/Card/interfaces.ts b/src/components/Card/interfaces.ts new file mode 100644 index 000000000..5fc0f7aba --- /dev/null +++ b/src/components/Card/interfaces.ts @@ -0,0 +1,9 @@ +export interface IGoodsItem { + id: number; + isInStock: boolean; + title: string; + details: string | string[]; + price: number; + imageListing: string; + imageFull: string[]; +} diff --git a/src/components/Counter/Counter.tsx b/src/components/Counter/Counter.tsx new file mode 100644 index 000000000..eb11fe5b2 --- /dev/null +++ b/src/components/Counter/Counter.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ICounter } from './interfaces'; + +export const Counter = ({ counter }: ICounter): React.ReactElement => ( +
+ + {counter} + +
+); diff --git a/src/components/Counter/interfaces.ts b/src/components/Counter/interfaces.ts new file mode 100644 index 000000000..8aab8f69d --- /dev/null +++ b/src/components/Counter/interfaces.ts @@ -0,0 +1,3 @@ +export interface ICounter { + counter: number; +} diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx new file mode 100644 index 000000000..94412dcb8 --- /dev/null +++ b/src/components/Header/Header.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Logo } from '../../components'; +import { ELogoType } from '../interfaces'; + +export const Header = (): React.ReactElement => ( +
+
+ +
+
+); diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx new file mode 100644 index 000000000..1dde332c0 --- /dev/null +++ b/src/components/Layout/Layout.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Header } from '../../components'; +import { ILayout } from './interfaces'; + +export const Layout = ({ children }: ILayout): React.ReactElement => ( +
+
+
{children}
+
+); diff --git a/src/components/Layout/interfaces.ts b/src/components/Layout/interfaces.ts new file mode 100644 index 000000000..f4d2d36fe --- /dev/null +++ b/src/components/Layout/interfaces.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +export interface ILayout { + children: React.JSX.Element | React.JSX.Element[] | string; +} diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx new file mode 100644 index 000000000..3ea06d3f7 --- /dev/null +++ b/src/components/Logo/Logo.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { ELogoType, ILogo } from '../interfaces'; + +export const Logo = ({ type, text }: ILogo): React.ReactElement => { + const imageUrl = 'https://raw.githubusercontent.com/furtivite/cdn.furtivite.github.io/refs/heads/main/images/'; + let url: string; + let width: number; + let height: number; + switch (type) { + case ELogoType.DARK: + url = 'logo-dark.svg'; + width = 44; + height = 44; + break; + case ELogoType.NOBORDER: + url = 'logo-empty.svg'; + width = 16; + height = 30; + break; + default: + url = 'logo-white.svg'; + width = 40; + height = 40; + break; + // case ELogoType.LIGHT: + } + + return ( +
+ + + {type === ELogoType.NOBORDER ? 'Admin' : text} + +
+ ); +}; diff --git a/src/components/Logo/interfaces.ts b/src/components/Logo/interfaces.ts new file mode 100644 index 000000000..f7b8c3b30 --- /dev/null +++ b/src/components/Logo/interfaces.ts @@ -0,0 +1,10 @@ +export enum ELogoType { + LIGHT = 'light', + DARK = 'dark', + NOBORDER = 'no-border', +} + +export interface ILogo { + type: ELogoType; + text: string; +} diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx new file mode 100644 index 000000000..574dfe796 --- /dev/null +++ b/src/components/Modal/Modal.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { IModal } from './interfaces'; + +export const Modal = ({ isVisible, children }: IModal): React.ReactElement => { + if (isVisible) { + return ( +
+
+
+ +
{children}
+
+
+ ); + } else return <>; +}; diff --git a/src/components/Modal/interfaces.ts b/src/components/Modal/interfaces.ts new file mode 100644 index 000000000..a035df9f9 --- /dev/null +++ b/src/components/Modal/interfaces.ts @@ -0,0 +1,5 @@ +import { ILayout } from '../interfaces'; + +export interface IModal extends ILayout { + isVisible: boolean; +} diff --git a/src/components/StockStatus/StockStatus.tsx b/src/components/StockStatus/StockStatus.tsx new file mode 100644 index 000000000..11fc00612 --- /dev/null +++ b/src/components/StockStatus/StockStatus.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { IStockStatus } from './interfaces'; + +export const StockStatus = ({ isInStock }: IStockStatus): React.ReactElement => ( + + {isInStock ? 'IN STOCK' : 'OUT OF STOCK'} + +); diff --git a/src/components/StockStatus/interfaces.ts b/src/components/StockStatus/interfaces.ts new file mode 100644 index 000000000..8b8c201d3 --- /dev/null +++ b/src/components/StockStatus/interfaces.ts @@ -0,0 +1,3 @@ +export interface IStockStatus { + isInStock: boolean; +} diff --git a/src/components/index.tsx b/src/components/index.tsx new file mode 100644 index 000000000..4cfe99e18 --- /dev/null +++ b/src/components/index.tsx @@ -0,0 +1,9 @@ +export { BasketGoodsItem } from './Basket'; +export { AddToCart, Btn, DeleteBtn } from './Btns'; +export { FullCard, ShortCard } from './Card'; +export { Counter } from './Counter/Counter'; +export { Header } from './Header/Header'; +export { Layout } from './Layout/Layout'; +export { Logo } from './Logo/Logo'; +export { Modal } from './Modal/Modal'; +export { StockStatus } from './StockStatus/StockStatus'; diff --git a/src/components/interfaces.ts b/src/components/interfaces.ts new file mode 100644 index 000000000..6192c95ac --- /dev/null +++ b/src/components/interfaces.ts @@ -0,0 +1,7 @@ +export * from './Btns/interfaces'; +export * from './Card/interfaces'; +export * from './Counter/interfaces'; +export * from './Layout/interfaces'; +export * from './Logo/interfaces'; +export * from './Modal/interfaces'; +export * from './StockStatus/interfaces'; diff --git a/src/index.tsx b/src/index.tsx index 26d2b1437..7f6a5922d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './app/index.css'; +import './styles/styles.scss'; import App from './app/App'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); diff --git a/src/stories/AddToCart.stories.ts b/src/stories/AddToCart.stories.ts new file mode 100644 index 000000000..3f07fdb50 --- /dev/null +++ b/src/stories/AddToCart.stories.ts @@ -0,0 +1,31 @@ +import type { Meta } from '@storybook/react'; +import { AddToCart } from '../components'; + +const meta: Meta = { + title: 'UI/Btns/AddToCart', + component: AddToCart, + tags: ['autodocs'], +}; + +export default meta; + +export const Empty = { + args: { + children: 'Add to cart', + isDisabled: false, + }, +}; + +export const HasGoods = { + args: { + counter: 1, + isDisabled: false, + }, +}; + +export const Disabled = { + args: { + children: 'Add to cart', + isDisabled: true, + }, +}; diff --git a/src/stories/BasketGoodsItem.stories.ts b/src/stories/BasketGoodsItem.stories.ts new file mode 100644 index 000000000..23dd0324b --- /dev/null +++ b/src/stories/BasketGoodsItem.stories.ts @@ -0,0 +1,25 @@ +import type { Meta } from '@storybook/react'; +import { BasketGoodsItem } from '../components/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Basket/BasketGoodsItem', + component: BasketGoodsItem, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const Default = { + args: { + id: item.id, + isInStock: true, + title: item.title, + details: item.details, + price: item.price, + imageListing: item.imageListing, + counter: 1, + }, +}; diff --git a/src/stories/Btn.stories.ts b/src/stories/Btn.stories.ts new file mode 100644 index 000000000..e255e73fd --- /dev/null +++ b/src/stories/Btn.stories.ts @@ -0,0 +1,24 @@ +import type { Meta } from '@storybook/react'; +import { Btn } from '../components'; + +const meta: Meta = { + title: 'UI/Btns/Btn', + component: Btn, + tags: ['autodocs'], +}; + +export default meta; + +export const Active = { + args: { + children: 'Subscribe', + isDisabled: false, + }, +}; + +export const Disabled = { + args: { + children: 'Subscribe', + isDisabled: true, + }, +}; diff --git a/src/stories/Button.stories.ts b/src/stories/Button.stories.ts deleted file mode 100644 index 8e2958cdd..000000000 --- a/src/stories/Button.stories.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Meta } from '@storybook/react'; - -import { Button } from './Button'; - -// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction -const meta: Meta = { - title: 'Example/Button', - component: Button, - tags: ['autodocs'], - argTypes: { - backgroundColor: { control: 'color' }, - }, -}; - -export default meta; - -// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args -export const Primary = { - args: { - primary: true, - label: 'Button', - }, -}; - -export const Secondary = { - args: { - label: 'Button', - }, -}; - -export const Large = { - args: { - size: 'large', - label: 'Button', - }, -}; - -export const Small = { - args: { - size: 'small', - label: 'Button', - }, -}; diff --git a/src/stories/Button.tsx b/src/stories/Button.tsx deleted file mode 100644 index 244ff033e..000000000 --- a/src/stories/Button.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import s from './button.module.sass'; - -interface ButtonProps { - /** - * Is this the principal call to action on the page? - */ - primary?: boolean; - /** - * What background color to use - */ - backgroundColor?: string; - /** - * How large should the button be? - */ - size?: 'small' | 'medium' | 'large'; - /** - * Button contents - */ - label: string; - /** - * Optional click handler - */ - onClick?: () => void; -} - -/** - * Primary UI component for user interaction - */ -export function Button({ primary = false, size = 'medium', backgroundColor, label, ...props }: ButtonProps) { - const mode = primary ? s.primary : s.secondary; - return ( - - ); -} diff --git a/src/stories/Counter.stories.ts b/src/stories/Counter.stories.ts new file mode 100644 index 000000000..124af3846 --- /dev/null +++ b/src/stories/Counter.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { Counter } from '../components'; + +const meta: Meta = { + title: 'UI/Counter', + component: Counter, + tags: ['autodocs'], +}; + +export default meta; + +export const Default = { + args: { + counter: 0, + }, +}; diff --git a/src/stories/DeleteBtn.stories.ts b/src/stories/DeleteBtn.stories.ts new file mode 100644 index 000000000..acd18e505 --- /dev/null +++ b/src/stories/DeleteBtn.stories.ts @@ -0,0 +1,24 @@ +import type { Meta } from '@storybook/react'; +import { DeleteBtn } from '../components'; + +const meta: Meta = { + title: 'UI/Btns/DeleteBtn', + component: DeleteBtn, + tags: ['autodocs'], +}; + +export default meta; + +export const Active = { + args: { + children: 'Subscribe', + isDisabled: false, + }, +}; + +export const Disabled = { + args: { + children: 'Subscribe', + isDisabled: true, + }, +}; diff --git a/src/stories/FullCard.stories.ts b/src/stories/FullCard.stories.ts new file mode 100644 index 000000000..c5e8153d9 --- /dev/null +++ b/src/stories/FullCard.stories.ts @@ -0,0 +1,35 @@ +import type { Meta } from '@storybook/react'; +import { FullCard } from '../components/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/FullCard', + component: FullCard, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const InStock = { + args: { + id: item.id, + isInStock: true, + title: item.title, + details: item.details, + price: item.price, + imageFull: item.imageFull, + }, +}; + +export const OutOfStock = { + args: { + id: item.id, + isInStock: false, + title: item.title, + details: item.details, + price: item.price, + imageFull: item.imageFull, + }, +}; diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts index c74c3732a..afe5b5883 100644 --- a/src/stories/Header.stories.ts +++ b/src/stories/Header.stories.ts @@ -1,26 +1,14 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { Header } from './Header'; +import type { Meta } from '@storybook/react'; +import { Header } from '../components'; const meta: Meta = { - title: 'Example/Header', + title: 'UI/Header', component: Header, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs tags: ['autodocs'], - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout - layout: 'fullscreen', - }, }; export default meta; -type Story = StoryObj; -export const LoggedIn = { - args: { - user: { - name: 'Jane Doe', - }, - }, +export const Default = { + args: {}, }; - -export const LoggedOut: Story = {}; diff --git a/src/stories/Header.tsx b/src/stories/Header.tsx deleted file mode 100644 index 555e7ce46..000000000 --- a/src/stories/Header.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; - -import { Button } from './Button'; -import './header.css'; - -type User = { - name: string; -}; - -interface HeaderProps { - user?: User; - onLogin: () => void; - onLogout: () => void; - onCreateAccount: () => void; -} - -export function Header({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) { - return ( -
-
-
- - - - - - - -

Acme

-
-
- {user ? ( - <> - - Welcome, {user.name}! - -
-
-
- ); -} diff --git a/src/stories/Layout.stories.ts b/src/stories/Layout.stories.ts new file mode 100644 index 000000000..5b87cbdf4 --- /dev/null +++ b/src/stories/Layout.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { Layout } from '../components'; + +const meta: Meta = { + title: 'UI/Layout', + component: Layout, + tags: ['autodocs'], +}; + +export default meta; + +export const Default = { + args: { + children: 'Some content', + }, +}; diff --git a/src/stories/Logo.stories.ts b/src/stories/Logo.stories.ts new file mode 100644 index 000000000..bd2a08ba9 --- /dev/null +++ b/src/stories/Logo.stories.ts @@ -0,0 +1,32 @@ +import type { Meta } from '@storybook/react'; +import { Logo } from '../components'; +import { ELogoType } from '../components/interfaces'; + +const meta: Meta = { + title: 'UI/Logo', + component: Logo, + tags: ['autodocs'], +}; + +export default meta; + +export const Dark = { + args: { + type: ELogoType.DARK, + text: 'Ecommerce', + }, +}; + +export const Light = { + args: { + type: ELogoType.LIGHT, + text: 'Ecommerce', + }, +}; + +export const Admin = { + args: { + type: ELogoType.NOBORDER, + text: '', + }, +}; diff --git a/src/stories/Modal.stories.ts b/src/stories/Modal.stories.ts new file mode 100644 index 000000000..d1b346bca --- /dev/null +++ b/src/stories/Modal.stories.ts @@ -0,0 +1,24 @@ +import type { Meta } from '@storybook/react'; +import { Modal } from '../components'; + +const meta: Meta = { + title: 'UI/Modal', + component: Modal, + tags: ['autodocs'], +}; + +export default meta; + +export const Visible = { + args: { + children: 'Some content', + isVisible: true, + }, +}; + +export const Hidden = { + args: { + children: 'Some content', + isVisible: false, + }, +}; diff --git a/src/stories/Page.stories.ts b/src/stories/Page.stories.ts deleted file mode 100644 index 905187f96..000000000 --- a/src/stories/Page.stories.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { within, userEvent } from '@storybook/testing-library'; - -import { Page } from './Page'; - -const meta: Meta = { - title: 'Example/Page', - component: Page, - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout - layout: 'fullscreen', - }, -}; - -export default meta; -type Story = StoryObj; - -export const LoggedOut: Story = {}; - -// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing -export const LoggedIn: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const loginButton = await canvas.getByRole('button', { - name: /Log in/i, - }); - await userEvent.click(loginButton); - }, -}; diff --git a/src/stories/Page.tsx b/src/stories/Page.tsx deleted file mode 100644 index 7b01ba5ce..000000000 --- a/src/stories/Page.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { Header } from './Header'; -import './page.css'; - -type User = { - name: string; -}; - -export const Page: React.FC = () => { - const [user, setUser] = React.useState(); - - return ( -
-
setUser({ name: 'Jane Doe' })} - onLogout={() => setUser(undefined)} - onCreateAccount={() => setUser({ name: 'Jane Doe' })} - /> - -
-

Pages in Storybook

-

- We recommend building UIs with a{' '} - - component-driven - {' '} - process starting with atomic components and ending with pages. -

-

- Render pages with mock data. This makes it easy to build and review page states without needing to navigate to - them in your app. Here are some handy patterns for managing page data in Storybook: -

-
    -
  • - Use a higher-level connected component. Storybook helps you compose such data from the args of child - component stories -
  • -
  • - Assemble data in the page component from your services. You can mock these services out using Storybook. -
  • -
-

- Get a guided tutorial on component-driven development at{' '} - - Storybook tutorials - - . Read more in the{' '} - - docs - - . -

-
- Tip Adjust the width of the canvas with the{' '} - - - - - - Viewports addon in the toolbar -
-
-
- ); -}; diff --git a/src/stories/ShortCard.stories.ts b/src/stories/ShortCard.stories.ts new file mode 100644 index 000000000..5836273ce --- /dev/null +++ b/src/stories/ShortCard.stories.ts @@ -0,0 +1,35 @@ +import type { Meta } from '@storybook/react'; +import { ShortCard } from '../components/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/ShortCard', + component: ShortCard, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const InStock = { + args: { + id: item.id, + isInStock: true, + title: item.title, + details: item.details, + price: item.price, + imageListing: item.imageListing, + }, +}; + +export const OutOfStock = { + args: { + id: item.id, + isInStock: false, + title: item.title, + details: item.details, + price: item.price, + imageListing: item.imageListing, + }, +}; diff --git a/src/stories/StockStatus.stories.ts b/src/stories/StockStatus.stories.ts new file mode 100644 index 000000000..546c8e786 --- /dev/null +++ b/src/stories/StockStatus.stories.ts @@ -0,0 +1,22 @@ +import type { Meta } from '@storybook/react'; +import { StockStatus } from '../components'; + +const meta: Meta = { + title: 'UI/StockItemStatus', + component: StockStatus, + tags: ['autodocs'], +}; + +export default meta; + +export const InStock = { + args: { + isInStock: true, + }, +}; + +export const OutOfStock = { + args: { + isInStock: false, + }, +}; diff --git a/src/stories/button.module.sass b/src/stories/button.module.sass deleted file mode 100644 index afb77db88..000000000 --- a/src/stories/button.module.sass +++ /dev/null @@ -1,30 +0,0 @@ -.button - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif - font-weight: 700 - border: 0 - border-radius: 3em - cursor: pointer - display: inline-block - line-height: 1 - - &.primary - color: white - background-color: #1ea7fd - - &.secondary - color: #333 - background-color: transparent - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset - - &.small - font-size: 12px - padding: 10px 16px - - &.medium - font-size: 14px - padding: 11px 20px - - &.large - font-size: 16px - padding: 12px 24px - diff --git a/src/stories/header.css b/src/stories/header.css deleted file mode 100644 index e30766072..000000000 --- a/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.storybook-header { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -.storybook-header svg { - display: inline-block; - vertical-align: top; -} - -.storybook-header h1 { - font-weight: 700; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - color: #333; - font-size: 14px; - margin-right: 10px; -} diff --git a/src/stories/page.css b/src/stories/page.css deleted file mode 100644 index 139f6885a..000000000 --- a/src/stories/page.css +++ /dev/null @@ -1,69 +0,0 @@ -.storybook-page { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 24px; - padding: 48px 20px; - margin: 0 auto; - max-width: 600px; - color: #333; -} - -.storybook-page h2 { - font-weight: 700; - font-size: 32px; - line-height: 1; - margin: 0 0 4px; - display: inline-block; - vertical-align: top; -} - -.storybook-page p { - margin: 1em 0; -} - -.storybook-page a { - text-decoration: none; - color: #1ea7fd; -} - -.storybook-page ul { - padding-left: 30px; - margin: 1em 0; -} - -.storybook-page li { - margin-bottom: 8px; -} - -.storybook-page .tip { - display: inline-block; - border-radius: 1em; - font-size: 11px; - line-height: 12px; - font-weight: 700; - background: #e7fdd8; - color: #66bf3c; - padding: 4px 12px; - margin-right: 10px; - vertical-align: top; -} - -.storybook-page .tip-wrapper { - font-size: 13px; - line-height: 20px; - margin-top: 40px; - margin-bottom: 40px; -} - -.storybook-page .tip-wrapper svg { - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - vertical-align: top; - margin-top: 3px; -} - -.storybook-page .tip-wrapper svg path { - fill: #1ea7fd; -} diff --git a/src/tailwind.css b/src/tailwind.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/src/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 000000000..a10b6b0cb --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,24 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{js,ts,jsx,tsx,html}'], + theme: { + colors: { + b: { + 100: '#E6E7E8', + 500: '#5C5F6A', + 600: '#474B57', + 800: '#202533', + 900: '#0E1422', + }, + w: { + 900: '#FFFFFF', + }, + }, + extend: {}, + fontFamily: { + inter: ['Inter', 'sans-serif'], + manrope: ['Manrope', 'sans-serif'], + }, + }, + plugins: [], +}; diff --git a/webpack.config.js b/webpack.config.js index 583dfd1ed..89bbbba89 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,11 +55,20 @@ module.exports = (_, args) => { }, { test: /\.css$/, + exclude: /node_modules/, use: [ { - loader: MiniCssExtractPlugin.loader, + loader: 'style-loader', + }, + { + loader: 'css-loader', + options: { + importLoaders: 1, + }, + }, + { + loader: 'postcss-loader', }, - 'css-loader', ], }, { From 46222bd565ea3ac54bc338c67d7f026ff4976e38 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Tue, 24 Dec 2024 15:30:19 +0300 Subject: [PATCH 08/30] fix: delete class --- src/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 7f6a5922d..26d2b1437 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './app/index.css'; -import './styles/styles.scss'; import App from './app/App'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); From ba974f1ce675b5879384d4c7689b89b9c91d2e2f Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Tue, 24 Dec 2024 15:34:36 +0300 Subject: [PATCH 09/30] feat: create new script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 39c061423..b77637635 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint": "eslint src --fix", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "deploy": "npx gh-pages -d dist" + "deploy": "npx gh-pages -d dist", + "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static" }, "license": "MIT", "devDependencies": { From 9fbcd4805c8c6c147fa944197fbe6862f3051075 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Tue, 24 Dec 2024 15:47:49 +0300 Subject: [PATCH 10/30] feat: change storybook deploy path --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b77637635..2603aeb8c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "deploy": "npx gh-pages -d dist", - "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static" + "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static -e storybook" }, "license": "MIT", "devDependencies": { From 325c9d9aae284414fb46720e916a391ba91add67 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Wed, 25 Dec 2024 07:41:06 +0300 Subject: [PATCH 11/30] feat: create homework 2 (#3) * feat: make custom deploy scripts * feat: change types --- package.json | 3 ++- src/components/Basket/BasketGoodsItem.tsx | 2 +- src/components/Btns/AddToCart.tsx | 3 ++- src/components/Btns/Btn.tsx | 2 +- src/components/Btns/DeleteBtn.tsx | 2 +- src/components/Btns/interfaces.ts | 2 +- src/components/Card/FullCard.tsx | 2 +- src/components/Card/ShortCard.tsx | 2 +- src/components/Counter/Counter.tsx | 2 +- src/components/Header/Header.tsx | 2 +- src/components/Layout/Layout.tsx | 2 +- src/components/Layout/interfaces.ts | 2 +- src/components/Logo/Logo.tsx | 2 +- src/components/Modal/Modal.tsx | 2 +- src/components/StockStatus/StockStatus.tsx | 2 +- 15 files changed, 17 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 2603aeb8c..e8bf8a4a0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "deploy": "npx gh-pages -d dist", - "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static -e storybook" + "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static -e storybook", + "deploy-all": "npm run build && npm run deploy && npm run deploy-storybook" }, "license": "MIT", "devDependencies": { diff --git a/src/components/Basket/BasketGoodsItem.tsx b/src/components/Basket/BasketGoodsItem.tsx index f336a4085..20e190551 100644 --- a/src/components/Basket/BasketGoodsItem.tsx +++ b/src/components/Basket/BasketGoodsItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ICounter, IGoodsItem } from '../interfaces'; import { Counter, DeleteBtn } from '../../components'; -export const BasketGoodsItem = ({ counter, title, price, imageListing }: ICounter & IGoodsItem): React.ReactElement => ( +export const BasketGoodsItem: React.FC = ({ counter, title, price, imageListing }) => (
diff --git a/src/components/Btns/AddToCart.tsx b/src/components/Btns/AddToCart.tsx index 7a9e4faed..9786881ff 100644 --- a/src/components/Btns/AddToCart.tsx +++ b/src/components/Btns/AddToCart.tsx @@ -2,10 +2,11 @@ import React from 'react'; import { IBtn, ICounter } from '../interfaces'; import { Btn, Counter } from '../../components'; -export const AddToCart = ({ isDisabled, counter, children }: IBtn & ICounter): React.ReactElement => { +export const AddToCart: React.FC = ({ isDisabled, counter, children }) => { if (counter > 0) { return ; } + return (
diff --git a/src/components/Btns/Btn.tsx b/src/components/Btns/Btn.tsx index d09825809..2d58f51fc 100644 --- a/src/components/Btns/Btn.tsx +++ b/src/components/Btns/Btn.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IBtn } from './interfaces'; -export const Btn = ({ children, isDisabled }: IBtn): React.ReactElement => ( +export const Btn: React.FC = ({ children, isDisabled }) => ( + ); + })} +
+
+
+

Select size

+
+ {sizes && + sizes.map((item) => { + const sizeClass = clsx( + 'w-10 aspect-square border-[1px] border-solid', + currentSize === item ? 'border-b-900 text-b-900' : 'border-b-100 text-b-500', + 'rounded font-medium text-xs leading-6 uppercase' + ); + return ( + + ); + })} +
+
+
+

QUANTITY

+ +
+
+ Add to cart +

+ — Free shipping on orders $100+ +

+
+
+ ); +}; diff --git a/src/components/Card/CardImage/CardImage.tsx b/src/components/Card/CardImage/CardImage.tsx new file mode 100644 index 000000000..092d6bf5b --- /dev/null +++ b/src/components/Card/CardImage/CardImage.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import clsx from 'clsx'; +import { nanoid } from 'nanoid'; +import { IGoodsItem } from '../../interfaces'; + +export const CardImage: React.FC> = ({ imageFull }) => { + const [sliderNumber, setSliderNumber] = React.useState(0); + + const changeSlider = (id: number): void => { + setSliderNumber(id); + }; + + return ( +
+ +
+ {imageFull.map((item, index) => { + const btnClassName = clsx( + 'w-2', + 'aspect-square', + index === sliderNumber ? 'bg-b-900' : 'bg-b-200', + 'rounded-full' + ); + + return ( + + ); + })} +
+
+ ); +}; diff --git a/src/components/Card/FullCard.tsx b/src/components/Card/FullCard.tsx deleted file mode 100644 index 4c4c40c0e..000000000 --- a/src/components/Card/FullCard.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { nanoid } from 'nanoid'; -import { IGoodsItem } from './interfaces'; -import { Btn, Counter, StockStatus } from '../../components'; - -export const FullCard: React.FC = ({ title, price, details, imageFull, isInStock }) => ( -
-
- {/* TEMP ONLY FIRST IMAGE */} - -
-

{title}

- -

${price}

-
-

QUANTITY

- -
-
- Add to cart -

- — Free shipping on orders $100+ -

-
-
-
-

Details

- {typeof details === 'string' ? ( -

{details}

- ) : ( - details.map((item) => ( -

- {item} -

- )) - )} -
-); diff --git a/src/components/Card/FullCard/FullCard.tsx b/src/components/Card/FullCard/FullCard.tsx new file mode 100644 index 000000000..38ef469d6 --- /dev/null +++ b/src/components/Card/FullCard/FullCard.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { nanoid } from 'nanoid'; +import { IGoodsItem } from '../../interfaces'; +import { CardImage, CardDescription } from '../../../components'; + +export const FullCard: React.FC = ({ title, price, details, imageFull, isInStock, colors, sizes }) => ( +
+
+ + +
+

Details

+ {typeof details === 'string' ? ( +

{details}

+ ) : ( + details.map((item) => ( +

+ {item} +

+ )) + )} +
+); diff --git a/src/components/Card/ShortCard.tsx b/src/components/Card/ShortCard/ShortCard.tsx similarity index 84% rename from src/components/Card/ShortCard.tsx rename to src/components/Card/ShortCard/ShortCard.tsx index 9de1ac2ba..dfeec421a 100644 --- a/src/components/Card/ShortCard.tsx +++ b/src/components/Card/ShortCard/ShortCard.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import { IGoodsItem } from './interfaces'; -import { StockStatus } from '../StockStatus/StockStatus'; -import { AddToCart } from '../Btns'; +import { IGoodsItem } from '../../interfaces'; +import { AddToCart, StockStatus } from '../../../components'; export const ShortCard: React.FC = ({ isInStock, title, price, details, imageListing }) => (
diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts index a98e5a875..bdac07a09 100644 --- a/src/components/Card/index.ts +++ b/src/components/Card/index.ts @@ -1,2 +1,4 @@ -export { FullCard } from './FullCard'; -export { ShortCard } from './ShortCard'; +export { CardDescription } from './CardDescription/CardDescription'; +export { CardImage } from './CardImage/CardImage'; +export { FullCard } from './FullCard/FullCard'; +export { ShortCard } from './ShortCard/ShortCard'; diff --git a/src/components/Card/interfaces.ts b/src/components/Card/interfaces.ts index 5fc0f7aba..8af3a77b2 100644 --- a/src/components/Card/interfaces.ts +++ b/src/components/Card/interfaces.ts @@ -1,3 +1,10 @@ +export enum EGoodsSizes { + S = 's', + M = 'm', + XL = 'xl', + XXL = 'xxl', +} + export interface IGoodsItem { id: number; isInStock: boolean; @@ -6,4 +13,6 @@ export interface IGoodsItem { price: number; imageListing: string; imageFull: string[]; + colors: string[]; + sizes: EGoodsSizes[]; } diff --git a/src/components/Counter/Counter.tsx b/src/components/Counter/Counter.tsx index 2fffed5b2..6d967aa3c 100644 --- a/src/components/Counter/Counter.tsx +++ b/src/components/Counter/Counter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ICounter } from './interfaces'; +import { ICounter } from '../interfaces'; export const Counter: React.FC = ({ counter }) => (
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index ce48f8a0f..3c75ffce2 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Header } from '../../components'; -import { ILayout } from './interfaces'; +import { ILayout } from '../interfaces'; export const Layout: React.FC = ({ children }: ILayout) => (
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 432482882..d86381859 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { IModal } from './interfaces'; +import { IModal } from '../interfaces'; export const Modal: React.FC = ({ isVisible, children }) => { if (isVisible) { diff --git a/src/components/StockStatus/StockStatus.tsx b/src/components/StockStatus/StockStatus.tsx index 03d1416ab..5e91874ed 100644 --- a/src/components/StockStatus/StockStatus.tsx +++ b/src/components/StockStatus/StockStatus.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { IStockStatus } from './interfaces'; +import { IStockStatus } from '../interfaces'; export const StockStatus: React.FC = ({ isInStock }) => ( diff --git a/src/components/index.tsx b/src/components/index.tsx index 4cfe99e18..1da543303 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -1,6 +1,6 @@ export { BasketGoodsItem } from './Basket'; export { AddToCart, Btn, DeleteBtn } from './Btns'; -export { FullCard, ShortCard } from './Card'; +export { CardDescription, CardImage, FullCard, ShortCard } from './Card'; export { Counter } from './Counter/Counter'; export { Header } from './Header/Header'; export { Layout } from './Layout/Layout'; diff --git a/src/components/interfaces.ts b/src/components/interfaces.ts index 6192c95ac..a8b3a3f6e 100644 --- a/src/components/interfaces.ts +++ b/src/components/interfaces.ts @@ -1,3 +1,4 @@ +export * from './Basket/interfaces'; export * from './Btns/interfaces'; export * from './Card/interfaces'; export * from './Counter/interfaces'; diff --git a/src/stories/BasketGoodsItem.stories.ts b/src/stories/BasketGoodsItem.stories.ts index 23dd0324b..a915b8fec 100644 --- a/src/stories/BasketGoodsItem.stories.ts +++ b/src/stories/BasketGoodsItem.stories.ts @@ -13,13 +13,18 @@ export default meta; const item = goods[0]; export const Default = { + argTypes: { + color: { + options: item.colors, + control: 'radio', + }, + }, args: { - id: item.id, - isInStock: true, + size: item.sizes[0], + color: item.colors[0], + counter: 1, title: item.title, - details: item.details, price: item.price, - imageListing: item.imageListing, - counter: 1, + image: item.imageListing, }, }; diff --git a/src/stories/CardDescription.stories.ts b/src/stories/CardDescription.stories.ts new file mode 100644 index 000000000..7af3fa19c --- /dev/null +++ b/src/stories/CardDescription.stories.ts @@ -0,0 +1,33 @@ +import type { Meta } from '@storybook/react'; +import { CardDescription } from '../components/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/components/CardDescription', + component: CardDescription, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const isInStock = { + args: { + title: item.title, + isInStock: item.isInStock, + price: item.price, + colors: item.colors, + sizes: item.sizes, + }, +}; + +export const isOutOfStock = { + args: { + title: item.title, + isInStock: !item.isInStock, + price: item.price, + colors: item.colors, + sizes: item.sizes, + }, +}; diff --git a/src/stories/CardImage.stories.ts b/src/stories/CardImage.stories.ts new file mode 100644 index 000000000..280d8867e --- /dev/null +++ b/src/stories/CardImage.stories.ts @@ -0,0 +1,19 @@ +import type { Meta } from '@storybook/react'; +import { CardImage } from '../components/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/components/CardImage', + component: CardImage, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const Default = { + args: { + imageFull: item.imageFull, + }, +}; diff --git a/src/stories/Counter.stories.ts b/src/stories/Counter.stories.ts index 124af3846..6207859a2 100644 --- a/src/stories/Counter.stories.ts +++ b/src/stories/Counter.stories.ts @@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react'; import { Counter } from '../components'; const meta: Meta = { - title: 'UI/Counter', + title: 'UI/Cards/components/Counter', component: Counter, tags: ['autodocs'], }; diff --git a/src/stories/FullCard.stories.ts b/src/stories/FullCard.stories.ts index c5e8153d9..2ffd925ab 100644 --- a/src/stories/FullCard.stories.ts +++ b/src/stories/FullCard.stories.ts @@ -20,6 +20,8 @@ export const InStock = { details: item.details, price: item.price, imageFull: item.imageFull, + colors: item.colors, + sizes: item.sizes, }, }; @@ -31,5 +33,7 @@ export const OutOfStock = { details: item.details, price: item.price, imageFull: item.imageFull, + colors: item.colors, + sizes: item.sizes, }, }; diff --git a/src/stories/StockStatus.stories.ts b/src/stories/StockStatus.stories.ts index 546c8e786..737d7b2cd 100644 --- a/src/stories/StockStatus.stories.ts +++ b/src/stories/StockStatus.stories.ts @@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react'; import { StockStatus } from '../components'; const meta: Meta = { - title: 'UI/StockItemStatus', + title: 'UI/Cards/components/StockItemStatus', component: StockStatus, tags: ['autodocs'], }; diff --git a/tailwind.config.js b/tailwind.config.js index a10b6b0cb..7ee51fb68 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -5,14 +5,25 @@ module.exports = { colors: { b: { 100: '#E6E7E8', + 200: '#B6B7BC', 500: '#5C5F6A', 600: '#474B57', 800: '#202533', 900: '#0E1422', }, w: { + 100: '#F6F6F6', 900: '#FFFFFF', }, + blue: { + 400: '#A3BEF8', + }, + yellow: { + 400: '#FFD58A', + }, + green: { + 400: '#83B18B', + }, }, extend: {}, fontFamily: { From 0f4350577394c545f68091678187801aa03e1a67 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:04:51 +0300 Subject: [PATCH 13/30] feat: create homework 3 (#5) * feat: move tailwind.css * feat: create MainPage * feat: install react router * feat: move components to right place * feat: create Store Page * fix: editing imports * fix: fix logo on main page * feat: add tailwind to store page * feat: create context * fix: rename AddToCart => AddToCartBtn * fix: edit all paths * fix: change tailwind colors * feat: create lang and theme switchers * feat: change header logo and layout height * feat: changed Store Layout * feat: init i18n * feat: add some translations * feat: add some translations and pages * feat: add dark theme classes * feat: make new Modal, Input and update Header * feat: finalyse --- .storybook/decorators/StoreDecorator.tsx | 40 +++ .storybook/main.ts | 17 +- .storybook/preview.ts | 2 + package-lock.json | 229 ++++++++++++++++-- package.json | 4 +- src/app/App.tsx | 21 +- src/app/Routing.tsx | 35 +++ src/app/StoreContext.ts | 32 +++ src/app/i18n/config.ts | 12 + src/app/i18n/translation.ts | 92 +++++++ src/{ => app}/tailwind.css | 0 src/assets/goods.ts | 2 +- .../BasketGoodsItem/BasketGoodsItem.tsx | 35 --- src/components/Btns/Btn/Btn.tsx | 11 - src/components/Btns/index.ts | 3 - .../Card/CardDescription/CardDescription.tsx | 75 ------ src/components/Card/FullCard/FullCard.tsx | 23 -- src/components/Card/ShortCard/ShortCard.tsx | 22 -- src/components/Counter/Counter.tsx | 26 -- src/components/Header/Header.tsx | 11 - src/components/Layout/Layout.tsx | 10 - src/components/Modal/Modal.tsx | 24 -- src/components/Modal/interfaces.ts | 5 - src/components/StockStatus/StockStatus.tsx | 8 - src/components/index.tsx | 9 - .../BasketGoodsItem/BasketGoodsItem.tsx | 46 ++++ src/{components => entities}/Basket/index.ts | 0 .../Basket/interfaces.ts | 2 +- .../Card/CardDescription/CardDescription.tsx | 97 ++++++++ .../Card/CardImage/CardImage.tsx | 2 +- src/entities/Card/FullCard/FullCard.tsx | 37 +++ src/entities/Card/ShortCard/ShortCard.tsx | 27 +++ src/{components => entities}/Card/index.ts | 0 .../Card/interfaces.ts | 0 src/entities/Header/Header.tsx | 28 +++ src/entities/Layout/Layout.tsx | 17 ++ .../Layout/interfaces.ts | 0 src/entities/Modal/Modal/Modal.tsx | 12 + .../Modal/ModalBackgorund/ModalBackgorund.tsx | 27 +++ src/entities/Modal/ModalItem/ModalItem.tsx | 15 ++ src/entities/Modal/index.ts | 3 + src/entities/Modal/interfaces.ts | 5 + src/entities/index.tsx | 5 + src/entities/interfaces.ts | 4 + src/index.tsx | 2 +- src/pages/MainPage/MainPage.tsx | 22 ++ src/pages/Store/Store.tsx | 85 +++++++ src/pages/StoreBasket/StoreBasket.tsx | 72 ++++++ src/pages/StoreCard/StoreCard.tsx | 75 ++++++ src/pages/index.ts | 4 + .../Btns/AddToCartBtn/AddToCartBtn.tsx} | 8 +- src/shared/Btns/Btn/Btn.tsx | 20 ++ .../Btns/DeleteBtn/DeleteBtn.tsx | 5 +- .../Btns/LangSwitcherBtn/LangSwitcherBtn.tsx | 31 +++ .../ThemeSwitcherBtn/ThemeSwitcherBtn.tsx | 16 ++ src/shared/Btns/index.ts | 5 + src/{components => shared}/Btns/interfaces.ts | 1 + src/shared/Counter/Counter.tsx | 40 +++ .../Counter/interfaces.ts | 0 src/shared/Input/Input.tsx | 50 ++++ src/shared/Input/interfaces.ts | 10 + src/{components => shared}/Logo/Logo.tsx | 2 +- src/{components => shared}/Logo/interfaces.ts | 0 src/shared/StockStatus/StockStatus.tsx | 12 + .../StockStatus/interfaces.ts | 0 src/shared/button/Button.tsx | 34 --- src/shared/button/button.css | 30 --- src/shared/button/sum.test.ts | 5 - src/shared/button/sum.ts | 1 - src/shared/index.ts | 5 + src/{components => shared}/interfaces.ts | 4 - ...art.stories.ts => AddToCartBtn.stories.ts} | 8 +- src/stories/BasketGoodsItem.stories.ts | 2 +- src/stories/Btn.stories.ts | 2 +- src/stories/CardDescription.stories.ts | 2 +- src/stories/CardImage.stories.ts | 2 +- src/stories/Counter.stories.ts | 2 +- src/stories/DeleteBtn.stories.ts | 2 +- src/stories/FullCard.stories.ts | 2 +- src/stories/Header.stories.ts | 6 +- src/stories/Input.stories.ts | 35 +++ src/stories/LangSwitcherBtn.stories.ts | 16 ++ src/stories/Layout.stories.ts | 6 +- src/stories/Logo.stories.ts | 4 +- src/stories/Modal.stories.ts | 14 +- src/stories/ModalBackgorund.stories.ts | 16 ++ src/stories/ModalItem.stories.ts | 16 ++ src/stories/ShortCard.stories.ts | 2 +- src/stories/StockStatus.stories.ts | 2 +- src/stories/ThemeSwitcherBtn.stories.ts | 16 ++ tailwind.config.js | 46 ++-- 91 files changed, 1367 insertions(+), 448 deletions(-) create mode 100644 .storybook/decorators/StoreDecorator.tsx create mode 100644 src/app/Routing.tsx create mode 100644 src/app/StoreContext.ts create mode 100644 src/app/i18n/config.ts create mode 100644 src/app/i18n/translation.ts rename src/{ => app}/tailwind.css (100%) delete mode 100644 src/components/Basket/BasketGoodsItem/BasketGoodsItem.tsx delete mode 100644 src/components/Btns/Btn/Btn.tsx delete mode 100644 src/components/Btns/index.ts delete mode 100644 src/components/Card/CardDescription/CardDescription.tsx delete mode 100644 src/components/Card/FullCard/FullCard.tsx delete mode 100644 src/components/Card/ShortCard/ShortCard.tsx delete mode 100644 src/components/Counter/Counter.tsx delete mode 100644 src/components/Header/Header.tsx delete mode 100644 src/components/Layout/Layout.tsx delete mode 100644 src/components/Modal/Modal.tsx delete mode 100644 src/components/Modal/interfaces.ts delete mode 100644 src/components/StockStatus/StockStatus.tsx delete mode 100644 src/components/index.tsx create mode 100644 src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx rename src/{components => entities}/Basket/index.ts (100%) rename src/{components => entities}/Basket/interfaces.ts (72%) create mode 100644 src/entities/Card/CardDescription/CardDescription.tsx rename src/{components => entities}/Card/CardImage/CardImage.tsx (94%) create mode 100644 src/entities/Card/FullCard/FullCard.tsx create mode 100644 src/entities/Card/ShortCard/ShortCard.tsx rename src/{components => entities}/Card/index.ts (100%) rename src/{components => entities}/Card/interfaces.ts (100%) create mode 100644 src/entities/Header/Header.tsx create mode 100644 src/entities/Layout/Layout.tsx rename src/{components => entities}/Layout/interfaces.ts (100%) create mode 100644 src/entities/Modal/Modal/Modal.tsx create mode 100644 src/entities/Modal/ModalBackgorund/ModalBackgorund.tsx create mode 100644 src/entities/Modal/ModalItem/ModalItem.tsx create mode 100644 src/entities/Modal/index.ts create mode 100644 src/entities/Modal/interfaces.ts create mode 100644 src/entities/index.tsx create mode 100644 src/entities/interfaces.ts create mode 100644 src/pages/MainPage/MainPage.tsx create mode 100644 src/pages/Store/Store.tsx create mode 100644 src/pages/StoreBasket/StoreBasket.tsx create mode 100644 src/pages/StoreCard/StoreCard.tsx create mode 100644 src/pages/index.ts rename src/{components/Btns/AddToCart/AddToCart.tsx => shared/Btns/AddToCartBtn/AddToCartBtn.tsx} (62%) create mode 100644 src/shared/Btns/Btn/Btn.tsx rename src/{components => shared}/Btns/DeleteBtn/DeleteBtn.tsx (74%) create mode 100644 src/shared/Btns/LangSwitcherBtn/LangSwitcherBtn.tsx create mode 100644 src/shared/Btns/ThemeSwitcherBtn/ThemeSwitcherBtn.tsx create mode 100644 src/shared/Btns/index.ts rename src/{components => shared}/Btns/interfaces.ts (91%) create mode 100644 src/shared/Counter/Counter.tsx rename src/{components => shared}/Counter/interfaces.ts (100%) create mode 100644 src/shared/Input/Input.tsx create mode 100644 src/shared/Input/interfaces.ts rename src/{components => shared}/Logo/Logo.tsx (93%) rename src/{components => shared}/Logo/interfaces.ts (100%) create mode 100644 src/shared/StockStatus/StockStatus.tsx rename src/{components => shared}/StockStatus/interfaces.ts (100%) delete mode 100644 src/shared/button/Button.tsx delete mode 100644 src/shared/button/button.css delete mode 100644 src/shared/button/sum.test.ts delete mode 100644 src/shared/button/sum.ts create mode 100644 src/shared/index.ts rename src/{components => shared}/interfaces.ts (50%) rename src/stories/{AddToCart.stories.ts => AddToCartBtn.stories.ts} (71%) create mode 100644 src/stories/Input.stories.ts create mode 100644 src/stories/LangSwitcherBtn.stories.ts create mode 100644 src/stories/ModalBackgorund.stories.ts create mode 100644 src/stories/ModalItem.stories.ts create mode 100644 src/stories/ThemeSwitcherBtn.stories.ts diff --git a/.storybook/decorators/StoreDecorator.tsx b/.storybook/decorators/StoreDecorator.tsx new file mode 100644 index 000000000..9a8dac81e --- /dev/null +++ b/.storybook/decorators/StoreDecorator.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { + defaultContext, + ELangVariables, + EThemeVariables, + IStoreContext, + StoreContext, +} from '../../src/app/StoreContext'; + +export default function StoreDecorator(Story: any) { + const [contextValue, setContextValue] = React.useState(defaultContext); + + const { theme, lang } = contextValue; + const { i18n } = useTranslation(); + + const themeSwitchHandler = (): void => { + if (theme === 'light') { + setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); + } else { + setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); + } + }; + + const langSwitchHandler = (): void => { + if (lang === 'ru') { + setContextValue({ ...contextValue, lang: ELangVariables.EN }); + i18n.changeLanguage('en-US'); + } else { + setContextValue({ ...contextValue, lang: ELangVariables.RU }); + i18n.changeLanguage('ru-RU'); + } + }; + + return ( + + + + ); +} diff --git a/.storybook/main.ts b/.storybook/main.ts index 3d1c9b2d5..8eca1d7ba 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,18 +1,19 @@ const config = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "@storybook/preset-scss", - "@storybook/addon-mdx-gfm" + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + '@storybook/preset-scss', + '@storybook/addon-mdx-gfm', + 'storybook-react-i18next', ], framework: { - name: "@storybook/react-webpack5", + name: '@storybook/react-webpack5', options: {}, }, docs: { - autodocs: "tag", + autodocs: 'tag', }, }; export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 6bb6e56b1..be42fe7b9 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,4 +1,5 @@ import type { Preview } from '@storybook/react'; +import i18n from '../src/app/i18n/config'; import '!style-loader!css-loader!postcss-loader!tailwindcss/tailwind.css'; const preview: Preview = { @@ -10,6 +11,7 @@ const preview: Preview = { date: /Date$/, }, }, + i18n, }, }; diff --git a/package-lock.json b/package-lock.json index 00b27b99e..9902e96b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "clsx": "^1.2.1", "nanoid": "^5.0.9", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-i18next": "^15.4.0", + "react-router": "^7.1.1" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -2051,12 +2053,12 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -7539,6 +7541,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -13749,6 +13757,15 @@ "node": ">=12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -13929,6 +13946,38 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "24.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.1.tgz", + "integrity": "sha512-Q2wC1TjWcSikn1VAJg13UGIjc+okpFxQTxjVAymOnSA3RpttBQNMPf2ovcgoFVsV4QNxTfNZMAxorXZXsk4fBA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -20427,6 +20476,28 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, + "node_modules/react-i18next": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", + "integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -20489,6 +20560,39 @@ } } }, + "node_modules/react-router": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", + "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -20704,10 +20808,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -21353,6 +21457,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -22766,6 +22876,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -22836,7 +22952,7 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23341,6 +23457,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -25552,12 +25677,11 @@ "dev": true }, "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dev": true, + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -29396,6 +29520,11 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -34164,6 +34293,14 @@ "terser": "^5.10.0" } }, + "html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "requires": { + "void-elements": "3.1.0" + } + }, "html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -34285,6 +34422,15 @@ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true }, + "i18next": { + "version": "24.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.1.tgz", + "integrity": "sha512-Q2wC1TjWcSikn1VAJg13UGIjc+okpFxQTxjVAymOnSA3RpttBQNMPf2ovcgoFVsV4QNxTfNZMAxorXZXsk4fBA==", + "peer": true, + "requires": { + "@babel/runtime": "^7.23.2" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -38864,6 +39010,15 @@ } } }, + "react-i18next": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", + "integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==", + "requires": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -38899,6 +39054,24 @@ "tslib": "^2.0.0" } }, + "react-router": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", + "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "requires": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "dependencies": { + "cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==" + } + } + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -39060,10 +39233,9 @@ } }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { "version": "0.15.2", @@ -39534,6 +39706,11 @@ "send": "0.18.0" } }, + "set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -40613,6 +40790,11 @@ } } }, + "turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -40665,7 +40847,7 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "dev": true + "devOptional": true }, "ufo": { "version": "1.4.0", @@ -41024,6 +41206,11 @@ "unist-util-stringify-position": "^3.0.0" } }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index e8bf8a4a0..f3c056d89 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,8 @@ "clsx": "^1.2.1", "nanoid": "^5.0.9", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-i18next": "^15.4.0", + "react-router": "^7.1.1" } } diff --git a/src/app/App.tsx b/src/app/App.tsx index fc8f434ed..d3c458edf 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,25 +1,8 @@ import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import { Routing } from './Routing'; function App() { - return ( -
-
- logo -

- Привет, меня зовут Егор. Я — разработчик интерфейсов. -
- Пишу на React, Angular с TypeScript. Хочу разобраться с React получше. -
- Контакт{' '} - - @antytoto - -

-
-
- ); + return ; } export default App; diff --git a/src/app/Routing.tsx b/src/app/Routing.tsx new file mode 100644 index 000000000..e06530e43 --- /dev/null +++ b/src/app/Routing.tsx @@ -0,0 +1,35 @@ +import { nanoid } from 'nanoid'; +import React from 'react'; +import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; +import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; + +export const Routing: React.FC = () => { + const pages: RouteProps[] = [ + { + path: '/', + element: , + }, + { + path: '/store', + element: , + }, + { + path: '/store/basket', + element: , + }, + { + path: '/store/card', + element: , + }, + ]; + + return ( + + + {pages.map((item) => ( + + ))} + + + ); +}; diff --git a/src/app/StoreContext.ts b/src/app/StoreContext.ts new file mode 100644 index 000000000..8c013276d --- /dev/null +++ b/src/app/StoreContext.ts @@ -0,0 +1,32 @@ +import React from 'react'; + +export enum EThemeVariables { + LIGHT = 'light', + DARK = 'dark', +} + +export enum ELangVariables { + RU = 'ru', + EN = 'en', +} + +export interface IStoreContext { + theme: EThemeVariables; + lang: ELangVariables; +} + +export interface IStoreContextProps extends IStoreContext { + themeSwitchHandler: () => void; + langSwitchHandler: () => void; +} + +export const defaultContext: IStoreContext = { + theme: EThemeVariables.LIGHT, + lang: ELangVariables.EN, +}; + +export const StoreContext = React.createContext({ + ...defaultContext, + themeSwitchHandler: () => null, + langSwitchHandler: () => null, +}); diff --git a/src/app/i18n/config.ts b/src/app/i18n/config.ts new file mode 100644 index 000000000..300f2689b --- /dev/null +++ b/src/app/i18n/config.ts @@ -0,0 +1,12 @@ +import i18next from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import { resources } from './translation'; +import { ELangVariables } from '../StoreContext'; + +i18next.use(initReactI18next).init({ + lng: ELangVariables.EN, + debug: true, + resources, +}); + +export default i18next; diff --git a/src/app/i18n/translation.ts b/src/app/i18n/translation.ts new file mode 100644 index 000000000..7f806a501 --- /dev/null +++ b/src/app/i18n/translation.ts @@ -0,0 +1,92 @@ +export const resources = { + en: { + translation: { + storeName: 'Ecommerce', + tempLinks: { + toMain: 'to Main Page', + toCard: 'to Card', + toBasket: 'to Basket', + }, + card: { + stockStatus: { + in: 'IN STOCK', + out: 'OUT OF STOCK', + }, + colors: { + title: 'Available Colors', + current: 'Current color is', + choose: 'You choose', + }, + sizes: { + title: 'Select Size', + current: 'Current size is', + choose: 'You choose', + }, + quantity: 'QUANTITY', + offer: '\u2013\u00A0Free shipping on\u00A0orders $100+', + details: 'Details', + addToCart: 'Add to Cart', + }, + basket: { + color: 'Color', + size: 'Size', + }, + counter: { + increase: 'Increase', + decrease: 'Decrease', + }, + mainPage: { + placeholder: 'Enter the value', + showBtn: 'Show modal', + }, + modal: { + close: 'Close modal window', + }, + }, + }, + ru: { + translation: { + storeName: 'Ecommerce.RU', + tempLinks: { + toMain: 'На главную', + toCard: 'В карточку', + toBasket: 'В корзину', + }, + card: { + stockStatus: { + in: 'В НАЛИЧИИ', + out: 'ЗАКОНЧИЛОСЬ', + }, + colors: { + title: 'Доступные цвета', + current: 'Текущий цвет', + choose: 'Вы выбираете', + }, + sizes: { + title: 'Доступные размеры', + current: 'Текущий размер', + choose: 'Вы выбираете', + }, + quantity: 'КОЛИЧЕСТВО', + offer: '\u2013\u00A0Бесплатная доставка заказов от\u00A0$100', + details: 'Описание', + addToCart: 'Добавить в корзину', + }, + basket: { + color: 'Цвет', + size: 'Размер', + }, + counter: { + increase: 'Увеличить количество', + decrease: 'Уменьшить количество', + }, + mainPage: { + placeholder: 'Введите значение', + showBtn: 'Показать модальное окно', + }, + modal: { + close: 'Закрыть модальное окно', + }, + }, + }, +}; diff --git a/src/tailwind.css b/src/app/tailwind.css similarity index 100% rename from src/tailwind.css rename to src/app/tailwind.css diff --git a/src/assets/goods.ts b/src/assets/goods.ts index df0113eee..635356848 100644 --- a/src/assets/goods.ts +++ b/src/assets/goods.ts @@ -1,4 +1,4 @@ -import { EGoodsSizes, IGoodsItem } from '../components/interfaces'; +import { EGoodsSizes, IGoodsItem } from '../entities/interfaces'; const image1 = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/1/small.png?raw=true'; const image1big = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/1/big.png?raw=true'; diff --git a/src/components/Basket/BasketGoodsItem/BasketGoodsItem.tsx b/src/components/Basket/BasketGoodsItem/BasketGoodsItem.tsx deleted file mode 100644 index c1436e1a6..000000000 --- a/src/components/Basket/BasketGoodsItem/BasketGoodsItem.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import { IBasketGoodsItem } from '../../interfaces'; -import { Counter, DeleteBtn } from '../..'; - -export const BasketGoodsItem: React.FC = ({ size, color, counter, title, price, image }) => { - const colorClass = clsx('w-3 aspect-square rounded-full', color); - - return ( -
-
-
- -
-
-

{title}

-
-

Color:

-
-

-  — Size: {size} -

-
-
-
-
-

${price}

-
- - -
-
-
- ); -}; diff --git a/src/components/Btns/Btn/Btn.tsx b/src/components/Btns/Btn/Btn.tsx deleted file mode 100644 index d6420c9a6..000000000 --- a/src/components/Btns/Btn/Btn.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { IBtn } from '../../interfaces'; - -export const Btn: React.FC = ({ children, isDisabled }) => ( - -); diff --git a/src/components/Btns/index.ts b/src/components/Btns/index.ts deleted file mode 100644 index bffe39fc5..000000000 --- a/src/components/Btns/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { AddToCart } from './AddToCart/AddToCart'; -export { Btn } from './Btn/Btn'; -export { DeleteBtn } from './DeleteBtn/DeleteBtn'; diff --git a/src/components/Card/CardDescription/CardDescription.tsx b/src/components/Card/CardDescription/CardDescription.tsx deleted file mode 100644 index 60e12ca83..000000000 --- a/src/components/Card/CardDescription/CardDescription.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import { nanoid } from 'nanoid'; -import { EGoodsSizes, IGoodsItem } from '../../interfaces'; -import { Btn, Counter, StockStatus } from '../../../components'; - -export const CardDescription: React.FC> = ({ title, isInStock, price, colors, sizes }) => { - const [currentColor, setCurrentColor] = React.useState(colors ? colors[0] : ''); - const [currentSize, setCurrentSize] = React.useState(sizes[0]); - - const changeColor = (color: string) => { - setCurrentColor(color); - }; - - const changeSize = (size: EGoodsSizes) => { - setCurrentSize(size); - }; - - return ( -
-

{title}

- -

${price}

-
-

Available Colors

-
- {colors && - colors.map((item) => { - const colorClass = clsx( - 'w-6 aspect-square rounded-full', - item, - currentColor === item && 'outline outline-1 outline-b-900 outline-offset-2' - ); - return ( - - ); - })} -
-
-
-

Select size

-
- {sizes && - sizes.map((item) => { - const sizeClass = clsx( - 'w-10 aspect-square border-[1px] border-solid', - currentSize === item ? 'border-b-900 text-b-900' : 'border-b-100 text-b-500', - 'rounded font-medium text-xs leading-6 uppercase' - ); - return ( - - ); - })} -
-
-
-

QUANTITY

- -
-
- Add to cart -

- — Free shipping on orders $100+ -

-
-
- ); -}; diff --git a/src/components/Card/FullCard/FullCard.tsx b/src/components/Card/FullCard/FullCard.tsx deleted file mode 100644 index 38ef469d6..000000000 --- a/src/components/Card/FullCard/FullCard.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { nanoid } from 'nanoid'; -import { IGoodsItem } from '../../interfaces'; -import { CardImage, CardDescription } from '../../../components'; - -export const FullCard: React.FC = ({ title, price, details, imageFull, isInStock, colors, sizes }) => ( -
-
- - -
-

Details

- {typeof details === 'string' ? ( -

{details}

- ) : ( - details.map((item) => ( -

- {item} -

- )) - )} -
-); diff --git a/src/components/Card/ShortCard/ShortCard.tsx b/src/components/Card/ShortCard/ShortCard.tsx deleted file mode 100644 index dfeec421a..000000000 --- a/src/components/Card/ShortCard/ShortCard.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { IGoodsItem } from '../../interfaces'; -import { AddToCart, StockStatus } from '../../../components'; - -export const ShortCard: React.FC = ({ isInStock, title, price, details, imageListing }) => ( -
-
- -
- - Add to cart - -
-

{title}

-

{details[0]}

-
- -

${price}

-
-
-
-); diff --git a/src/components/Counter/Counter.tsx b/src/components/Counter/Counter.tsx deleted file mode 100644 index 6d967aa3c..000000000 --- a/src/components/Counter/Counter.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { ICounter } from '../interfaces'; - -export const Counter: React.FC = ({ counter }) => ( -
- - {counter} - -
-); diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx deleted file mode 100644 index 99fad8dc3..000000000 --- a/src/components/Header/Header.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { Logo } from '../../components'; -import { ELogoType } from '../interfaces'; - -export const Header: React.FC = () => ( -
-
- -
-
-); diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx deleted file mode 100644 index 3c75ffce2..000000000 --- a/src/components/Layout/Layout.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { Header } from '../../components'; -import { ILayout } from '../interfaces'; - -export const Layout: React.FC = ({ children }: ILayout) => ( -
-
-
{children}
-
-); diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx deleted file mode 100644 index d86381859..000000000 --- a/src/components/Modal/Modal.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { IModal } from '../interfaces'; - -export const Modal: React.FC = ({ isVisible, children }) => { - if (isVisible) { - return ( -
-
-
- -
{children}
-
-
- ); - } else return <>; -}; diff --git a/src/components/Modal/interfaces.ts b/src/components/Modal/interfaces.ts deleted file mode 100644 index a035df9f9..000000000 --- a/src/components/Modal/interfaces.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ILayout } from '../interfaces'; - -export interface IModal extends ILayout { - isVisible: boolean; -} diff --git a/src/components/StockStatus/StockStatus.tsx b/src/components/StockStatus/StockStatus.tsx deleted file mode 100644 index 5e91874ed..000000000 --- a/src/components/StockStatus/StockStatus.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { IStockStatus } from '../interfaces'; - -export const StockStatus: React.FC = ({ isInStock }) => ( - - {isInStock ? 'IN STOCK' : 'OUT OF STOCK'} - -); diff --git a/src/components/index.tsx b/src/components/index.tsx deleted file mode 100644 index 1da543303..000000000 --- a/src/components/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export { BasketGoodsItem } from './Basket'; -export { AddToCart, Btn, DeleteBtn } from './Btns'; -export { CardDescription, CardImage, FullCard, ShortCard } from './Card'; -export { Counter } from './Counter/Counter'; -export { Header } from './Header/Header'; -export { Layout } from './Layout/Layout'; -export { Logo } from './Logo/Logo'; -export { Modal } from './Modal/Modal'; -export { StockStatus } from './StockStatus/StockStatus'; diff --git a/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx b/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx new file mode 100644 index 000000000..27d8e6ad0 --- /dev/null +++ b/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { IBasketGoodsItem } from '../../../entities/interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; +import { Counter, DeleteBtn } from '../../../shared'; + +export const BasketGoodsItem: React.FC = ({ size, color, counter, title, price, image }) => { + const colorClass = clsx('w-3 aspect-square rounded-full', color); + const { theme } = React.useContext(StoreContext); + const { t } = useTranslation(); + + const isDarkTheme = theme === EThemeVariables.DARK; + const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; + const b500ClassName = isDarkTheme ? 'text-w-500' : 'text-b-500'; + const h1ClassName = clsx('font-semibold text-sm leading-7', w900ClassName); + const subtitleClassName = clsx('flex items-baseline gap-2 font-medium text-xs leading-6', b500ClassName); + const priceClassName = clsx('font text-sm text-b-900 leading-7', w900ClassName); + + return ( +
+
+
+ +
+
+

{title}

+
+

{t('basket.color')}

+
+

+  — {t('basket.size')}: {size} +

+
+
+
+
+

${price}

+
+ + null} /> +
+
+
+ ); +}; diff --git a/src/components/Basket/index.ts b/src/entities/Basket/index.ts similarity index 100% rename from src/components/Basket/index.ts rename to src/entities/Basket/index.ts diff --git a/src/components/Basket/interfaces.ts b/src/entities/Basket/interfaces.ts similarity index 72% rename from src/components/Basket/interfaces.ts rename to src/entities/Basket/interfaces.ts index 6dd340161..80abaf281 100644 --- a/src/components/Basket/interfaces.ts +++ b/src/entities/Basket/interfaces.ts @@ -1,4 +1,4 @@ -import { EGoodsSizes } from '../interfaces'; +import { EGoodsSizes } from '../../entities/interfaces'; export interface IBasketGoodsItem { size: EGoodsSizes; diff --git a/src/entities/Card/CardDescription/CardDescription.tsx b/src/entities/Card/CardDescription/CardDescription.tsx new file mode 100644 index 000000000..564e23508 --- /dev/null +++ b/src/entities/Card/CardDescription/CardDescription.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import clsx from 'clsx'; +import { nanoid } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { EGoodsSizes, IGoodsItem } from '../../../entities/interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; +import { Btn, Counter, StockStatus } from '../../../shared'; + +export const CardDescription: React.FC> = ({ title, isInStock, price, colors, sizes }) => { + const [currentColor, setCurrentColor] = React.useState(colors ? colors[0] : ''); + const [currentSize, setCurrentSize] = React.useState(sizes[0]); + + const { theme } = React.useContext(StoreContext); + const { t } = useTranslation(); + + const isDarkTheme = theme === EThemeVariables.DARK; + + const changeColor = (color: string) => { + setCurrentColor(color); + }; + + const changeSize = (size: EGoodsSizes) => { + setCurrentSize(size); + }; + + const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; + const b500ClassName = isDarkTheme ? 'text-w-500' : 'text-b-500'; + const h1ClassName = clsx('mb-3 font-bold text-2xl text-b-900', w900ClassName); + const priceClassName = clsx('mt-6 font-semibold text-lg', w900ClassName); + const sectionTitleClassName = clsx('mb-[10px] font-medium text-xs leading-7 uppercase', b500ClassName); + + return ( +
+

{title}

+ +

${price}

+
+

{t('card.colors.title')}

+
+ {colors && + colors.map((item) => { + const isTargetItem = currentColor === item; + const colorClass = clsx( + 'w-6 aspect-square rounded-full', + item, + isTargetItem && 'outline outline-1 outline-b-900 outline-offset-2', + isTargetItem && isDarkTheme && 'outline-w-900', + isTargetItem && !isDarkTheme && 'outline-b-900' + ); + return ( + + ); + })} +
+
+
+

{t('card.sizes.title')}

+
+ {sizes && + sizes.map((item) => { + const isTargetItem = currentSize === item; + const sizeClass = clsx( + 'w-10 aspect-square border-[1px] border-solid font-medium text-xs leading-6', + isTargetItem && isDarkTheme && 'bg-w-900 text-b-900', + !isTargetItem && isDarkTheme && '', + isTargetItem && !isDarkTheme && 'border-b-900 text-b-900', + !isTargetItem && !isDarkTheme && 'border-b-100 text-b-500', + 'rounded uppercase' + ); + return ( + + ); + })} +
+
+
+

{t('card.quantity')}

+ +
+
+ null} isDisabled={!isInStock}> + {t('card.addToCart')} + +

{t('card.offer')}

+
+
+ ); +}; diff --git a/src/components/Card/CardImage/CardImage.tsx b/src/entities/Card/CardImage/CardImage.tsx similarity index 94% rename from src/components/Card/CardImage/CardImage.tsx rename to src/entities/Card/CardImage/CardImage.tsx index 092d6bf5b..d35acbb63 100644 --- a/src/components/Card/CardImage/CardImage.tsx +++ b/src/entities/Card/CardImage/CardImage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import clsx from 'clsx'; import { nanoid } from 'nanoid'; -import { IGoodsItem } from '../../interfaces'; +import { IGoodsItem } from '../../../entities/interfaces'; export const CardImage: React.FC> = ({ imageFull }) => { const [sliderNumber, setSliderNumber] = React.useState(0); diff --git a/src/entities/Card/FullCard/FullCard.tsx b/src/entities/Card/FullCard/FullCard.tsx new file mode 100644 index 000000000..9886bae79 --- /dev/null +++ b/src/entities/Card/FullCard/FullCard.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { nanoid } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { IGoodsItem } from '../../../entities/interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; +import { CardImage, CardDescription } from '../../../entities'; +import clsx from 'clsx'; + +export const FullCard: React.FC = ({ title, price, details, imageFull, isInStock, colors, sizes }) => { + const { theme } = React.useContext(StoreContext); + const { t } = useTranslation(); + + const isDarkTheme = theme === EThemeVariables.DARK; + const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; + const b500ClassName = isDarkTheme ? 'text-w-500' : 'text-b-500'; + const h2ClassName = clsx('mb-6 font-bold text-base', w900ClassName); + const textClassName = clsx('text-sm leading-7', b500ClassName); + + return ( +
+
+ + +
+

{t('card.details')}

+ {typeof details === 'string' ? ( +

{details}

+ ) : ( + details.map((item) => ( +

+ {item} +

+ )) + )} +
+ ); +}; diff --git a/src/entities/Card/ShortCard/ShortCard.tsx b/src/entities/Card/ShortCard/ShortCard.tsx new file mode 100644 index 000000000..60603ab5d --- /dev/null +++ b/src/entities/Card/ShortCard/ShortCard.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { IGoodsItem } from '../../../entities/interfaces'; +import { AddToCartBtn, StockStatus } from '../../../shared'; + +export const ShortCard: React.FC = ({ isInStock, title, price, details, imageListing }) => { + const { t } = useTranslation(); + + return ( +
+
+ +
+ null} counter={0} isDisabled={!isInStock}> + {t('card.addToCart')} + +
+

{title}

+

{details[0]}

+
+ +

${price}

+
+
+
+ ); +}; diff --git a/src/components/Card/index.ts b/src/entities/Card/index.ts similarity index 100% rename from src/components/Card/index.ts rename to src/entities/Card/index.ts diff --git a/src/components/Card/interfaces.ts b/src/entities/Card/interfaces.ts similarity index 100% rename from src/components/Card/interfaces.ts rename to src/entities/Card/interfaces.ts diff --git a/src/entities/Header/Header.tsx b/src/entities/Header/Header.tsx new file mode 100644 index 000000000..27132def6 --- /dev/null +++ b/src/entities/Header/Header.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { LangSwitcherBtn, Logo, ThemeSwitcherBtn } from '../../shared'; +import { ELogoType } from '../../shared/interfaces'; +import { EThemeVariables, StoreContext } from '../../app/StoreContext'; + +export const Header: React.FC = () => { + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + const className = clsx( + 'sticky top-0 py-[22px] border-b-[1px] border-solid', + isDarkTheme ? 'bg-black border-b-900' : 'bg-white border-b-100' + ); + const { t } = useTranslation(); + + return ( +
+
+ +
+ + +
+
+
+ ); +}; diff --git a/src/entities/Layout/Layout.tsx b/src/entities/Layout/Layout.tsx new file mode 100644 index 000000000..a9e923137 --- /dev/null +++ b/src/entities/Layout/Layout.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Header } from '../../entities'; +import { ILayout } from '../../entities/interfaces'; +import { EThemeVariables, StoreContext } from '../../app/StoreContext'; + +export const Layout: React.FC = ({ children }: ILayout) => { + const { theme, lang } = React.useContext(StoreContext); + const className = clsx('relative min-h-screen', theme === EThemeVariables.DARK ? 'bg-black text-w-100' : ''); + + return ( +
+
+
{children}
+
+ ); +}; diff --git a/src/components/Layout/interfaces.ts b/src/entities/Layout/interfaces.ts similarity index 100% rename from src/components/Layout/interfaces.ts rename to src/entities/Layout/interfaces.ts diff --git a/src/entities/Modal/Modal/Modal.tsx b/src/entities/Modal/Modal/Modal.tsx new file mode 100644 index 000000000..7e40a5ba1 --- /dev/null +++ b/src/entities/Modal/Modal/Modal.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { ModalBackgorund } from '../ModalBackgorund/ModalBackgorund'; +import { ModalItem } from '../ModalItem/ModalItem'; +import { IModal } from '../interfaces'; + +export const Modal: React.FC = ({ children, changeVisibility }) => { + return ( + + {children} + + ); +}; diff --git a/src/entities/Modal/ModalBackgorund/ModalBackgorund.tsx b/src/entities/Modal/ModalBackgorund/ModalBackgorund.tsx new file mode 100644 index 000000000..7468c733a --- /dev/null +++ b/src/entities/Modal/ModalBackgorund/ModalBackgorund.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { IModal } from '../../interfaces'; + +export const ModalBackgorund: React.FC = ({ children, changeVisibility }) => { + const { t } = useTranslation(); + + return ( +
+
+
+
+ + {children} +
+
+
+ ); +}; diff --git a/src/entities/Modal/ModalItem/ModalItem.tsx b/src/entities/Modal/ModalItem/ModalItem.tsx new file mode 100644 index 000000000..54f2330aa --- /dev/null +++ b/src/entities/Modal/ModalItem/ModalItem.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import clsx from 'clsx'; +import { ILayout } from 'src/entities/interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; + +export const ModalItem: React.FC = ({ children }) => { + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + const className = clsx( + 'w-full max-w-[384px] border-[1px] border-w-200 border-solid p-8 rounded', + isDarkTheme ? 'bg-b-900 text-w-100' : 'bg-white text-b-900' + ); + + return
{children}
; +}; diff --git a/src/entities/Modal/index.ts b/src/entities/Modal/index.ts new file mode 100644 index 000000000..c0188773a --- /dev/null +++ b/src/entities/Modal/index.ts @@ -0,0 +1,3 @@ +export { Modal } from './Modal/Modal'; +export { ModalBackgorund } from './ModalBackgorund/ModalBackgorund'; +export { ModalItem } from './ModalItem/ModalItem'; diff --git a/src/entities/Modal/interfaces.ts b/src/entities/Modal/interfaces.ts new file mode 100644 index 000000000..4653592bc --- /dev/null +++ b/src/entities/Modal/interfaces.ts @@ -0,0 +1,5 @@ +import { ILayout } from '../../entities/interfaces'; + +export interface IModal extends ILayout { + changeVisibility: () => void; +} diff --git a/src/entities/index.tsx b/src/entities/index.tsx new file mode 100644 index 000000000..9f8cc0750 --- /dev/null +++ b/src/entities/index.tsx @@ -0,0 +1,5 @@ +export { BasketGoodsItem } from './Basket'; +export { CardDescription, CardImage, FullCard, ShortCard } from './Card'; +export { Header } from './Header/Header'; +export { Layout } from './Layout/Layout'; +export { Modal, ModalBackgorund, ModalItem } from './Modal'; diff --git a/src/entities/interfaces.ts b/src/entities/interfaces.ts new file mode 100644 index 000000000..ad1858a3d --- /dev/null +++ b/src/entities/interfaces.ts @@ -0,0 +1,4 @@ +export * from './Basket/interfaces'; +export * from './Card/interfaces'; +export * from './Layout/interfaces'; +export * from './Modal/interfaces'; diff --git a/src/index.tsx b/src/index.tsx index 26d2b1437..768272fc5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './app/index.css'; +import './app/i18n/config'; import App from './app/App'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); diff --git a/src/pages/MainPage/MainPage.tsx b/src/pages/MainPage/MainPage.tsx new file mode 100644 index 000000000..0c7cdd1a1 --- /dev/null +++ b/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Logo from '../../app/logo.svg'; +import '../../app/index.css'; +import '../../app/App.css'; + +export const MainPage: React.FC = () => ( +
+
+ logo +

+ Привет, меня зовут Егор. Я — разработчик интерфейсов. +
+ Пишу на React, Angular с TypeScript. Хочу разобраться с React получше. +
+ Контакт{' '} + + @antytoto + +

+
+
+); diff --git a/src/pages/Store/Store.tsx b/src/pages/Store/Store.tsx new file mode 100644 index 000000000..ce1b8f33d --- /dev/null +++ b/src/pages/Store/Store.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import '../../app/tailwind.css'; +import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; +import { Layout, Modal } from '../../entities'; +import { nanoid } from 'nanoid'; +import { Btn, Input } from 'src/shared'; + +export const Store: React.FC = () => { + const [contextValue, setContextValue] = React.useState(defaultContext); + const [inputValue, setInputValue] = React.useState(''); + const [isModalVisible, setIsModalVisible] = React.useState(false); + + const { theme, lang } = contextValue; + const { i18n, t } = useTranslation(); + + const themeSwitchHandler = (): void => { + if (theme === 'light') { + setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); + } else { + setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); + } + }; + + const langSwitchHandler = (): void => { + if (lang === 'ru') { + setContextValue({ ...contextValue, lang: ELangVariables.EN }); + i18n.changeLanguage('en-US'); + } else { + setContextValue({ ...contextValue, lang: ELangVariables.RU }); + i18n.changeLanguage('ru-RU'); + } + }; + + // TODO: Delete this Links and translation in future + const tempLinks = [ + { + path: '/store/card', + text: t('tempLinks.toCard'), + }, + { + path: '/store/basket', + text: t('tempLinks.toBasket'), + }, + ]; + + const changeInputValue = (value: string): void => { + setInputValue(value); + }; + + const changeModalVisibility = (): void => { + setIsModalVisible(!isModalVisible); + }; + + return ( + + + +
+
+
+ +
+ + {t('mainPage.showBtn')} + +
+
+ {isModalVisible && ( + +

{inputValue}

+
+ )} +
+
+ ); +}; diff --git a/src/pages/StoreBasket/StoreBasket.tsx b/src/pages/StoreBasket/StoreBasket.tsx new file mode 100644 index 000000000..152c27501 --- /dev/null +++ b/src/pages/StoreBasket/StoreBasket.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { nanoid } from 'nanoid'; +import '../../app/tailwind.css'; +import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; +import { BasketGoodsItem, Layout } from '../../entities'; +import { goods } from '../../assets/goods'; + +export const StoreBasket: React.FC = () => { + const [contextValue, setContextValue] = React.useState(defaultContext); + + const { theme, lang } = contextValue; + const { i18n, t } = useTranslation(); + + const themeSwitchHandler = (): void => { + if (theme === 'light') { + setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); + } else { + setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); + } + }; + + const langSwitchHandler = (): void => { + if (lang === 'ru') { + setContextValue({ ...contextValue, lang: ELangVariables.EN }); + i18n.changeLanguage('en-US'); + } else { + setContextValue({ ...contextValue, lang: ELangVariables.RU }); + i18n.changeLanguage('ru-RU'); + } + }; + + // TODO: Delete this Links and translation in future + const tempLinks = [ + { + path: '/store', + text: t('tempLinks.toMain'), + }, + { + path: '/store/card', + text: t('tempLinks.toCard'), + }, + ]; + + const item = goods[0]; + + return ( + + + +
+ +
+
+
+ ); +}; diff --git a/src/pages/StoreCard/StoreCard.tsx b/src/pages/StoreCard/StoreCard.tsx new file mode 100644 index 000000000..6cf7718c6 --- /dev/null +++ b/src/pages/StoreCard/StoreCard.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { nanoid } from 'nanoid'; +import '../../app/tailwind.css'; +import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; +import { FullCard, Layout } from '../../entities'; +import { goods } from '../../assets/goods'; + +export const StoreCard: React.FC = () => { + const [contextValue, setContextValue] = React.useState(defaultContext); + + const { theme, lang } = contextValue; + const { i18n, t } = useTranslation(); + + const themeSwitchHandler = (): void => { + if (theme === 'light') { + setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); + } else { + setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); + } + }; + + const langSwitchHandler = (): void => { + if (lang === 'ru') { + setContextValue({ ...contextValue, lang: ELangVariables.EN }); + i18n.changeLanguage('en-US'); + } else { + setContextValue({ ...contextValue, lang: ELangVariables.RU }); + i18n.changeLanguage('ru-RU'); + } + }; + + // TODO: Delete this Links and translation in future + const tempLinks = [ + { + path: '/store', + text: t('tempLinks.toMain'), + }, + { + path: '/store/basket', + text: t('tempLinks.toBasket'), + }, + ]; + + const item = goods[0]; + + return ( + + + +
+ +
+
+
+ ); +}; diff --git a/src/pages/index.ts b/src/pages/index.ts new file mode 100644 index 000000000..8b557c47b --- /dev/null +++ b/src/pages/index.ts @@ -0,0 +1,4 @@ +export { MainPage } from './MainPage/MainPage'; +export { Store } from './Store/Store'; +export { StoreBasket } from './StoreBasket/StoreBasket'; +export { StoreCard } from './StoreCard/StoreCard'; diff --git a/src/components/Btns/AddToCart/AddToCart.tsx b/src/shared/Btns/AddToCartBtn/AddToCartBtn.tsx similarity index 62% rename from src/components/Btns/AddToCart/AddToCart.tsx rename to src/shared/Btns/AddToCartBtn/AddToCartBtn.tsx index b2be5b1f0..779cf3ddd 100644 --- a/src/components/Btns/AddToCart/AddToCart.tsx +++ b/src/shared/Btns/AddToCartBtn/AddToCartBtn.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import { IBtn, ICounter } from '../../interfaces'; -import { Btn, Counter } from '../../../components'; +import { IBtn, ICounter } from '../../../shared/interfaces'; +import { Btn, Counter } from '../../../shared'; -export const AddToCart: React.FC = ({ isDisabled, counter, children }) => { +export const AddToCartBtn: React.FC = ({ isDisabled, counter, children }) => { if (counter > 0) { return ; } return ( - + null} isDisabled={isDisabled}>
{children} = ({ children, isDisabled, onClick }) => { + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + + const className = clsx( + 'px-6 py-[9.5px] font-inter text-sm leading-7 disabled:bg-b-600 rounded', + isDarkTheme ? 'bg-w-900 text-b-900' : 'bg-b-900 text-w-900' + ); + + return ( + + ); +}; diff --git a/src/components/Btns/DeleteBtn/DeleteBtn.tsx b/src/shared/Btns/DeleteBtn/DeleteBtn.tsx similarity index 74% rename from src/components/Btns/DeleteBtn/DeleteBtn.tsx rename to src/shared/Btns/DeleteBtn/DeleteBtn.tsx index 677af2f1a..3eaff2bdc 100644 --- a/src/components/Btns/DeleteBtn/DeleteBtn.tsx +++ b/src/shared/Btns/DeleteBtn/DeleteBtn.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { IBtnDefault } from '../../interfaces'; +import { IBtnDefault } from '../../../shared/interfaces'; -export const DeleteBtn: React.FC = ({ isDisabled }) => ( +export const DeleteBtn: React.FC = ({ isDisabled, onClick }) => ( + ); + })} +
+ ); +}; diff --git a/src/shared/Btns/ThemeSwitcherBtn/ThemeSwitcherBtn.tsx b/src/shared/Btns/ThemeSwitcherBtn/ThemeSwitcherBtn.tsx new file mode 100644 index 000000000..4c51dca1e --- /dev/null +++ b/src/shared/Btns/ThemeSwitcherBtn/ThemeSwitcherBtn.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; + +export const ThemeSwitcherBtn: React.FC = () => { + const { theme, themeSwitchHandler } = React.useContext(StoreContext); + const imagePath = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/'; + const imageSrc = + theme === EThemeVariables.DARK ? `${imagePath}sun-white.png?raw=true` : `${imagePath}moon-dark.png?raw=true`; + + return ( + + ); +}; diff --git a/src/shared/Btns/index.ts b/src/shared/Btns/index.ts new file mode 100644 index 000000000..22ab5b98c --- /dev/null +++ b/src/shared/Btns/index.ts @@ -0,0 +1,5 @@ +export { AddToCartBtn } from './AddToCartBtn/AddToCartBtn'; +export { Btn } from './Btn/Btn'; +export { DeleteBtn } from './DeleteBtn/DeleteBtn'; +export { LangSwitcherBtn } from './LangSwitcherBtn/LangSwitcherBtn'; +export { ThemeSwitcherBtn } from './ThemeSwitcherBtn/ThemeSwitcherBtn'; diff --git a/src/components/Btns/interfaces.ts b/src/shared/Btns/interfaces.ts similarity index 91% rename from src/components/Btns/interfaces.ts rename to src/shared/Btns/interfaces.ts index 4d969668a..9fbeb9779 100644 --- a/src/components/Btns/interfaces.ts +++ b/src/shared/Btns/interfaces.ts @@ -2,6 +2,7 @@ import React from 'react'; export interface IBtnDefault { isDisabled?: boolean; + onClick: () => void; } export interface IBtn extends IBtnDefault { diff --git a/src/shared/Counter/Counter.tsx b/src/shared/Counter/Counter.tsx new file mode 100644 index 000000000..ebde7b671 --- /dev/null +++ b/src/shared/Counter/Counter.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { ICounter } from '../../shared/interfaces'; +import { EThemeVariables, StoreContext } from '../../app/StoreContext'; + +export const Counter: React.FC = ({ counter }) => { + const { t } = useTranslation(); + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + + const className = clsx( + 'min-w-[164px] max-w-fit min-h-[47px] flex justify-between items-center px-4 border-[1px] border-solid border-b-100 rounded font-inter text-sm text-b-800 leading-7', + isDarkTheme ? 'text-w-900' : 'text-b-800' + ); + + return ( +
+ + {counter} + +
+ ); +}; diff --git a/src/components/Counter/interfaces.ts b/src/shared/Counter/interfaces.ts similarity index 100% rename from src/components/Counter/interfaces.ts rename to src/shared/Counter/interfaces.ts diff --git a/src/shared/Input/Input.tsx b/src/shared/Input/Input.tsx new file mode 100644 index 000000000..70467dcb0 --- /dev/null +++ b/src/shared/Input/Input.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import clsx from 'clsx'; +import { IInput } from './interfaces'; +import { EThemeVariables, StoreContext } from '../../app/StoreContext'; + +export const Input: React.FC = ({ + value, + name, + placeholder, + required, + id, + form, + disabled, + changeInputValue, +}) => { + const [currentValue, setCurrentValue] = React.useState(''); + + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + + const className = clsx( + 'w-full p-2 border-[1px] border-solid active:outline focus:outline rounded', + isDarkTheme + ? 'bg-b-900 border-b-100 active:outline-w-900 focus:outline-w-900' + : 'bg-w-900 active:outline-b-900 border-b-200 focus:outline-b-900' + ); + + React.useEffect(() => { + setCurrentValue(value); + }, [value]); + + const changeValue = (newValue: string): void => { + setCurrentValue(newValue); + changeInputValue(newValue); + }; + + return ( + changeValue(ev.target.value)} + /> + ); +}; diff --git a/src/shared/Input/interfaces.ts b/src/shared/Input/interfaces.ts new file mode 100644 index 000000000..d16eafaff --- /dev/null +++ b/src/shared/Input/interfaces.ts @@ -0,0 +1,10 @@ +export interface IInput { + value: string; + name?: string; + placeholder?: string; + required?: boolean; + id?: string; + form?: string; + disabled?: boolean; + changeInputValue?: (value: string | undefined) => void; +} diff --git a/src/components/Logo/Logo.tsx b/src/shared/Logo/Logo.tsx similarity index 93% rename from src/components/Logo/Logo.tsx rename to src/shared/Logo/Logo.tsx index 990e7238a..3fe951521 100644 --- a/src/components/Logo/Logo.tsx +++ b/src/shared/Logo/Logo.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ELogoType, ILogo } from '../interfaces'; +import { ELogoType, ILogo } from '../../shared/interfaces'; export const Logo: React.FC = ({ type, text }) => { const imageUrl = 'https://raw.githubusercontent.com/furtivite/cdn.furtivite.github.io/refs/heads/main/images/'; diff --git a/src/components/Logo/interfaces.ts b/src/shared/Logo/interfaces.ts similarity index 100% rename from src/components/Logo/interfaces.ts rename to src/shared/Logo/interfaces.ts diff --git a/src/shared/StockStatus/StockStatus.tsx b/src/shared/StockStatus/StockStatus.tsx new file mode 100644 index 000000000..570ecfdd0 --- /dev/null +++ b/src/shared/StockStatus/StockStatus.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { IStockStatus } from '../../shared/interfaces'; + +export const StockStatus: React.FC = ({ isInStock }) => { + const { t } = useTranslation(); + return ( + + {isInStock ? t('card.stockStatus.in') : t('card.stockStatus.out')} + + ); +}; diff --git a/src/components/StockStatus/interfaces.ts b/src/shared/StockStatus/interfaces.ts similarity index 100% rename from src/components/StockStatus/interfaces.ts rename to src/shared/StockStatus/interfaces.ts diff --git a/src/shared/button/Button.tsx b/src/shared/button/Button.tsx deleted file mode 100644 index 7548da044..000000000 --- a/src/shared/button/Button.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { FC } from 'react'; -import cn from 'clsx'; -import { sum } from './sum'; -import './button.css'; - -interface ButtonProps { - primary?: boolean; - backgroundColor?: string | null; - size?: string; - label: string; -} -/** - * Primary UI component for user interaction - */ - -export const Button: FC = ({ primary, backgroundColor, size, label, ...props }) => { - const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; - - const onClick = () => { - sum(4, 5); - }; - - return ( - - ); -}; diff --git a/src/shared/button/button.css b/src/shared/button/button.css deleted file mode 100644 index 61c9c11f3..000000000 --- a/src/shared/button/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; -} -.storybook-button--primary { - color: white; - background-color: #1ea7fd; -} -.storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; -} -.storybook-button--small { - font-size: 12px; - padding: 10px 16px; -} -.storybook-button--medium { - font-size: 14px; - padding: 11px 20px; -} -.storybook-button--large { - font-size: 16px; - padding: 12px 24px; -} diff --git a/src/shared/button/sum.test.ts b/src/shared/button/sum.test.ts deleted file mode 100644 index b0c67eb9b..000000000 --- a/src/shared/button/sum.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { sum } from './sum'; - -test('adds 4 + 5 to equal 9', () => { - expect(sum(4, 5)).toBe(9); -}); diff --git a/src/shared/button/sum.ts b/src/shared/button/sum.ts deleted file mode 100644 index 8073d27cc..000000000 --- a/src/shared/button/sum.ts +++ /dev/null @@ -1 +0,0 @@ -export const sum = (a: number, b: number): number => a + b; diff --git a/src/shared/index.ts b/src/shared/index.ts new file mode 100644 index 000000000..1695a4383 --- /dev/null +++ b/src/shared/index.ts @@ -0,0 +1,5 @@ +export * from './Btns'; +export { Counter } from './Counter/Counter'; +export { Input } from './Input/Input'; +export { Logo } from './Logo/Logo'; +export { StockStatus } from './StockStatus/StockStatus'; diff --git a/src/components/interfaces.ts b/src/shared/interfaces.ts similarity index 50% rename from src/components/interfaces.ts rename to src/shared/interfaces.ts index a8b3a3f6e..f0efbe17f 100644 --- a/src/components/interfaces.ts +++ b/src/shared/interfaces.ts @@ -1,8 +1,4 @@ -export * from './Basket/interfaces'; export * from './Btns/interfaces'; -export * from './Card/interfaces'; export * from './Counter/interfaces'; -export * from './Layout/interfaces'; export * from './Logo/interfaces'; -export * from './Modal/interfaces'; export * from './StockStatus/interfaces'; diff --git a/src/stories/AddToCart.stories.ts b/src/stories/AddToCartBtn.stories.ts similarity index 71% rename from src/stories/AddToCart.stories.ts rename to src/stories/AddToCartBtn.stories.ts index 3f07fdb50..917caa407 100644 --- a/src/stories/AddToCart.stories.ts +++ b/src/stories/AddToCartBtn.stories.ts @@ -1,9 +1,9 @@ import type { Meta } from '@storybook/react'; -import { AddToCart } from '../components'; +import { AddToCartBtn } from '../shared'; -const meta: Meta = { - title: 'UI/Btns/AddToCart', - component: AddToCart, +const meta: Meta = { + title: 'UI/Btns/AddToCartBtn', + component: AddToCartBtn, tags: ['autodocs'], }; diff --git a/src/stories/BasketGoodsItem.stories.ts b/src/stories/BasketGoodsItem.stories.ts index a915b8fec..0cbf6b345 100644 --- a/src/stories/BasketGoodsItem.stories.ts +++ b/src/stories/BasketGoodsItem.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { BasketGoodsItem } from '../components/index'; +import { BasketGoodsItem } from '../entities'; import { goods } from '../assets/goods'; const meta: Meta = { diff --git a/src/stories/Btn.stories.ts b/src/stories/Btn.stories.ts index e255e73fd..e515896de 100644 --- a/src/stories/Btn.stories.ts +++ b/src/stories/Btn.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { Btn } from '../components'; +import { Btn } from '../shared'; const meta: Meta = { title: 'UI/Btns/Btn', diff --git a/src/stories/CardDescription.stories.ts b/src/stories/CardDescription.stories.ts index 7af3fa19c..67dbad331 100644 --- a/src/stories/CardDescription.stories.ts +++ b/src/stories/CardDescription.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { CardDescription } from '../components/index'; +import { CardDescription } from '../entities/index'; import { goods } from '../assets/goods'; const meta: Meta = { diff --git a/src/stories/CardImage.stories.ts b/src/stories/CardImage.stories.ts index 280d8867e..644ee2816 100644 --- a/src/stories/CardImage.stories.ts +++ b/src/stories/CardImage.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { CardImage } from '../components/index'; +import { CardImage } from '../entities/index'; import { goods } from '../assets/goods'; const meta: Meta = { diff --git a/src/stories/Counter.stories.ts b/src/stories/Counter.stories.ts index 6207859a2..7a923e3f0 100644 --- a/src/stories/Counter.stories.ts +++ b/src/stories/Counter.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { Counter } from '../components'; +import { Counter } from '../shared'; const meta: Meta = { title: 'UI/Cards/components/Counter', diff --git a/src/stories/DeleteBtn.stories.ts b/src/stories/DeleteBtn.stories.ts index acd18e505..1702e7554 100644 --- a/src/stories/DeleteBtn.stories.ts +++ b/src/stories/DeleteBtn.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { DeleteBtn } from '../components'; +import { DeleteBtn } from '../shared'; const meta: Meta = { title: 'UI/Btns/DeleteBtn', diff --git a/src/stories/FullCard.stories.ts b/src/stories/FullCard.stories.ts index 2ffd925ab..49900afcc 100644 --- a/src/stories/FullCard.stories.ts +++ b/src/stories/FullCard.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { FullCard } from '../components/index'; +import { FullCard } from '../entities'; import { goods } from '../assets/goods'; const meta: Meta = { diff --git a/src/stories/Header.stories.ts b/src/stories/Header.stories.ts index afe5b5883..6c1cfcb75 100644 --- a/src/stories/Header.stories.ts +++ b/src/stories/Header.stories.ts @@ -1,10 +1,12 @@ import type { Meta } from '@storybook/react'; -import { Header } from '../components'; +import { Header } from '../entities'; +import StoreDecorator from '../../.storybook/decorators/StoreDecorator'; const meta: Meta = { - title: 'UI/Header', component: Header, + decorators: [StoreDecorator], tags: ['autodocs'], + title: 'UI/Header', }; export default meta; diff --git a/src/stories/Input.stories.ts b/src/stories/Input.stories.ts new file mode 100644 index 000000000..5af1c36b0 --- /dev/null +++ b/src/stories/Input.stories.ts @@ -0,0 +1,35 @@ +import type { Meta } from '@storybook/react'; +import { Input } from '../shared'; + +const meta: Meta = { + component: Input, + decorators: [], + tags: ['autodocs'], + title: 'UI/Input', +}; + +export default meta; + +export const Active = { + args: { + value: '', + name: 'string', + placeholder: 'Some placeholder', + required: false, + id: 'string', + form: 'form', + disabled: false, + }, +}; + +export const Disabled = { + args: { + value: '', + name: 'string', + placeholder: 'Some placeholder', + required: false, + id: 'string', + form: 'form', + disabled: true, + }, +}; diff --git a/src/stories/LangSwitcherBtn.stories.ts b/src/stories/LangSwitcherBtn.stories.ts new file mode 100644 index 000000000..e54d2ca5e --- /dev/null +++ b/src/stories/LangSwitcherBtn.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { LangSwitcherBtn } from '../shared'; +import StoreDecorator from '../../.storybook/decorators/StoreDecorator'; + +const meta: Meta = { + component: LangSwitcherBtn, + decorators: [StoreDecorator], + tags: ['autodocs'], + title: 'UI/Btns/LangSwitcherBtn', +}; + +export default meta; + +export const Default = { + args: {}, +}; diff --git a/src/stories/Layout.stories.ts b/src/stories/Layout.stories.ts index 5b87cbdf4..8d7fd730a 100644 --- a/src/stories/Layout.stories.ts +++ b/src/stories/Layout.stories.ts @@ -1,10 +1,12 @@ import type { Meta } from '@storybook/react'; -import { Layout } from '../components'; +import { Layout } from '../entities'; +import StoreDecorator from '../../.storybook/decorators/StoreDecorator'; const meta: Meta = { - title: 'UI/Layout', component: Layout, + decorators: [StoreDecorator], tags: ['autodocs'], + title: 'UI/Layout', }; export default meta; diff --git a/src/stories/Logo.stories.ts b/src/stories/Logo.stories.ts index bd2a08ba9..e8c61ec82 100644 --- a/src/stories/Logo.stories.ts +++ b/src/stories/Logo.stories.ts @@ -1,6 +1,6 @@ import type { Meta } from '@storybook/react'; -import { Logo } from '../components'; -import { ELogoType } from '../components/interfaces'; +import { Logo } from '../shared'; +import { ELogoType } from '../shared/interfaces'; const meta: Meta = { title: 'UI/Logo', diff --git a/src/stories/Modal.stories.ts b/src/stories/Modal.stories.ts index d1b346bca..5839bac36 100644 --- a/src/stories/Modal.stories.ts +++ b/src/stories/Modal.stories.ts @@ -1,24 +1,16 @@ import type { Meta } from '@storybook/react'; -import { Modal } from '../components'; +import { Modal } from '../entities'; const meta: Meta = { - title: 'UI/Modal', + title: 'UI/Modal/Modal', component: Modal, tags: ['autodocs'], }; export default meta; -export const Visible = { +export const Default = { args: { children: 'Some content', - isVisible: true, - }, -}; - -export const Hidden = { - args: { - children: 'Some content', - isVisible: false, }, }; diff --git a/src/stories/ModalBackgorund.stories.ts b/src/stories/ModalBackgorund.stories.ts new file mode 100644 index 000000000..0a5be5837 --- /dev/null +++ b/src/stories/ModalBackgorund.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { ModalBackgorund } from '../entities'; + +const meta: Meta = { + title: 'UI/Modal/ModalBackgorund', + component: ModalBackgorund, + tags: ['autodocs'], +}; + +export default meta; + +export const Default = { + args: { + children: 'Some content', + }, +}; diff --git a/src/stories/ModalItem.stories.ts b/src/stories/ModalItem.stories.ts new file mode 100644 index 000000000..52200c263 --- /dev/null +++ b/src/stories/ModalItem.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { ModalItem } from '../entities'; + +const meta: Meta = { + title: 'UI/Modal/ModalItem', + component: ModalItem, + tags: ['autodocs'], +}; + +export default meta; + +export const Default = { + args: { + children: 'Some content', + }, +}; diff --git a/src/stories/ShortCard.stories.ts b/src/stories/ShortCard.stories.ts index 5836273ce..89e985b95 100644 --- a/src/stories/ShortCard.stories.ts +++ b/src/stories/ShortCard.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { ShortCard } from '../components/index'; +import { ShortCard } from '../entities'; import { goods } from '../assets/goods'; const meta: Meta = { diff --git a/src/stories/StockStatus.stories.ts b/src/stories/StockStatus.stories.ts index 737d7b2cd..6e954f3d8 100644 --- a/src/stories/StockStatus.stories.ts +++ b/src/stories/StockStatus.stories.ts @@ -1,5 +1,5 @@ import type { Meta } from '@storybook/react'; -import { StockStatus } from '../components'; +import { StockStatus } from '../shared'; const meta: Meta = { title: 'UI/Cards/components/StockItemStatus', diff --git a/src/stories/ThemeSwitcherBtn.stories.ts b/src/stories/ThemeSwitcherBtn.stories.ts new file mode 100644 index 000000000..1bf10daaf --- /dev/null +++ b/src/stories/ThemeSwitcherBtn.stories.ts @@ -0,0 +1,16 @@ +import type { Meta } from '@storybook/react'; +import { ThemeSwitcherBtn } from '../shared'; +import StoreDecorator from '../../.storybook/decorators/StoreDecorator'; + +const meta: Meta = { + component: ThemeSwitcherBtn, + decorators: [StoreDecorator], + tags: ['autodocs'], + title: 'UI/Btns/ThemeSwitcherBtn', +}; + +export default meta; + +export const Default = { + args: {}, +}; diff --git a/tailwind.config.js b/tailwind.config.js index 7ee51fb68..87845bdaf 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,30 +2,32 @@ module.exports = { content: ['./src/**/*.{js,ts,jsx,tsx,html}'], theme: { - colors: { - b: { - 100: '#E6E7E8', - 200: '#B6B7BC', - 500: '#5C5F6A', - 600: '#474B57', - 800: '#202533', - 900: '#0E1422', - }, - w: { - 100: '#F6F6F6', - 900: '#FFFFFF', - }, - blue: { - 400: '#A3BEF8', - }, - yellow: { - 400: '#FFD58A', - }, - green: { - 400: '#83B18B', + extend: { + colors: { + b: { + 100: '#E6E7E8', + 200: '#B6B7BC', + 500: '#5C5F6A', + 600: '#474B57', + 800: '#202533', + 900: '#0E1422', + }, + w: { + 100: '#F6F6F6', + 200: '#E9E9EB', + 900: '#FFFFFF', + }, + blue: { + 400: '#A3BEF8', + }, + yellow: { + 400: '#FFD58A', + }, + green: { + 400: '#83B18B', + }, }, }, - extend: {}, fontFamily: { inter: ['Inter', 'sans-serif'], manrope: ['Manrope', 'sans-serif'], From 8d225d320d9414e09682b4808b82deb4643d3311 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Thu, 16 Jan 2025 13:20:56 +0300 Subject: [PATCH 14/30] fix: react-rouyter at gh-pages problem --- package.json | 3 ++- src/app/Routing.tsx | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f3c056d89..3874102ca 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "Start repo with required configuration", "main": "src/index.tsx", - "author": "Igor ", + "author": "Egor ", + "homepage": "https://furtivite.github.io/", "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", diff --git a/src/app/Routing.tsx b/src/app/Routing.tsx index e06530e43..889ed46ff 100644 --- a/src/app/Routing.tsx +++ b/src/app/Routing.tsx @@ -4,21 +4,22 @@ import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; export const Routing: React.FC = () => { + const home = 'furtivite.github.io'; const pages: RouteProps[] = [ { - path: '/', + path: `${home}/`, element: , }, { - path: '/store', + path: `${home}/store/`, element: , }, { - path: '/store/basket', + path: `${home}/store/basket`, element: , }, { - path: '/store/card', + path: `${home}/store/card`, element: , }, ]; From ce9b68dbdd552cb96a53a2f004f4f5793aa8bc58 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Thu, 16 Jan 2025 13:24:06 +0300 Subject: [PATCH 15/30] Revert "fix: react-rouyter at gh-pages problem" This reverts commit 8d225d320d9414e09682b4808b82deb4643d3311. --- package.json | 3 +-- src/app/Routing.tsx | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3874102ca..f3c056d89 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "1.0.0", "description": "Start repo with required configuration", "main": "src/index.tsx", - "author": "Egor ", - "homepage": "https://furtivite.github.io/", + "author": "Igor ", "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", diff --git a/src/app/Routing.tsx b/src/app/Routing.tsx index 889ed46ff..e06530e43 100644 --- a/src/app/Routing.tsx +++ b/src/app/Routing.tsx @@ -4,22 +4,21 @@ import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; export const Routing: React.FC = () => { - const home = 'furtivite.github.io'; const pages: RouteProps[] = [ { - path: `${home}/`, + path: '/', element: , }, { - path: `${home}/store/`, + path: '/store', element: , }, { - path: `${home}/store/basket`, + path: '/store/basket', element: , }, { - path: `${home}/store/card`, + path: '/store/card', element: , }, ]; From 8bc192e1a1c7d3ce94d0e4c3d9e4566ab5e94a56 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Thu, 16 Jan 2025 13:27:19 +0300 Subject: [PATCH 16/30] feat: changed mainpage --- src/app/Routing.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/Routing.tsx b/src/app/Routing.tsx index e06530e43..e47a7f26b 100644 --- a/src/app/Routing.tsx +++ b/src/app/Routing.tsx @@ -5,12 +5,12 @@ import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; export const Routing: React.FC = () => { const pages: RouteProps[] = [ + // { + // path: '/', + // element: , + // }, { path: '/', - element: , - }, - { - path: '/store', element: , }, { From d5d9e35e4d5ae322174a2af3088bda30169b7cb7 Mon Sep 17 00:00:00 2001 From: Egor Levchenko Date: Mon, 20 Jan 2025 08:12:00 +0300 Subject: [PATCH 17/30] feat: move routing --- src/app/App.tsx | 33 +++++++++++++++++++++++++++++++-- src/app/Routing.tsx | 35 ----------------------------------- 2 files changed, 31 insertions(+), 37 deletions(-) delete mode 100644 src/app/Routing.tsx diff --git a/src/app/App.tsx b/src/app/App.tsx index d3c458edf..519afb24f 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,8 +1,37 @@ import React from 'react'; -import { Routing } from './Routing'; +import { nanoid } from 'nanoid'; +import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; +import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; function App() { - return ; + const pages: RouteProps[] = [ + // { + // path: '/', + // element: , + // }, + { + path: '/', + element: , + }, + { + path: '/store/basket', + element: , + }, + { + path: '/store/card', + element: , + }, + ]; + + return ( + + + {pages.map((item) => ( + + ))} + + + ); } export default App; diff --git a/src/app/Routing.tsx b/src/app/Routing.tsx deleted file mode 100644 index e47a7f26b..000000000 --- a/src/app/Routing.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { nanoid } from 'nanoid'; -import React from 'react'; -import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; -import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; - -export const Routing: React.FC = () => { - const pages: RouteProps[] = [ - // { - // path: '/', - // element: , - // }, - { - path: '/', - element: , - }, - { - path: '/store/basket', - element: , - }, - { - path: '/store/card', - element: , - }, - ]; - - return ( - - - {pages.map((item) => ( - - ))} - - - ); -}; From ab6b4a44a8e4c0b7bc2b58e9a8994409a7db3ed7 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:32:15 +0300 Subject: [PATCH 18/30] feat: create homework 4 (#6) * fix: edit temp link * feat: delete default app start page with sources * feat: move Store to App * feat: create Container * feat: Create init Goods List * feat: make portal for modal window * feat: changed bg for photo * feat: add new goods * feat: generate faatures * feat: add translation to add more button --- src/app/App.css | 38 ------ src/app/App.tsx | 94 ++++++++++++--- src/app/i18n/translation.ts | 8 ++ src/app/index.css | 13 -- src/app/logo.svg | 1 - src/assets/goods.ts | 137 +++++++++++++++++++++- src/entities/Card/ShortCard/ShortCard.tsx | 26 ++-- src/entities/Container/Container.tsx | 6 + src/entities/Header/Header.tsx | 6 +- src/entities/index.tsx | 1 + src/features/generateAwailibleColors.ts | 20 ++++ src/features/generateAwailibleSizes.ts | 39 ++++++ src/features/generateImageAddress.ts | 6 + src/features/generateRandomCard.ts | 24 ++++ src/features/generateRandomCardList.ts | 12 ++ src/features/generateRandomNumber.ts | 3 + src/pages/MainPage/MainPage.tsx | 22 ---- src/pages/Store/Store.tsx | 80 ++++--------- src/pages/StoreBasket/StoreBasket.tsx | 74 ++---------- src/pages/StoreCard/StoreCard.tsx | 85 +++----------- src/pages/StoreList/StoreList.tsx | 45 +++++++ src/pages/index.ts | 2 +- 22 files changed, 447 insertions(+), 295 deletions(-) delete mode 100644 src/app/App.css delete mode 100644 src/app/index.css delete mode 100644 src/app/logo.svg create mode 100644 src/entities/Container/Container.tsx create mode 100644 src/features/generateAwailibleColors.ts create mode 100644 src/features/generateAwailibleSizes.ts create mode 100644 src/features/generateImageAddress.ts create mode 100644 src/features/generateRandomCard.ts create mode 100644 src/features/generateRandomCardList.ts create mode 100644 src/features/generateRandomNumber.ts delete mode 100644 src/pages/MainPage/MainPage.tsx create mode 100644 src/pages/StoreList/StoreList.tsx diff --git a/src/app/App.css b/src/app/App.css deleted file mode 100644 index 78b8850cf..000000000 --- a/src/app/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/app/App.tsx b/src/app/App.tsx index 519afb24f..ba17dc013 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,36 +1,102 @@ import React from 'react'; import { nanoid } from 'nanoid'; import { BrowserRouter, Route, RouteProps, Routes } from 'react-router'; -import { MainPage, Store, StoreBasket, StoreCard } from 'src/pages'; +import { useTranslation } from 'react-i18next'; +import './tailwind.css'; +import { Layout } from '../entities'; +import { Store, StoreBasket, StoreCard, StoreList } from '../pages'; +import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from './StoreContext'; function App() { + const [contextValue, setContextValue] = React.useState(defaultContext); const pages: RouteProps[] = [ - // { - // path: '/', - // element: , - // }, { path: '/', element: , }, { - path: '/store/basket', + path: '/basket', element: , }, { - path: '/store/card', + path: '/card/:id', element: , }, + { + path: '/list', + element: , + }, + ]; + const { theme, lang } = contextValue; + const { i18n, t } = useTranslation(); + + const themeSwitchHandler = (): void => { + if (theme === 'light') { + setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); + } else { + setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); + } + }; + + const langSwitchHandler = (): void => { + if (lang === 'ru') { + setContextValue({ ...contextValue, lang: ELangVariables.EN }); + i18n.changeLanguage('en-US'); + } else { + setContextValue({ ...contextValue, lang: ELangVariables.RU }); + i18n.changeLanguage('ru-RU'); + } + }; + + // TODO: Delete this Links and translation in future + const tempLinks = [ + { + path: '/', + text: t('tempLinks.toMain'), + }, + { + path: '/card/1', + text: t('tempLinks.toCard'), + }, + { + path: '/basket', + text: t('tempLinks.toBasket'), + }, + { + path: '/list', + text: t('tempLinks.toList'), + }, ]; + const currentUrl = window.location.pathname; + // END TODO return ( - - - {pages.map((item) => ( - - ))} - - + + + {/* TODO: Delete this Links and translation in future */} + + {/* END TODO */} + + + {pages.map((item) => ( + + ))} + + + + ); } diff --git a/src/app/i18n/translation.ts b/src/app/i18n/translation.ts index 7f806a501..df00fc0e8 100644 --- a/src/app/i18n/translation.ts +++ b/src/app/i18n/translation.ts @@ -6,6 +6,7 @@ export const resources = { toMain: 'to Main Page', toCard: 'to Card', toBasket: 'to Basket', + toList: 'to Goods List', }, card: { stockStatus: { @@ -39,6 +40,9 @@ export const resources = { placeholder: 'Enter the value', showBtn: 'Show modal', }, + listPage: { + addMore: 'Add more', + }, modal: { close: 'Close modal window', }, @@ -51,6 +55,7 @@ export const resources = { toMain: 'На главную', toCard: 'В карточку', toBasket: 'В корзину', + toList: 'К списку товаров', }, card: { stockStatus: { @@ -84,6 +89,9 @@ export const resources = { placeholder: 'Введите значение', showBtn: 'Показать модальное окно', }, + listPage: { + addMore: 'Добавить ещё', + }, modal: { close: 'Закрыть модальное окно', }, diff --git a/src/app/index.css b/src/app/index.css deleted file mode 100644 index 4b326a5a4..000000000 --- a/src/app/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', - 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', - 'Helvetica Neue', sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/app/logo.svg b/src/app/logo.svg deleted file mode 100644 index 9dfc1c058..000000000 --- a/src/app/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/goods.ts b/src/assets/goods.ts index 635356848..d436b245a 100644 --- a/src/assets/goods.ts +++ b/src/assets/goods.ts @@ -1,8 +1,9 @@ +import generateAwailibleSizes from '../features/generateAwailibleSizes'; +import generateImageAddress from '../features/generateImageAddress'; +import generateRandomNumber from '../features/generateRandomNumber'; +import generateAwailibleColors from '../features/generateAwailibleColors'; import { EGoodsSizes, IGoodsItem } from '../entities/interfaces'; -const image1 = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/1/small.png?raw=true'; -const image1big = 'https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/1/big.png?raw=true'; - export const goods: IGoodsItem[] = [ { id: 1, @@ -13,9 +14,135 @@ export const goods: IGoodsItem[] = [ "The classic black color never goes out of style. Whether you're dressing up for a special occasion or keeping it casual, these black t-shirts are the perfect choice, effortlessly complementing any outfit.", ], price: 75, - imageListing: image1, - imageFull: [image1big, image1big], + imageListing: generateImageAddress(1, false), + imageFull: [generateImageAddress(1, true), generateImageAddress(1, true)], colors: ['bg-blue-400', 'bg-yellow-400', 'bg-green-400'], sizes: [EGoodsSizes.S, EGoodsSizes.M, EGoodsSizes.XL, EGoodsSizes.XXL], }, + { + id: 2, + isInStock: generateRandomNumber(0, 3) > 0 ? true : false, + title: 'Some cool goods 2', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(2, false), + imageFull: [generateImageAddress(2, true), generateImageAddress(2, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 3, + isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + title: 'Some cool goods 3', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(3, false), + imageFull: [generateImageAddress(3, true), generateImageAddress(3, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 4, + isInStock: generateRandomNumber(0, 12) > 0 ? true : false, + title: 'Some cool goods 4', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(4, false), + imageFull: [generateImageAddress(4, true), generateImageAddress(4, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 5, + isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + title: 'Some cool goods 5', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(5, false), + imageFull: [generateImageAddress(5, true), generateImageAddress(5, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 6, + isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + title: 'Some cool goods 6', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(6, false), + imageFull: [generateImageAddress(6, true), generateImageAddress(6, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 7, + isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + title: 'Some cool goods 7', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(7, false), + imageFull: [generateImageAddress(7, true), generateImageAddress(7, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 8, + isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + title: 'Some cool goods 8', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(8, false), + imageFull: [generateImageAddress(8, true), generateImageAddress(8, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: generateAwailibleSizes(true), + }, + { + id: 9, + isInStock: generateRandomNumber(0, 8) > 0 ? true : false, + title: 'Some cool goods 9', + details: [ + `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + ], + price: generateRandomNumber(50, 250), + imageListing: generateImageAddress(9, false), + imageFull: [generateImageAddress(9, true), generateImageAddress(9, true)], + colors: generateAwailibleColors(generateRandomNumber(1, 7)), + sizes: [EGoodsSizes.S, EGoodsSizes.M, EGoodsSizes.XXL], + }, + // { + // id: 10, + // isInStock: generateRandomNumber(0, 1) > 0 ? true : false, + // title: 'Some cool goods 10', + // details: [ + // `Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla eu metus accumsan, tempus eros vitae, pharetra ex. Nam risus eros, sagittis et rhoncus vitae, sollicitudin non mauris. Vivamus ultrices commodo magna, ac pulvinar sem consectetur eget. Nunc efficitur lacus quis erat ultrices interdum. Fusce ultricies, mauris id placerat consectetur, libero purus dignissim metus, eget cursus ipsum ex vitae enim. Quisque faucibus condimentum enim, nec vehicula nisi consequat ut. Vestibulum sollicitudin, ante sit amet mollis condimentum, velit dolor volutpat leo, consequat volutpat nibh nibh ac nibh. Aenean bibendum sollicitudin mi, sit amet laoreet justo convallis sit amet. Nullam ac convallis justo, ut sollicitudin felis. Praesent a tempor ex. Vivamus iaculis vel leo id sagittis. Maecenas quis quam vitae dui viverra cursus. Vivamus sed est elementum, dignissim dui sed, consequat tellus.`, + // `Curabitur tincidunt ex vel magna iaculis varius. Duis eleifend ligula vitae lectus cursus, eu luctus leo rutrum. Pellentesque ullamcorper lobortis velit ultrices hendrerit. In eleifend efficitur ante vitae dignissim. Sed lacus ipsum, tincidunt at justo sit amet, finibus congue arcu. Pellentesque sit amet malesuada tellus. Suspendisse eget vestibulum odio. Nam vitae euismod mauris. Donec justo libero, pharetra sit amet lobortis ac, ornare in urna. Praesent vitae leo sit amet libero efficitur auctor in a metus. Suspendisse quis pellentesque tellus, vel tristique lorem. Aliquam efficitur arcu ut aliquam efficitur. Praesent sit amet tortor ut erat mollis eleifend sed vel sapien.`, + // ], + // price: generateRandomNumber(50, 250), + // imageListing: generateImageAddress(10, false), + // imageFull: [generateImageAddress(10, true), generateImageAddress(10, true)], + // colors: generateAwailibleColors(generateRandomNumber(1, 7)), + // sizes: generateAwailibleSizes(true), + // }, ]; diff --git a/src/entities/Card/ShortCard/ShortCard.tsx b/src/entities/Card/ShortCard/ShortCard.tsx index 60603ab5d..dde846e38 100644 --- a/src/entities/Card/ShortCard/ShortCard.tsx +++ b/src/entities/Card/ShortCard/ShortCard.tsx @@ -3,25 +3,29 @@ import { useTranslation } from 'react-i18next'; import { IGoodsItem } from '../../../entities/interfaces'; import { AddToCartBtn, StockStatus } from '../../../shared'; -export const ShortCard: React.FC = ({ isInStock, title, price, details, imageListing }) => { +export const ShortCard: React.FC = ({ id, isInStock, title, price, details, imageListing }) => { const { t } = useTranslation(); return ( ); }; diff --git a/src/entities/Container/Container.tsx b/src/entities/Container/Container.tsx new file mode 100644 index 000000000..23f2ffbd4 --- /dev/null +++ b/src/entities/Container/Container.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { ILayout } from '../../entities/interfaces'; + +export const Container: React.FC = ({ children }: ILayout) => ( +
{children}
+); diff --git a/src/entities/Header/Header.tsx b/src/entities/Header/Header.tsx index 27132def6..0fa9f2526 100644 --- a/src/entities/Header/Header.tsx +++ b/src/entities/Header/Header.tsx @@ -9,7 +9,7 @@ export const Header: React.FC = () => { const { theme } = React.useContext(StoreContext); const isDarkTheme = theme === EThemeVariables.DARK; const className = clsx( - 'sticky top-0 py-[22px] border-b-[1px] border-solid', + 'sticky top-0 py-[22px] border-b-[1px] border-solid z-90', isDarkTheme ? 'bg-black border-b-900' : 'bg-white border-b-100' ); const { t } = useTranslation(); @@ -17,7 +17,9 @@ export const Header: React.FC = () => { return (
- + + +
diff --git a/src/entities/index.tsx b/src/entities/index.tsx index 9f8cc0750..f9785fd77 100644 --- a/src/entities/index.tsx +++ b/src/entities/index.tsx @@ -1,5 +1,6 @@ export { BasketGoodsItem } from './Basket'; export { CardDescription, CardImage, FullCard, ShortCard } from './Card'; +export { Container } from './Container/Container'; export { Header } from './Header/Header'; export { Layout } from './Layout/Layout'; export { Modal, ModalBackgorund, ModalItem } from './Modal'; diff --git a/src/features/generateAwailibleColors.ts b/src/features/generateAwailibleColors.ts new file mode 100644 index 000000000..b0ad073b4 --- /dev/null +++ b/src/features/generateAwailibleColors.ts @@ -0,0 +1,20 @@ +const generateAwailibleColors = (count: number): string[] => { + switch (count) { + case 1: + return ['bg-yellow-400']; + case 2: + return ['bg-green-400']; + case 3: + return ['bg-blue-400']; + case 4: + return ['bg-yellow-400', 'bg-green-400']; + case 5: + return ['bg-blue-400', 'bg-green-400']; + case 6: + return ['bg-blue-400', 'bg-yellow-400']; + default: + return ['bg-blue-400', 'bg-yellow-400', 'bg-green-400']; + } +}; + +export default generateAwailibleColors; diff --git a/src/features/generateAwailibleSizes.ts b/src/features/generateAwailibleSizes.ts new file mode 100644 index 000000000..1d55b5db3 --- /dev/null +++ b/src/features/generateAwailibleSizes.ts @@ -0,0 +1,39 @@ +import { EGoodsSizes } from 'src/entities/interfaces'; +import generateRandomNumber from './generateRandomNumber'; + +const generateRandomSizes = (number: number): EGoodsSizes[] => { + switch (number) { + case 1: + return [EGoodsSizes.XXL]; + case 2: + return [EGoodsSizes.XL]; + case 3: + return [EGoodsSizes.M]; + case 4: + return [EGoodsSizes.S]; + case 5: + return [EGoodsSizes.S, EGoodsSizes.XL]; + case 6: + return [EGoodsSizes.M, EGoodsSizes.XL]; + case 7: + return [EGoodsSizes.S, EGoodsSizes.XXL]; + case 8: + return [EGoodsSizes.XL, EGoodsSizes.XXL]; + case 9: + return [EGoodsSizes.S, EGoodsSizes.M]; + case 10: + return [EGoodsSizes.M, EGoodsSizes.XL, EGoodsSizes.XXL]; + case 11: + return [EGoodsSizes.S, EGoodsSizes.XL, EGoodsSizes.XXL]; + case 12: + return [EGoodsSizes.S, EGoodsSizes.M, EGoodsSizes.XXL]; + case 13: + return [EGoodsSizes.S, EGoodsSizes.M, EGoodsSizes.XL]; + default: + return [EGoodsSizes.S, EGoodsSizes.M, EGoodsSizes.XL, EGoodsSizes.XXL]; + } +}; + +const generateAwailibleSizes = (inStock: boolean) => (inStock ? generateRandomSizes(generateRandomNumber(0, 14)) : []); + +export default generateAwailibleSizes; diff --git a/src/features/generateImageAddress.ts b/src/features/generateImageAddress.ts new file mode 100644 index 000000000..fc73259e4 --- /dev/null +++ b/src/features/generateImageAddress.ts @@ -0,0 +1,6 @@ +const generateImageAddress = (id: number, isBig: boolean): string => + `https://github.com/furtivite/cdn.furtivite.github.io/blob/main/images/goods/${id}/${ + isBig ? 'big' : 'small' + }.png?raw=true`; + +export default generateImageAddress; diff --git a/src/features/generateRandomCard.ts b/src/features/generateRandomCard.ts new file mode 100644 index 000000000..144848de3 --- /dev/null +++ b/src/features/generateRandomCard.ts @@ -0,0 +1,24 @@ +import { goods } from '../assets/goods'; +import { EGoodsSizes, IGoodsItem } from '../entities/interfaces'; +import generateRandomNumber from './generateRandomNumber'; +import generateAwailibleSizes from './generateAwailibleSizes'; +import generateAwailibleColors from './generateAwailibleColors'; + +const generateRandomCard = (lastId: number): IGoodsItem => { + const useCurrentGood: IGoodsItem = goods[generateRandomNumber(0, 9)]; + const newPrice: number = generateRandomNumber(50, 250); + const isInStock: boolean = generateRandomNumber(0, 1) > 0 ? true : false; + const awailibleSizes: EGoodsSizes[] = generateAwailibleSizes(isInStock); + const awailibleColors: string[] = generateAwailibleColors(generateRandomNumber(1, 7)); + + return { + ...useCurrentGood, + id: lastId + 1, + price: newPrice, + isInStock: isInStock, + sizes: awailibleSizes, + colors: awailibleColors, + }; +}; + +export default generateRandomCard; diff --git a/src/features/generateRandomCardList.ts b/src/features/generateRandomCardList.ts new file mode 100644 index 000000000..e64877792 --- /dev/null +++ b/src/features/generateRandomCardList.ts @@ -0,0 +1,12 @@ +import { IGoodsItem } from '../entities/interfaces'; +import generateRandomCard from './generateRandomCard'; + +const generateRandomCardList = (lastId: number, count: number): IGoodsItem[] => { + const newArr: IGoodsItem[] = []; + for (let i = 1; i <= count; i++) { + newArr.push(generateRandomCard(lastId + i)); + } + return newArr; +}; + +export default generateRandomCardList; diff --git a/src/features/generateRandomNumber.ts b/src/features/generateRandomNumber.ts new file mode 100644 index 000000000..ecdd43b91 --- /dev/null +++ b/src/features/generateRandomNumber.ts @@ -0,0 +1,3 @@ +const generateRandomNumber = (max: number, min: number): number => Math.floor(Math.random() * (max - min)) + min; + +export default generateRandomNumber; diff --git a/src/pages/MainPage/MainPage.tsx b/src/pages/MainPage/MainPage.tsx deleted file mode 100644 index 0c7cdd1a1..000000000 --- a/src/pages/MainPage/MainPage.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import Logo from '../../app/logo.svg'; -import '../../app/index.css'; -import '../../app/App.css'; - -export const MainPage: React.FC = () => ( -
-
- logo -

- Привет, меня зовут Егор. Я — разработчик интерфейсов. -
- Пишу на React, Angular с TypeScript. Хочу разобраться с React получше. -
- Контакт{' '} - - @antytoto - -

-
-
-); diff --git a/src/pages/Store/Store.tsx b/src/pages/Store/Store.tsx index ce1b8f33d..76d931845 100644 --- a/src/pages/Store/Store.tsx +++ b/src/pages/Store/Store.tsx @@ -1,85 +1,49 @@ import React from 'react'; +import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; -import '../../app/tailwind.css'; -import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; -import { Layout, Modal } from '../../entities'; -import { nanoid } from 'nanoid'; +import { Container, Modal } from '../../entities'; import { Btn, Input } from 'src/shared'; export const Store: React.FC = () => { - const [contextValue, setContextValue] = React.useState(defaultContext); const [inputValue, setInputValue] = React.useState(''); const [isModalVisible, setIsModalVisible] = React.useState(false); - const { theme, lang } = contextValue; - const { i18n, t } = useTranslation(); + const { t } = useTranslation(); - const themeSwitchHandler = (): void => { - if (theme === 'light') { - setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); - } else { - setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); - } - }; - - const langSwitchHandler = (): void => { - if (lang === 'ru') { - setContextValue({ ...contextValue, lang: ELangVariables.EN }); - i18n.changeLanguage('en-US'); - } else { - setContextValue({ ...contextValue, lang: ELangVariables.RU }); - i18n.changeLanguage('ru-RU'); - } + const eraseInput = (): void => { + setInputValue(''); }; - // TODO: Delete this Links and translation in future - const tempLinks = [ - { - path: '/store/card', - text: t('tempLinks.toCard'), - }, - { - path: '/store/basket', - text: t('tempLinks.toBasket'), - }, - ]; - const changeInputValue = (value: string): void => { setInputValue(value); }; const changeModalVisibility = (): void => { setIsModalVisible(!isModalVisible); + if (isModalVisible) { + eraseInput(); + } }; return ( - - - -
-
-
- -
- - {t('mainPage.showBtn')} - + <> + +
+
+
+ + {t('mainPage.showBtn')} +
- {isModalVisible && ( +
+ {isModalVisible && + createPortal(

{inputValue}

-
+ , + document.body )} - - + ); }; diff --git a/src/pages/StoreBasket/StoreBasket.tsx b/src/pages/StoreBasket/StoreBasket.tsx index 152c27501..b1606e589 100644 --- a/src/pages/StoreBasket/StoreBasket.tsx +++ b/src/pages/StoreBasket/StoreBasket.tsx @@ -1,72 +1,20 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { nanoid } from 'nanoid'; -import '../../app/tailwind.css'; -import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; -import { BasketGoodsItem, Layout } from '../../entities'; +import { BasketGoodsItem, Container } from '../../entities'; import { goods } from '../../assets/goods'; export const StoreBasket: React.FC = () => { - const [contextValue, setContextValue] = React.useState(defaultContext); - - const { theme, lang } = contextValue; - const { i18n, t } = useTranslation(); - - const themeSwitchHandler = (): void => { - if (theme === 'light') { - setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); - } else { - setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); - } - }; - - const langSwitchHandler = (): void => { - if (lang === 'ru') { - setContextValue({ ...contextValue, lang: ELangVariables.EN }); - i18n.changeLanguage('en-US'); - } else { - setContextValue({ ...contextValue, lang: ELangVariables.RU }); - i18n.changeLanguage('ru-RU'); - } - }; - - // TODO: Delete this Links and translation in future - const tempLinks = [ - { - path: '/store', - text: t('tempLinks.toMain'), - }, - { - path: '/store/card', - text: t('tempLinks.toCard'), - }, - ]; - const item = goods[0]; return ( - - - -
- -
-
-
+ + + ); }; diff --git a/src/pages/StoreCard/StoreCard.tsx b/src/pages/StoreCard/StoreCard.tsx index 6cf7718c6..9e13c3ffc 100644 --- a/src/pages/StoreCard/StoreCard.tsx +++ b/src/pages/StoreCard/StoreCard.tsx @@ -1,75 +1,26 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { nanoid } from 'nanoid'; -import '../../app/tailwind.css'; -import { defaultContext, ELangVariables, EThemeVariables, IStoreContext, StoreContext } from '../../app/StoreContext'; -import { FullCard, Layout } from '../../entities'; +import { useParams } from 'react-router'; +import { Container, FullCard } from '../../entities'; import { goods } from '../../assets/goods'; export const StoreCard: React.FC = () => { - const [contextValue, setContextValue] = React.useState(defaultContext); - - const { theme, lang } = contextValue; - const { i18n, t } = useTranslation(); - - const themeSwitchHandler = (): void => { - if (theme === 'light') { - setContextValue({ ...contextValue, theme: EThemeVariables.DARK }); - } else { - setContextValue({ ...contextValue, theme: EThemeVariables.LIGHT }); - } - }; - - const langSwitchHandler = (): void => { - if (lang === 'ru') { - setContextValue({ ...contextValue, lang: ELangVariables.EN }); - i18n.changeLanguage('en-US'); - } else { - setContextValue({ ...contextValue, lang: ELangVariables.RU }); - i18n.changeLanguage('ru-RU'); - } - }; - - // TODO: Delete this Links and translation in future - const tempLinks = [ - { - path: '/store', - text: t('tempLinks.toMain'), - }, - { - path: '/store/basket', - text: t('tempLinks.toBasket'), - }, - ]; - - const item = goods[0]; + const { id } = useParams(); + const itemPlace = Number(id) - 1; + const item = goods[itemPlace]; return ( - - - -
- -
-
-
+ + + ); }; diff --git a/src/pages/StoreList/StoreList.tsx b/src/pages/StoreList/StoreList.tsx new file mode 100644 index 000000000..63dbd2880 --- /dev/null +++ b/src/pages/StoreList/StoreList.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { nanoid } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { goods } from '../../assets/goods'; +import { Container, ShortCard } from '../../entities'; +import { IGoodsItem } from '../../entities/interfaces'; +import { Btn } from '../../shared'; +import generateRandomCardList from '../../features/generateRandomCardList'; + +export const StoreList: React.FC = () => { + const [myGoods, setMyGoods] = React.useState([]); + const [lastId, setLastId] = React.useState(goods[goods.length - 1].id); + + React.useLayoutEffect(() => { + const onlyAwailible: IGoodsItem[] = goods.filter((el) => el.isInStock); + const onlyUnAwailible: IGoodsItem[] = goods.filter((el) => !el.isInStock); + const newGoods: IGoodsItem[] = [...onlyAwailible, ...onlyUnAwailible]; + + setMyGoods(newGoods); + }, []); + + const cardGenerator = () => { + const newCount = 3; + const newGoods: IGoodsItem[] = generateRandomCardList(lastId, newCount); + setMyGoods([...myGoods, ...newGoods]); + setLastId(lastId + newCount); + }; + + const { t } = useTranslation(); + + return ( + +
    + {myGoods.map((item) => ( +
  • + +
  • + ))} +
+
+ {t('listPage.addMore')} +
+
+ ); +}; diff --git a/src/pages/index.ts b/src/pages/index.ts index 8b557c47b..e1d030ea0 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,4 +1,4 @@ -export { MainPage } from './MainPage/MainPage'; export { Store } from './Store/Store'; export { StoreBasket } from './StoreBasket/StoreBasket'; export { StoreCard } from './StoreCard/StoreCard'; +export { StoreList } from './StoreList/StoreList'; From 2a946d685d4dee482871583b7e4b24bb95b1a78f Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:35:47 +0300 Subject: [PATCH 19/30] feat: create homework 5 (#7) * feat: decomposing BasketGoodsItem * fix: path at generateAvailibleSizes * feat: refactoring CardSizes * feat: refactoring Counter --- .../BasketGoodsItem/BasketGoodsItem.tsx | 72 ++++++++++++------- .../CardColorChanger/CardColorChanger.tsx | 41 +++++++++++ .../Card/CardDescription/CardDescription.tsx | 59 ++------------- .../CardSizesChanger/CardSizesChanger.tsx | 44 ++++++++++++ src/entities/Card/index.ts | 2 + src/entities/Card/interfaces.ts | 11 ++- src/entities/index.tsx | 2 +- src/features/generateAwailibleSizes.ts | 2 +- src/shared/Counter/Counter.tsx | 38 +++++----- src/stories/CardColorChanger.stories.ts | 19 +++++ src/stories/CardSizesChanger.stories.ts | 19 +++++ 11 files changed, 207 insertions(+), 102 deletions(-) create mode 100644 src/entities/Card/CardColorChanger/CardColorChanger.tsx create mode 100644 src/entities/Card/CardSizesChanger/CardSizesChanger.tsx create mode 100644 src/stories/CardColorChanger.stories.ts create mode 100644 src/stories/CardSizesChanger.stories.ts diff --git a/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx b/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx index 27d8e6ad0..79280b164 100644 --- a/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx +++ b/src/entities/Basket/BasketGoodsItem/BasketGoodsItem.tsx @@ -5,42 +5,64 @@ import { IBasketGoodsItem } from '../../../entities/interfaces'; import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; import { Counter, DeleteBtn } from '../../../shared'; -export const BasketGoodsItem: React.FC = ({ size, color, counter, title, price, image }) => { - const colorClass = clsx('w-3 aspect-square rounded-full', color); - const { theme } = React.useContext(StoreContext); - const { t } = useTranslation(); +const ImagePlace: React.FC> = ({ image }) => ( +
+ +
+); - const isDarkTheme = theme === EThemeVariables.DARK; +interface Props extends Partial { + isDarkTheme: boolean; +} + +const DesccriptionPlace: React.FC = ({ color, title, size, isDarkTheme }) => { + const { t } = useTranslation(); const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; const b500ClassName = isDarkTheme ? 'text-w-500' : 'text-b-500'; const h1ClassName = clsx('font-semibold text-sm leading-7', w900ClassName); const subtitleClassName = clsx('flex items-baseline gap-2 font-medium text-xs leading-6', b500ClassName); + const colorClass = clsx('w-3 aspect-square rounded-full', color); + + return ( +
+

{title}

+
+

{t('basket.color')}

+
+

+  — {t('basket.size')}: {size} +

+
+
+ ); +}; + +const CounterPlace: React.FC = ({ price, counter, isDarkTheme }) => { + const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; const priceClassName = clsx('font text-sm text-b-900 leading-7', w900ClassName); + return ( +
+

${price}

+
+ + null} /> +
+
+ ); +}; + +export const BasketGoodsItem: React.FC = ({ size, color, counter, title, price, image }) => { + const { theme } = React.useContext(StoreContext); + + const isDarkTheme = theme === EThemeVariables.DARK; return (
-
- -
-
-

{title}

-
-

{t('basket.color')}

-
-

-  — {t('basket.size')}: {size} -

-
-
-
-
-

${price}

-
- - null} /> -
+ +
+
); }; diff --git a/src/entities/Card/CardColorChanger/CardColorChanger.tsx b/src/entities/Card/CardColorChanger/CardColorChanger.tsx new file mode 100644 index 000000000..31e480d73 --- /dev/null +++ b/src/entities/Card/CardColorChanger/CardColorChanger.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import clsx from 'clsx'; +import { nanoid } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { IGoogsColor } from '../interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; + +export const CardColorChanger: React.FC> = ({ colors }) => { + const [currentColor, setCurrentColor] = React.useState(colors ? colors[0] : ''); + + const { t } = useTranslation(); + const { theme } = React.useContext(StoreContext); + + const isDarkTheme = theme === EThemeVariables.DARK; + + const changeColor = (color: string) => { + setCurrentColor(color); + }; + + return ( +
+ {colors.map((item) => { + const isTargetItem = currentColor === item; + const colorClass = clsx( + 'w-6 aspect-square rounded-full', + item, + isTargetItem && 'outline outline-1 outline-b-900 outline-offset-2', + isTargetItem && isDarkTheme && 'outline-w-900', + isTargetItem && !isDarkTheme && 'outline-b-900' + ); + return ( + + ); + })} +
+ ); +}; diff --git a/src/entities/Card/CardDescription/CardDescription.tsx b/src/entities/Card/CardDescription/CardDescription.tsx index 564e23508..ae111fbd1 100644 --- a/src/entities/Card/CardDescription/CardDescription.tsx +++ b/src/entities/Card/CardDescription/CardDescription.tsx @@ -1,28 +1,17 @@ import React from 'react'; import clsx from 'clsx'; -import { nanoid } from 'nanoid'; import { useTranslation } from 'react-i18next'; -import { EGoodsSizes, IGoodsItem } from '../../../entities/interfaces'; +import { CardColorChanger, CardSizesChanger } from '../../../entities'; +import { IGoodsItem } from '../../../entities/interfaces'; import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; import { Btn, Counter, StockStatus } from '../../../shared'; export const CardDescription: React.FC> = ({ title, isInStock, price, colors, sizes }) => { - const [currentColor, setCurrentColor] = React.useState(colors ? colors[0] : ''); - const [currentSize, setCurrentSize] = React.useState(sizes[0]); - const { theme } = React.useContext(StoreContext); const { t } = useTranslation(); const isDarkTheme = theme === EThemeVariables.DARK; - const changeColor = (color: string) => { - setCurrentColor(color); - }; - - const changeSize = (size: EGoodsSizes) => { - setCurrentSize(size); - }; - const w900ClassName = isDarkTheme ? 'text-w-900' : 'text-b-900'; const b500ClassName = isDarkTheme ? 'text-w-500' : 'text-b-500'; const h1ClassName = clsx('mb-3 font-bold text-2xl text-b-900', w900ClassName); @@ -36,51 +25,11 @@ export const CardDescription: React.FC> = ({ title, isInStoc

${price}

{t('card.colors.title')}

-
- {colors && - colors.map((item) => { - const isTargetItem = currentColor === item; - const colorClass = clsx( - 'w-6 aspect-square rounded-full', - item, - isTargetItem && 'outline outline-1 outline-b-900 outline-offset-2', - isTargetItem && isDarkTheme && 'outline-w-900', - isTargetItem && !isDarkTheme && 'outline-b-900' - ); - return ( - - ); - })} -
+

{t('card.sizes.title')}

-
- {sizes && - sizes.map((item) => { - const isTargetItem = currentSize === item; - const sizeClass = clsx( - 'w-10 aspect-square border-[1px] border-solid font-medium text-xs leading-6', - isTargetItem && isDarkTheme && 'bg-w-900 text-b-900', - !isTargetItem && isDarkTheme && '', - isTargetItem && !isDarkTheme && 'border-b-900 text-b-900', - !isTargetItem && !isDarkTheme && 'border-b-100 text-b-500', - 'rounded uppercase' - ); - return ( - - ); - })} -
+

{t('card.quantity')}

diff --git a/src/entities/Card/CardSizesChanger/CardSizesChanger.tsx b/src/entities/Card/CardSizesChanger/CardSizesChanger.tsx new file mode 100644 index 000000000..21d6d3a17 --- /dev/null +++ b/src/entities/Card/CardSizesChanger/CardSizesChanger.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import clsx from 'clsx'; +import { nanoid } from 'nanoid'; +import { useTranslation } from 'react-i18next'; +import { EGoodsSizes, IGoodsSizes } from '../interfaces'; +import { EThemeVariables, StoreContext } from '../../../app/StoreContext'; + +export const CardSizesChanger: React.FC = ({ sizes }) => { + const [currentSize, setCurrentSize] = React.useState(sizes[0]); + + const { theme } = React.useContext(StoreContext); + const { t } = useTranslation(); + + const isDarkTheme = theme === EThemeVariables.DARK; + + const changeSize = (size: EGoodsSizes) => { + setCurrentSize(size); + }; + + return ( +
+ {sizes && + sizes.map((item) => { + const isTargetItem = currentSize === item; + const sizeClass = clsx( + 'w-10 aspect-square border-[1px] border-solid font-medium text-xs leading-6', + isTargetItem && isDarkTheme && 'bg-w-900 text-b-900', + !isTargetItem && isDarkTheme && '', + isTargetItem && !isDarkTheme && 'border-b-900 text-b-900', + !isTargetItem && !isDarkTheme && 'border-b-100 text-b-500', + 'rounded uppercase' + ); + return ( + + ); + })} +
+ ); +}; diff --git a/src/entities/Card/index.ts b/src/entities/Card/index.ts index bdac07a09..a4fd8fa98 100644 --- a/src/entities/Card/index.ts +++ b/src/entities/Card/index.ts @@ -1,4 +1,6 @@ +export { CardColorChanger } from './CardColorChanger/CardColorChanger'; export { CardDescription } from './CardDescription/CardDescription'; export { CardImage } from './CardImage/CardImage'; +export { CardSizesChanger } from './CardSizesChanger/CardSizesChanger'; export { FullCard } from './FullCard/FullCard'; export { ShortCard } from './ShortCard/ShortCard'; diff --git a/src/entities/Card/interfaces.ts b/src/entities/Card/interfaces.ts index 8af3a77b2..bbe41bc93 100644 --- a/src/entities/Card/interfaces.ts +++ b/src/entities/Card/interfaces.ts @@ -5,7 +5,15 @@ export enum EGoodsSizes { XXL = 'xxl', } -export interface IGoodsItem { +export interface IGoogsColor { + colors: string[]; +} + +export interface IGoodsSizes { + sizes: EGoodsSizes[]; +} + +export interface IGoodsItem extends IGoogsColor, IGoodsSizes { id: number; isInStock: boolean; title: string; @@ -14,5 +22,4 @@ export interface IGoodsItem { imageListing: string; imageFull: string[]; colors: string[]; - sizes: EGoodsSizes[]; } diff --git a/src/entities/index.tsx b/src/entities/index.tsx index f9785fd77..47c83d9c7 100644 --- a/src/entities/index.tsx +++ b/src/entities/index.tsx @@ -1,5 +1,5 @@ export { BasketGoodsItem } from './Basket'; -export { CardDescription, CardImage, FullCard, ShortCard } from './Card'; +export * from './Card'; export { Container } from './Container/Container'; export { Header } from './Header/Header'; export { Layout } from './Layout/Layout'; diff --git a/src/features/generateAwailibleSizes.ts b/src/features/generateAwailibleSizes.ts index 1d55b5db3..6a040329c 100644 --- a/src/features/generateAwailibleSizes.ts +++ b/src/features/generateAwailibleSizes.ts @@ -1,4 +1,4 @@ -import { EGoodsSizes } from 'src/entities/interfaces'; +import { EGoodsSizes } from '../entities/interfaces'; import generateRandomNumber from './generateRandomNumber'; const generateRandomSizes = (number: number): EGoodsSizes[] => { diff --git a/src/shared/Counter/Counter.tsx b/src/shared/Counter/Counter.tsx index ebde7b671..3b84d531f 100644 --- a/src/shared/Counter/Counter.tsx +++ b/src/shared/Counter/Counter.tsx @@ -4,6 +4,18 @@ import clsx from 'clsx'; import { ICounter } from '../../shared/interfaces'; import { EThemeVariables, StoreContext } from '../../app/StoreContext'; +interface ICounterBtnProps { + src: string; + text: string; +} + +const CounterBtn: React.FC = ({ src, text }) => ( + +); + export const Counter: React.FC = ({ counter }) => { const { t } = useTranslation(); const { theme } = React.useContext(StoreContext); @@ -16,25 +28,15 @@ export const Counter: React.FC = ({ counter }) => { return (
- + {counter} - +
); }; diff --git a/src/stories/CardColorChanger.stories.ts b/src/stories/CardColorChanger.stories.ts new file mode 100644 index 000000000..c8084761f --- /dev/null +++ b/src/stories/CardColorChanger.stories.ts @@ -0,0 +1,19 @@ +import type { Meta } from '@storybook/react'; +import { CardColorChanger } from '../entities/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/components/CardColorChanger', + component: CardColorChanger, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const isInStock = { + args: { + colors: item.colors, + }, +}; diff --git a/src/stories/CardSizesChanger.stories.ts b/src/stories/CardSizesChanger.stories.ts new file mode 100644 index 000000000..e3d39f4cc --- /dev/null +++ b/src/stories/CardSizesChanger.stories.ts @@ -0,0 +1,19 @@ +import type { Meta } from '@storybook/react'; +import { CardSizesChanger } from '../entities/index'; +import { goods } from '../assets/goods'; + +const meta: Meta = { + title: 'UI/Cards/components/CardSizesChanger', + component: CardSizesChanger, + tags: ['autodocs'], +}; + +export default meta; + +const item = goods[0]; + +export const isInStock = { + args: { + sizes: item.sizes, + }, +}; From 6fa01836d22963996a73b748de9beaec58430c23 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:19:28 +0300 Subject: [PATCH 20/30] feat: add pattern (#8) --- src/entities/Card/CardImage/CardImage.tsx | 34 ++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/entities/Card/CardImage/CardImage.tsx b/src/entities/Card/CardImage/CardImage.tsx index d35acbb63..7da70daa4 100644 --- a/src/entities/Card/CardImage/CardImage.tsx +++ b/src/entities/Card/CardImage/CardImage.tsx @@ -10,25 +10,27 @@ export const CardImage: React.FC> = ({ imageFull }) => { setSliderNumber(id); }; + const renderBtns = React.useMemo(() => { + return imageFull.map((_, index) => { + const btnClassName = clsx( + 'w-2', + 'aspect-square', + index === sliderNumber ? 'bg-b-900' : 'bg-b-200', + 'rounded-full' + ); + + return ( + + ); + }); + }, [imageFull, sliderNumber]); + return (
-
- {imageFull.map((item, index) => { - const btnClassName = clsx( - 'w-2', - 'aspect-square', - index === sliderNumber ? 'bg-b-900' : 'bg-b-200', - 'rounded-full' - ); - - return ( - - ); - })} -
+
{renderBtns}
); }; From 119642f89a9284a96e91891248f46a7cc4ff0be2 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:43:04 +0300 Subject: [PATCH 21/30] feat: create Slider (#9) * feat: create RangeSlider * feat: place AsideFilter * feat: impuve RangeSlider * feat: add range slider --- .storybook/preview.ts | 1 + src/app/tailwind.css | 1 + .../Aside/AsideFilter/AsideFilter.tsx | 9 +++ .../Aside/AsideLayout/AsideLayout.tsx | 6 ++ src/entities/Aside/index.ts | 2 + src/entities/Aside/interfaces.ts | 5 ++ src/entities/index.tsx | 1 + src/pages/StoreList/StoreList.tsx | 36 ++++++--- src/shared/RangeSlider/RangeSlider.css | 18 +++++ src/shared/RangeSlider/RangeSlider.tsx | 77 +++++++++++++++++++ src/shared/RangeSlider/interfaces.ts | 10 +++ src/shared/index.ts | 1 + src/shared/interfaces.ts | 1 + src/stories/RangeSlider.stories.ts | 17 ++++ tailwind.config.js | 1 + 15 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 src/entities/Aside/AsideFilter/AsideFilter.tsx create mode 100644 src/entities/Aside/AsideLayout/AsideLayout.tsx create mode 100644 src/entities/Aside/index.ts create mode 100644 src/entities/Aside/interfaces.ts create mode 100644 src/shared/RangeSlider/RangeSlider.css create mode 100644 src/shared/RangeSlider/RangeSlider.tsx create mode 100644 src/shared/RangeSlider/interfaces.ts create mode 100644 src/stories/RangeSlider.stories.ts diff --git a/.storybook/preview.ts b/.storybook/preview.ts index be42fe7b9..fb52148f4 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,6 +1,7 @@ import type { Preview } from '@storybook/react'; import i18n from '../src/app/i18n/config'; import '!style-loader!css-loader!postcss-loader!tailwindcss/tailwind.css'; +import '!style-loader!css-loader!postcss-loader!../src/shared/RangeSlider/RangeSlider.css'; const preview: Preview = { parameters: { diff --git a/src/app/tailwind.css b/src/app/tailwind.css index b5c61c956..a90f0749c 100644 --- a/src/app/tailwind.css +++ b/src/app/tailwind.css @@ -1,3 +1,4 @@ @tailwind base; @tailwind components; @tailwind utilities; + diff --git a/src/entities/Aside/AsideFilter/AsideFilter.tsx b/src/entities/Aside/AsideFilter/AsideFilter.tsx new file mode 100644 index 000000000..4c0e89a45 --- /dev/null +++ b/src/entities/Aside/AsideFilter/AsideFilter.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { IAsideFilter } from '../interfaces'; + +export const AsideFlter: React.FC = ({ children, title }) => ( +
+

{title}

+ {children} +
+); diff --git a/src/entities/Aside/AsideLayout/AsideLayout.tsx b/src/entities/Aside/AsideLayout/AsideLayout.tsx new file mode 100644 index 000000000..656fab6e0 --- /dev/null +++ b/src/entities/Aside/AsideLayout/AsideLayout.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { ILayout } from '../../interfaces'; + +export const AsideLayout: React.FC = ({ children }) => ( + +); diff --git a/src/entities/Aside/index.ts b/src/entities/Aside/index.ts new file mode 100644 index 000000000..02bea267b --- /dev/null +++ b/src/entities/Aside/index.ts @@ -0,0 +1,2 @@ +export { AsideFlter } from './AsideFilter/AsideFilter'; +export { AsideLayout } from './AsideLayout/AsideLayout'; diff --git a/src/entities/Aside/interfaces.ts b/src/entities/Aside/interfaces.ts new file mode 100644 index 000000000..9a3489862 --- /dev/null +++ b/src/entities/Aside/interfaces.ts @@ -0,0 +1,5 @@ +import { ILayout } from '../interfaces'; + +export interface IAsideFilter extends ILayout { + title: string; +} diff --git a/src/entities/index.tsx b/src/entities/index.tsx index 47c83d9c7..9cdd83813 100644 --- a/src/entities/index.tsx +++ b/src/entities/index.tsx @@ -1,3 +1,4 @@ +export { AsideFlter, AsideLayout } from './Aside'; export { BasketGoodsItem } from './Basket'; export * from './Card'; export { Container } from './Container/Container'; diff --git a/src/pages/StoreList/StoreList.tsx b/src/pages/StoreList/StoreList.tsx index 63dbd2880..53dbe4e89 100644 --- a/src/pages/StoreList/StoreList.tsx +++ b/src/pages/StoreList/StoreList.tsx @@ -2,10 +2,11 @@ import React from 'react'; import { nanoid } from 'nanoid'; import { useTranslation } from 'react-i18next'; import { goods } from '../../assets/goods'; -import { Container, ShortCard } from '../../entities'; +import { AsideFlter, AsideLayout, Container, ShortCard } from '../../entities'; import { IGoodsItem } from '../../entities/interfaces'; -import { Btn } from '../../shared'; +import { Btn, RangeSlider } from '../../shared'; import generateRandomCardList from '../../features/generateRandomCardList'; +import { IRangeSliderChange } from 'src/shared/interfaces'; export const StoreList: React.FC = () => { const [myGoods, setMyGoods] = React.useState([]); @@ -28,17 +29,30 @@ export const StoreList: React.FC = () => { const { t } = useTranslation(); + const changePriceFilter = ({ min, max }: IRangeSliderChange) => { + console.log(`min = ${min}, max = ${max}`); + }; + return ( -
    - {myGoods.map((item) => ( -
  • - -
  • - ))} -
-
- {t('listPage.addMore')} +
+ + + + + +
+
    + {myGoods.map((item) => ( +
  • + +
  • + ))} +
+
+ {t('listPage.addMore')} +
+
); diff --git a/src/shared/RangeSlider/RangeSlider.css b/src/shared/RangeSlider/RangeSlider.css new file mode 100644 index 000000000..46274b39b --- /dev/null +++ b/src/shared/RangeSlider/RangeSlider.css @@ -0,0 +1,18 @@ +@tailwind components; + +/* Custom styling for input range */ +@layer components { + input[type='range'] { + @apply appearance-none h-0 w-[200px] absolute outline-none pointer-events-none; + -webkit-tap-highlight-color: transparent; + } + + input[type='range']::-webkit-slider-thumb { + @apply appearance-none h-[18px] w-[18px] mt-1 bg-b-900 border-solid border-w-900 border-[2px] rounded-[50%] shadow-sm cursor-pointer pointer-events-auto relative; + -webkit-tap-highlight-color: transparent; + } + + input[type='range']::-moz-range-thumb { + @apply h-[18px] w-[18px] mt-1 bg-b-900 border-solid border-w-900 border-[2px] rounded-[50%] shadow-sm cursor-pointer pointer-events-auto relative; + } +} diff --git a/src/shared/RangeSlider/RangeSlider.tsx b/src/shared/RangeSlider/RangeSlider.tsx new file mode 100644 index 000000000..35d1766e3 --- /dev/null +++ b/src/shared/RangeSlider/RangeSlider.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import clsx from 'clsx'; +import './RangeSlider.css'; +import { IRangeSlider } from './interfaces'; + +export const RangeSlider: React.FC = ({ min, max, onChange }) => { + const [minVal, setMinVal] = React.useState(min); + const [maxVal, setMaxVal] = React.useState(max); + + const minValRef = React.useRef(null); + const maxValRef = React.useRef(null); + const range = React.useRef(null); + + const getPercent = React.useCallback((value: number) => Math.round(((value - min) / (max - min)) * 100), [min, max]); + + React.useEffect(() => { + if (maxValRef.current) { + const minPercent = getPercent(minVal); + const maxPercent = getPercent(+maxValRef.current.value); + + if (range.current) { + range.current.style.left = `${minPercent}%`; + range.current.style.width = `${maxPercent - minPercent}%`; + } + } + }, [minVal, getPercent]); + + React.useEffect(() => { + if (minValRef.current) { + const minPercent = getPercent(+minValRef.current.value); + const maxPercent = getPercent(maxVal); + + if (range.current) { + range.current.style.width = `${maxPercent - minPercent}%`; + } + } + }, [maxVal, getPercent]); + + React.useEffect(() => { + onChange({ min: minVal, max: maxVal }); + }, [minVal, maxVal, onChange]); + + return ( + <> + ) => { + const value = Math.min(+ev.target.value, maxVal - 1); + setMinVal(value); + ev.target.value = value.toString(); + }} + className={clsx(minVal > max - 100 ? 'z-50' : 'z-30')} + /> + ) => { + const value = Math.max(+ev.target.value, minVal + 1); + setMaxVal(value); + ev.target.value = value.toString(); + }} + className="z-40" + /> +
+
+
+
+ + ); +}; diff --git a/src/shared/RangeSlider/interfaces.ts b/src/shared/RangeSlider/interfaces.ts new file mode 100644 index 000000000..b7498569a --- /dev/null +++ b/src/shared/RangeSlider/interfaces.ts @@ -0,0 +1,10 @@ +export interface IRangeSliderChange { + min: number; + max: number; +} + +export interface IRangeSlider { + min: number; + max: number; + onChange: ({ min, max }: IRangeSliderChange) => void; +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 1695a4383..0c790cdee 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -2,4 +2,5 @@ export * from './Btns'; export { Counter } from './Counter/Counter'; export { Input } from './Input/Input'; export { Logo } from './Logo/Logo'; +export { RangeSlider } from './RangeSlider/RangeSlider'; export { StockStatus } from './StockStatus/StockStatus'; diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index f0efbe17f..91d53de8f 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -1,4 +1,5 @@ export * from './Btns/interfaces'; export * from './Counter/interfaces'; export * from './Logo/interfaces'; +export * from './RangeSlider/interfaces'; export * from './StockStatus/interfaces'; diff --git a/src/stories/RangeSlider.stories.ts b/src/stories/RangeSlider.stories.ts new file mode 100644 index 000000000..10bee163d --- /dev/null +++ b/src/stories/RangeSlider.stories.ts @@ -0,0 +1,17 @@ +import type { Meta } from '@storybook/react'; +import { RangeSlider } from '../shared'; + +const meta: Meta = { + title: 'UI/RangeSlider', + component: RangeSlider, + tags: ['autodocs'], +}; + +export default meta; + +export const Default = { + args: { + min: 0, + max: 100, + }, +}; diff --git a/tailwind.config.js b/tailwind.config.js index 87845bdaf..8d061fa9a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,7 @@ module.exports = { b: { 100: '#E6E7E8', 200: '#B6B7BC', + 300: '#878A92', 500: '#5C5F6A', 600: '#474B57', 800: '#202533', From 97e2d8adf42e0b40e762db2b3fdb4a2317e4277a Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:05:29 +0300 Subject: [PATCH 22/30] fix: review (#10) --- src/shared/RangeSlider/RangeSlider.tsx | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/shared/RangeSlider/RangeSlider.tsx b/src/shared/RangeSlider/RangeSlider.tsx index 35d1766e3..f0c5f3a04 100644 --- a/src/shared/RangeSlider/RangeSlider.tsx +++ b/src/shared/RangeSlider/RangeSlider.tsx @@ -1,42 +1,36 @@ -import React from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import clsx from 'clsx'; import './RangeSlider.css'; import { IRangeSlider } from './interfaces'; export const RangeSlider: React.FC = ({ min, max, onChange }) => { - const [minVal, setMinVal] = React.useState(min); - const [maxVal, setMaxVal] = React.useState(max); + const [minVal, setMinVal] = useState(min); + const [maxVal, setMaxVal] = useState(max); - const minValRef = React.useRef(null); - const maxValRef = React.useRef(null); - const range = React.useRef(null); + const minValRef = useRef(null); + const maxValRef = useRef(null); + const range = useRef(null); const getPercent = React.useCallback((value: number) => Math.round(((value - min) / (max - min)) * 100), [min, max]); - React.useEffect(() => { + useEffect(() => { if (maxValRef.current) { const minPercent = getPercent(minVal); const maxPercent = getPercent(+maxValRef.current.value); - if (range.current) { range.current.style.left = `${minPercent}%`; range.current.style.width = `${maxPercent - minPercent}%`; } - } - }, [minVal, getPercent]); - - React.useEffect(() => { - if (minValRef.current) { + } else if (minValRef.current) { const minPercent = getPercent(+minValRef.current.value); const maxPercent = getPercent(maxVal); - if (range.current) { range.current.style.width = `${maxPercent - minPercent}%`; } } - }, [maxVal, getPercent]); + }, [minVal, getPercent, maxVal]); - React.useEffect(() => { + useEffect(() => { onChange({ min: minVal, max: maxVal }); }, [minVal, maxVal, onChange]); From adb12410262207e9d32d70e35430b03b62fec723 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:03:25 +0300 Subject: [PATCH 23/30] fix: strorybook (#11) --- .github/workflows/main.yml | 22 +++++++++++----------- package.json | 5 ++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 54bcf3b38..42fb743b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,21 +34,21 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # # Собираем приложение - # - name: Build Application - # run: npm run build + # # Собираем приложение + # - name: Build Application + # run: npm run build - # # Публикуем приложение на Github Pages - # - name: Deploy to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: dist + # # Публикуем приложение на Github Pages + # - name: Deploy to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: dist # Собираем Storybook - name: Build Storybook run: npm run build-storybook - # + # Публикуем Storybook на Github Pages - name: Deploy Storybook to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 @@ -60,4 +60,4 @@ jobs: # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests run: | - if [ ${{ job.status }} == 'failure' ]; then exit 1; fi; + if [ ${{ job.status }} == 'failure' ]; then exit 1; fi; \ No newline at end of file diff --git a/package.json b/package.json index f3c056d89..83b8aad08 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,8 @@ "lint": "eslint src --fix", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "deploy": "npx gh-pages -d dist", - "deploy-storybook": "npm run build-storybook && npx gh-pages -d storybook-static -e storybook", - "deploy-all": "npm run build && npm run deploy && npm run deploy-storybook" + "predeploy": "npm run build", + "deploy": "npx gh-pages -d dist" }, "license": "MIT", "devDependencies": { From 7600c881feaf6ee3e25c2669319adbbd8e272645 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:03:53 +0300 Subject: [PATCH 24/30] feat: create practice 2 (#12) --- src/app/i18n/translation.ts | 8 +++++++ src/shared/Collapse/Collapse.tsx | 39 +++++++++++++++++++++++++++++++ src/shared/Collapse/interfaces.ts | 4 ++++ src/shared/index.ts | 1 + src/shared/interfaces.ts | 1 + src/stories/Collapse.stories.ts | 18 ++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 src/shared/Collapse/Collapse.tsx create mode 100644 src/shared/Collapse/interfaces.ts create mode 100644 src/stories/Collapse.stories.ts diff --git a/src/app/i18n/translation.ts b/src/app/i18n/translation.ts index df00fc0e8..14c9d7921 100644 --- a/src/app/i18n/translation.ts +++ b/src/app/i18n/translation.ts @@ -46,6 +46,10 @@ export const resources = { modal: { close: 'Close modal window', }, + collapse: { + isClosed: 'Show more', + isOpened: 'Hide', + }, }, }, ru: { @@ -95,6 +99,10 @@ export const resources = { modal: { close: 'Закрыть модальное окно', }, + collapse: { + isClosed: 'Показать больше', + isOpened: 'Скрыть', + }, }, }, }; diff --git a/src/shared/Collapse/Collapse.tsx b/src/shared/Collapse/Collapse.tsx new file mode 100644 index 000000000..791267936 --- /dev/null +++ b/src/shared/Collapse/Collapse.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { ICollapse } from './interfaces'; +import { Btn } from '../Btns'; + +export const Collapse: React.FC = ({ children, showenText }) => { + const contentRef = React.useRef(null); + const [contentHeight, setContentHeight] = React.useState(0); + const [isCollapseOpen, setIsCollapseOpen] = React.useState(false); + const { t } = useTranslation(); + + // Хендлер для смены состояния открытости + const handleToggle = (): void => { + setIsCollapseOpen((prev) => !prev); + }; + + // Эффект для измерения высоты содержимого при значении флага isCollapseOpen === true + React.useLayoutEffect(() => { + if (isCollapseOpen && contentRef.current) { + const newHeight = contentRef.current.scrollHeight; + setContentHeight(newHeight); + } + }, [isCollapseOpen]); + + const contentClassNames = clsx(isCollapseOpen ? 'opacity-1 mt-8' : 'opacity-0'); + + return ( +
+
+

{showenText}

+ {isCollapseOpen ? t('collapse.isOpened') : t('collapse.isClosed')} +
+
+ {children} +
+
+ ); +}; diff --git a/src/shared/Collapse/interfaces.ts b/src/shared/Collapse/interfaces.ts new file mode 100644 index 000000000..671b68e4d --- /dev/null +++ b/src/shared/Collapse/interfaces.ts @@ -0,0 +1,4 @@ +export interface ICollapse { + children: React.ReactNode; + showenText: string; +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 0c790cdee..f01bf3cba 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,4 +1,5 @@ export * from './Btns'; +export { Collapse } from './Collapse/Collapse'; export { Counter } from './Counter/Counter'; export { Input } from './Input/Input'; export { Logo } from './Logo/Logo'; diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index 91d53de8f..4056049f9 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -1,4 +1,5 @@ export * from './Btns/interfaces'; +export * from './Collapse/interfaces'; export * from './Counter/interfaces'; export * from './Logo/interfaces'; export * from './RangeSlider/interfaces'; diff --git a/src/stories/Collapse.stories.ts b/src/stories/Collapse.stories.ts new file mode 100644 index 000000000..8e3be8ef8 --- /dev/null +++ b/src/stories/Collapse.stories.ts @@ -0,0 +1,18 @@ +import type { Meta } from '@storybook/react'; +import { Collapse } from '../shared'; + +const meta: Meta = { + title: 'UI/Collapse', + component: Collapse, + tags: ['autodocs'], +}; + +export default meta; + +export const Closed = { + args: { + showenText: 'Some text...', + children: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vestibulum metus at felis eleifend, at ornare lacus scelerisque. Praesent arcu mi, suscipit eu rhoncus ut, finibus a elit. Nulla finibus nisi orci. Sed vestibulum lectus ac semper porta. Proin et pharetra ipsum. Nulla vitae sem ut libero bibendum ultrices sit amet varius ipsum. Phasellus et ultrices orci. Nam pretium, erat ut ornare porta, nunc nulla vulputate dolor, ut hendrerit justo massa sed lorem. ', + }, +}; From afa8f9c6c2d80fdd683581496a53d5f3905457a6 Mon Sep 17 00:00:00 2001 From: Egor Levchenko <50330458+furtivite@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:02:08 +0300 Subject: [PATCH 25/30] feat: Create forms (#13) * feat: move generators * feat: install react-hook-form * feat: add TextArea * feat: start ProfileForm * feat: impruve type of Btn * feat: add profile form * feat: add AddGoodForm * feat: add auth form * feat: add err message --- package-lock.json | 23 +++++ package.json | 1 + src/app/i18n/translation.ts | 68 +++++++++++++ src/assets/goods.ts | 10 +- src/features/forms/AddGoodForm.tsx | 82 ++++++++++++++++ src/features/forms/AuthRegForm.tsx | 96 +++++++++++++++++++ src/features/forms/ProfileForm.tsx | 74 ++++++++++++++ src/features/forms/index.ts | 3 + .../generateAwailibleColors.ts | 0 .../generateAwailibleSizes.ts | 2 +- .../{ => generators}/generateImageAddress.ts | 0 .../{ => generators}/generateRandomCard.ts | 4 +- .../generateRandomCardList.ts | 2 +- .../{ => generators}/generateRandomNumber.ts | 0 src/features/generators/index.ts | 6 ++ src/pages/StoreList/StoreList.tsx | 2 +- src/shared/Btns/Btn/Btn.tsx | 4 +- src/shared/Btns/interfaces.ts | 3 +- src/shared/{ => Inputs}/Input/Input.tsx | 5 +- src/shared/Inputs/TextArea/TextArea.tsx | 51 ++++++++++ src/shared/Inputs/index.ts | 2 + src/shared/{Input => Inputs}/interfaces.ts | 0 src/shared/index.ts | 3 +- src/shared/interfaces.ts | 1 + src/stories/AddGoodForm.stories.ts | 14 +++ src/stories/AuthRegForm.stories.ts | 23 +++++ src/stories/Input.stories.ts | 2 +- src/stories/ProfileForm.stories.ts | 14 +++ src/stories/TextArea.stories.ts | 35 +++++++ 29 files changed, 514 insertions(+), 16 deletions(-) create mode 100644 src/features/forms/AddGoodForm.tsx create mode 100644 src/features/forms/AuthRegForm.tsx create mode 100644 src/features/forms/ProfileForm.tsx create mode 100644 src/features/forms/index.ts rename src/features/{ => generators}/generateAwailibleColors.ts (100%) rename src/features/{ => generators}/generateAwailibleSizes.ts (95%) rename src/features/{ => generators}/generateImageAddress.ts (100%) rename src/features/{ => generators}/generateRandomCard.ts (87%) rename src/features/{ => generators}/generateRandomCardList.ts (85%) rename src/features/{ => generators}/generateRandomNumber.ts (100%) create mode 100644 src/features/generators/index.ts rename src/shared/{ => Inputs}/Input/Input.tsx (88%) create mode 100644 src/shared/Inputs/TextArea/TextArea.tsx create mode 100644 src/shared/Inputs/index.ts rename src/shared/{Input => Inputs}/interfaces.ts (100%) create mode 100644 src/stories/AddGoodForm.stories.ts create mode 100644 src/stories/AuthRegForm.stories.ts create mode 100644 src/stories/ProfileForm.stories.ts create mode 100644 src/stories/TextArea.stories.ts diff --git a/package-lock.json b/package-lock.json index 9902e96b2..b8a26d092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "nanoid": "^5.0.9", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.4.0", "react-router": "^7.1.1" }, @@ -20476,6 +20477,22 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, + "node_modules/react-hook-form": { + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", @@ -39010,6 +39027,12 @@ } } }, + "react-hook-form": { + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "requires": {} + }, "react-i18next": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", diff --git a/package.json b/package.json index 83b8aad08..93bda091d 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "nanoid": "^5.0.9", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.54.2", "react-i18next": "^15.4.0", "react-router": "^7.1.1" } diff --git a/src/app/i18n/translation.ts b/src/app/i18n/translation.ts index 14c9d7921..072e455e6 100644 --- a/src/app/i18n/translation.ts +++ b/src/app/i18n/translation.ts @@ -50,6 +50,40 @@ export const resources = { isClosed: 'Show more', isOpened: 'Hide', }, + forms: { + name: { + label: 'Name', + placeholder: 'Enter a name', + requiredMessage: 'Please enter your name.', + patternMessage: 'Invalid characters in the input field', + }, + about: { + label: 'About me', + placeholder: 'Tell us about yourself', + }, + submit: 'Submit', + goods: { + title: 'Title', + details: 'Details', + price: 'Price', + }, + authReg: { + login: { + label: 'Username', + placeholder: 'Enter your username', + requiredMessage: 'Enter your username', + patternMessage: '', + }, + password: { + label: 'Password', + placeholder: 'Enter the password', + requiredMessage: 'Enter the password', + patternMessage: '', + }, + submitReg: 'Register', + submitAuth: 'Log in', + }, + }, }, }, ru: { @@ -103,6 +137,40 @@ export const resources = { isClosed: 'Показать больше', isOpened: 'Скрыть', }, + forms: { + name: { + label: 'Имя', + placeholder: 'Введите имя', + requiredMessage: 'Пожалуйста, введите ваше имя', + patternMessage: 'Недопустимые символы в поле ввода', + }, + about: { + label: 'Обо мне', + placeholder: 'Расскажите о себе', + }, + submit: 'Отправить', + goods: { + title: 'Название', + details: 'Описание', + price: 'Цена', + }, + authReg: { + login: { + label: 'Логин', + placeholder: 'Введите имя пользователя', + requiredMessage: 'Введите имя пользователя', + patternMessage: '', + }, + password: { + label: 'Пароль', + placeholder: 'Введите пароль', + requiredMessage: 'Введите пароль', + patternMessage: '', + }, + submitReg: 'Зарегистрироваться', + submitAuth: 'Войти', + }, + }, }, }, }; diff --git a/src/assets/goods.ts b/src/assets/goods.ts index d436b245a..2366cb04b 100644 --- a/src/assets/goods.ts +++ b/src/assets/goods.ts @@ -1,7 +1,9 @@ -import generateAwailibleSizes from '../features/generateAwailibleSizes'; -import generateImageAddress from '../features/generateImageAddress'; -import generateRandomNumber from '../features/generateRandomNumber'; -import generateAwailibleColors from '../features/generateAwailibleColors'; +import { + generateAwailibleSizes, + generateImageAddress, + generateRandomNumber, + generateAwailibleColors, +} from '../features/generators'; import { EGoodsSizes, IGoodsItem } from '../entities/interfaces'; export const goods: IGoodsItem[] = [ diff --git a/src/features/forms/AddGoodForm.tsx b/src/features/forms/AddGoodForm.tsx new file mode 100644 index 000000000..bfdae2203 --- /dev/null +++ b/src/features/forms/AddGoodForm.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { goods } from '../../assets/goods'; +import { EThemeVariables, StoreContext } from '../../app/StoreContext'; +import { Btn } from '../../shared'; +import { IGoodsItem } from 'src/entities/interfaces'; +import { generateRandomNumber } from '../generators'; + +export const AddGoodForm: React.FC = () => { + const { + handleSubmit, + register, + reset, + formState: { errors }, + } = useForm(); + const [lastId, setLastId] = React.useState(goods[goods.length - 1].id); + const { t } = useTranslation(); + + const onSubmit = (data: Partial): void => { + setLastId(lastId + 1); + console.log('Form :', data); + reset(); + }; + + const { theme } = React.useContext(StoreContext); + const isDarkTheme = theme === EThemeVariables.DARK; + + const className = clsx( + 'w-full p-2 border-[1px] border-solid active:outline focus:outline rounded', + 'disabled:bg-w-100 txt-b-700', + isDarkTheme + ? 'bg-b-900 border-b-100 active:outline-w-900 focus:outline-w-900' + : 'bg-w-900 active:outline-b-900 border-b-200 focus:outline-b-900' + ); + + return ( +
+ + + + + +