From 06afed282610900ead0e8739925717bcfd5668d6 Mon Sep 17 00:00:00 2001 From: Andres Date: Sat, 29 Jul 2023 12:47:02 +0200 Subject: [PATCH] homework 1: components --- package.json | 3 + pnpm-lock.yaml | 234 +++++++++++++++++- src/assets/icons/camera.tsx | 21 ++ src/assets/icons/check.tsx | 20 ++ src/assets/icons/chevron-up.tsx | 28 +++ src/assets/icons/close.tsx | 30 +++ src/assets/icons/edit.tsx | 32 +++ src/assets/icons/email.tsx | 74 ++++++ src/assets/icons/eye.tsx | 22 ++ src/assets/icons/index.ts | 11 + src/assets/icons/logo.tsx | 30 +++ src/assets/icons/logout.tsx | 31 +++ src/assets/icons/person-outline.tsx | 22 ++ src/assets/icons/visibility-off.tsx | 20 ++ src/assets/index.ts | 1 + src/components/ui/button/button.module.scss | 97 +++++++- src/components/ui/card/card.module.scss | 7 + src/components/ui/card/card.stories.tsx | 25 ++ src/components/ui/card/card.tsx | 15 ++ src/components/ui/card/index.ts | 1 + .../ui/checkbox/checkbox.module.scss | 80 ++++++ .../ui/checkbox/checkbox.stories.tsx | 35 +++ src/components/ui/checkbox/checkbox.tsx | 66 +++++ src/components/ui/checkbox/index.ts | 1 + src/components/ui/index.ts | 3 + src/components/ui/text-field/index.ts | 1 + .../ui/text-field/text-field.module.scss | 71 ++++++ .../ui/text-field/text-field.stories.ts | 35 +++ src/components/ui/text-field/text-field.tsx | 94 +++++++ src/components/ui/typography/index.ts | 1 + .../ui/typography/typography.module.scss | 72 ++++++ .../ui/typography/typography.stories.tsx | 123 +++++++++ src/components/ui/typography/typography.tsx | 37 +++ src/styles/_tokens.scss | 13 + src/styles/index.scss | 1 + 35 files changed, 1343 insertions(+), 14 deletions(-) create mode 100644 src/assets/icons/camera.tsx create mode 100644 src/assets/icons/check.tsx create mode 100644 src/assets/icons/chevron-up.tsx create mode 100644 src/assets/icons/close.tsx create mode 100644 src/assets/icons/edit.tsx create mode 100644 src/assets/icons/email.tsx create mode 100644 src/assets/icons/eye.tsx create mode 100644 src/assets/icons/index.ts create mode 100644 src/assets/icons/logo.tsx create mode 100644 src/assets/icons/logout.tsx create mode 100644 src/assets/icons/person-outline.tsx create mode 100644 src/assets/icons/visibility-off.tsx create mode 100644 src/assets/index.ts create mode 100644 src/components/ui/card/card.module.scss create mode 100644 src/components/ui/card/card.stories.tsx create mode 100644 src/components/ui/card/card.tsx create mode 100644 src/components/ui/card/index.ts create mode 100644 src/components/ui/checkbox/checkbox.module.scss create mode 100644 src/components/ui/checkbox/checkbox.stories.tsx create mode 100644 src/components/ui/checkbox/checkbox.tsx create mode 100644 src/components/ui/checkbox/index.ts create mode 100644 src/components/ui/text-field/index.ts create mode 100644 src/components/ui/text-field/text-field.module.scss create mode 100644 src/components/ui/text-field/text-field.stories.ts create mode 100644 src/components/ui/text-field/text-field.tsx create mode 100644 src/components/ui/typography/index.ts create mode 100644 src/components/ui/typography/typography.module.scss create mode 100644 src/components/ui/typography/typography.stories.tsx create mode 100644 src/components/ui/typography/typography.tsx create mode 100644 src/styles/_tokens.scss diff --git a/package.json b/package.json index c8428a8..9c67231 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ }, "dependencies": { "@fontsource/roboto": "^5.0.5", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-label": "^2.0.2", + "clsx": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6216568..c9e14e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ dependencies: '@fontsource/roboto': specifier: ^5.0.5 version: 5.0.5 + '@radix-ui/react-checkbox': + specifier: ^1.0.4 + version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-label': + specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + clsx: + specifier: ^2.0.0 + version: 2.0.0 react: specifier: ^18.2.0 version: 18.2.0 @@ -1396,7 +1405,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true /@babel/template@7.22.5: resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} @@ -1979,6 +1987,219 @@ packages: tslib: 2.6.1 dev: true + /@radix-ui/primitive@1.0.1: + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + dependencies: + '@babel/runtime': 7.22.6 + dev: false + + /@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-context@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-label@2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-slot@1.0.2(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.15)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + react: 18.2.0 + dev: false + /@rollup/pluginutils@5.0.2: resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} @@ -3109,7 +3330,6 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - dev: true /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} @@ -3123,7 +3343,6 @@ packages: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} dependencies: '@types/react': 18.2.15 - dev: true /@types/react@18.2.15: resolution: {integrity: sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==} @@ -3131,11 +3350,9 @@ packages: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.3 csstype: 3.1.2 - dev: true /@types/scheduler@0.16.3: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - dev: true /@types/semver@7.5.0: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} @@ -4046,6 +4263,11 @@ packages: engines: {node: '>=0.8'} dev: true + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -4215,7 +4437,6 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - dev: true /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -7459,7 +7680,6 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: true /regenerator-transform@0.15.1: resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} diff --git a/src/assets/icons/camera.tsx b/src/assets/icons/camera.tsx new file mode 100644 index 0000000..8b0b2e4 --- /dev/null +++ b/src/assets/icons/camera.tsx @@ -0,0 +1,21 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/check.tsx b/src/assets/icons/check.tsx new file mode 100644 index 0000000..1f35bda --- /dev/null +++ b/src/assets/icons/check.tsx @@ -0,0 +1,20 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/chevron-up.tsx b/src/assets/icons/chevron-up.tsx new file mode 100644 index 0000000..15c33e1 --- /dev/null +++ b/src/assets/icons/chevron-up.tsx @@ -0,0 +1,28 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const ChevronUp = (props: SVGProps, ref: Ref) => ( + + + + + + + + + + +) +const ForwardRef = forwardRef(ChevronUp) +const Memo = memo(ForwardRef) + +export default Memo diff --git a/src/assets/icons/close.tsx b/src/assets/icons/close.tsx new file mode 100644 index 0000000..3b8a146 --- /dev/null +++ b/src/assets/icons/close.tsx @@ -0,0 +1,30 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' + +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + + + + + + + + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/edit.tsx b/src/assets/icons/edit.tsx new file mode 100644 index 0000000..586cc58 --- /dev/null +++ b/src/assets/icons/edit.tsx @@ -0,0 +1,32 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + + + + + + + + + +) +const ForwardRef = forwardRef(SvgComponent) +const Memo = memo(ForwardRef) + +export default Memo diff --git a/src/assets/icons/email.tsx b/src/assets/icons/email.tsx new file mode 100644 index 0000000..f424822 --- /dev/null +++ b/src/assets/icons/email.tsx @@ -0,0 +1,74 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + + + + + + + + + + + + + + + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/eye.tsx b/src/assets/icons/eye.tsx new file mode 100644 index 0000000..12e4b70 --- /dev/null +++ b/src/assets/icons/eye.tsx @@ -0,0 +1,22 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts new file mode 100644 index 0000000..adf3ef0 --- /dev/null +++ b/src/assets/icons/index.ts @@ -0,0 +1,11 @@ +export { default as Eye } from './eye' +export { default as VisibilityOff } from './visibility-off' +export { default as Check } from './check' +export { default as Email } from './email' +export { default as Camera } from './camera' +export { default as Logout } from './logout' +export { default as Edit } from './edit' +export { default as Logo } from './logo' +export { default as PersonOutline } from './person-outline' +export { default as ChevronUp } from './chevron-up' +export { default as Close } from './close' diff --git a/src/assets/icons/logo.tsx b/src/assets/icons/logo.tsx new file mode 100644 index 0000000..0df7cbd --- /dev/null +++ b/src/assets/icons/logo.tsx @@ -0,0 +1,30 @@ +import { SVGProps } from 'react' +const SvgComponent = (props: SVGProps) => ( + + + + + +) + +export default SvgComponent diff --git a/src/assets/icons/logout.tsx b/src/assets/icons/logout.tsx new file mode 100644 index 0000000..a7c19af --- /dev/null +++ b/src/assets/icons/logout.tsx @@ -0,0 +1,31 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + + + + + + + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/icons/person-outline.tsx b/src/assets/icons/person-outline.tsx new file mode 100644 index 0000000..dd172d1 --- /dev/null +++ b/src/assets/icons/person-outline.tsx @@ -0,0 +1,22 @@ +import { SVGProps } from 'react' +const SvgComponent = (props: SVGProps) => ( + + + + + + + + + + +) + +export default SvgComponent diff --git a/src/assets/icons/visibility-off.tsx b/src/assets/icons/visibility-off.tsx new file mode 100644 index 0000000..c45f878 --- /dev/null +++ b/src/assets/icons/visibility-off.tsx @@ -0,0 +1,20 @@ +import { SVGProps, Ref, forwardRef, memo } from 'react' +const SvgComponent = (props: SVGProps, ref: Ref) => ( + + + +) +const ForwardRef = forwardRef(SvgComponent) + +export default memo(ForwardRef) diff --git a/src/assets/index.ts b/src/assets/index.ts new file mode 100644 index 0000000..826dac4 --- /dev/null +++ b/src/assets/index.ts @@ -0,0 +1 @@ +export * from './icons' diff --git a/src/components/ui/button/button.module.scss b/src/components/ui/button/button.module.scss index d1c56f5..99309a5 100644 --- a/src/components/ui/button/button.module.scss +++ b/src/components/ui/button/button.module.scss @@ -1,19 +1,102 @@ +@mixin button { + all: unset; + + cursor: pointer; + user-select: none; + + display: inline-flex; + flex-shrink: 0; + gap: 0.625rem; + align-items: center; + justify-content: center; + + box-sizing: border-box; + padding: 0.375rem 1.75rem; + + font-size: 1rem; + font-weight: 600; + line-height: 24px; + text-align: center; + text-decoration: none; + + background-color: transparent; + border: none; + border-radius: 0.25rem; + + transition: + var(--transition-duration-basic) background-color, + var(--transition-duration-basic) color; + + &:focus-visible { + outline: 2px solid var(--color-info-700); + } + + &:disabled { + cursor: default; + opacity: 0.5; + } + + &.fullWidth { + justify-content: center; + width: 100%; + } +} + .primary { - background-color: red; + @include button; + + color: var(--color-light-100); + background-color: var(--color-accent-500); + box-shadow: 0 4px 18px rgb(140 97 255 / 35%); + + &:hover:enabled { + background-color: var(--color-accent-300); + } + + &:active:enabled { + background-color: var(--color-accent-700); + } } .secondary { - background-color: green; + @include button; + + color: var(--color-light-100); + background-color: var(--color-dark-300); + box-shadow: 0 2px 10px 0 #6d6d6d40; + + &:hover:enabled { + background-color: var(--color-dark-100); + } + + &:active:enabled { + background-color: var(--color-dark-500); + } } .tertiary { - background-color: blue; + @include button; + + color: var(--color-accent-500); + background-color: var(--color-dark-900); + border: 1px solid var(--color-accent-700); + + &:hover:enabled { + background-color: var(--color-dark-500); + } + + &:active:enabled { + background-color: var(--color-accent-900); + } } .link { - background-color: yellow; -} + @include button; -.fullWidth { - width: 100%; + padding: 0.375rem 0; + + font-weight: var(--font-weight-bold); + line-height: var(--line-height-m); + color: var(--color-accent-500); + text-decoration-line: underline; } diff --git a/src/components/ui/card/card.module.scss b/src/components/ui/card/card.module.scss new file mode 100644 index 0000000..226fa87 --- /dev/null +++ b/src/components/ui/card/card.module.scss @@ -0,0 +1,7 @@ +.root { + background-color: var(--color-dark-700); + border-radius: 2px; + box-shadow: + 1px 1px 2px rgb(0 0 0 / 10%), + -1px -1px 2px rgb(0 0 0 / 10%); +} diff --git a/src/components/ui/card/card.stories.tsx b/src/components/ui/card/card.stories.tsx new file mode 100644 index 0000000..b650272 --- /dev/null +++ b/src/components/ui/card/card.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Card } from './' + +import { Typography } from '@/components' + +const meta = { + title: 'Components/Card', + component: Card, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + children: Card, + style: { + width: '300px', + height: '300px', + padding: '24px', + }, + }, +} diff --git a/src/components/ui/card/card.tsx b/src/components/ui/card/card.tsx new file mode 100644 index 0000000..180b8d5 --- /dev/null +++ b/src/components/ui/card/card.tsx @@ -0,0 +1,15 @@ +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +import { clsx } from 'clsx' + +import s from './card.module.scss' + +export type CardProps = {} & ComponentPropsWithoutRef<'div'> + +export const Card = forwardRef(({ className, ...restProps }, ref) => { + const classNames = { + root: clsx(s.root, className), + } + + return
+}) diff --git a/src/components/ui/card/index.ts b/src/components/ui/card/index.ts new file mode 100644 index 0000000..efb3293 --- /dev/null +++ b/src/components/ui/card/index.ts @@ -0,0 +1 @@ +export * from './card' diff --git a/src/components/ui/checkbox/checkbox.module.scss b/src/components/ui/checkbox/checkbox.module.scss new file mode 100644 index 0000000..2a82b44 --- /dev/null +++ b/src/components/ui/checkbox/checkbox.module.scss @@ -0,0 +1,80 @@ +.container { + display: flex; + align-items: center; +} + +.label { + cursor: pointer; + display: flex; + align-items: center; + + &.disabled { + cursor: initial; + color: var(--color-dark-100); + } +} + +.root { + cursor: pointer; + + display: flex; + align-items: center; + justify-content: center; + + width: 18px; + height: 18px; + + background-color: var(--color-dark-900); + border: 2px solid var(--color-light-900); + border-radius: 2px; + + &:focus { + background-color: var(--color-bg-focus); + } + + &:disabled { + cursor: initial; + border-color: var(--color-dark-100); + } + + &[data-state='checked']:disabled { + background-color: var(--color-checkbox-disabled); + } +} + +.buttonWrapper { + cursor: pointer; + + display: flex; + align-items: center; + justify-content: center; + + width: 36px; + height: 36px; + + background-color: var(--color-dark-900); + border-radius: 50%; + + &.disabled { + cursor: initial; + } + + &:focus-within, + &:hover:not(.disabled), + &:hover .root:not([data-state='checked']) { + background-color: var(--color-dark-500); + } + + &:active:not(.disabled) { + background-color: var(--color-dark-100); + } + + &.left { + margin-left: -9px; + } +} + +.indicator { + width: 18px; + height: 18px; +} diff --git a/src/components/ui/checkbox/checkbox.stories.tsx b/src/components/ui/checkbox/checkbox.stories.tsx new file mode 100644 index 0000000..40d183e --- /dev/null +++ b/src/components/ui/checkbox/checkbox.stories.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' + +import { Meta, StoryObj } from '@storybook/react' + +import { Checkbox } from './checkbox' +const meta = { + title: 'Components/Checkbox', + component: Checkbox, + tags: ['autodocs'], +} satisfies Meta + +export default meta + +type Story = StoryObj +export const Uncontrolled: Story = { + args: { + label: 'Click here', + disabled: false, + }, +} + +export const Controlled: Story = { + render: args => { + const [checked, setChecked] = useState(false) + + return ( + setChecked(!checked)} + /> + ) + }, +} diff --git a/src/components/ui/checkbox/checkbox.tsx b/src/components/ui/checkbox/checkbox.tsx new file mode 100644 index 0000000..b833184 --- /dev/null +++ b/src/components/ui/checkbox/checkbox.tsx @@ -0,0 +1,66 @@ +import { FC } from 'react' + +import * as CheckboxRadix from '@radix-ui/react-checkbox' +import * as LabelRadix from '@radix-ui/react-label' +import { clsx } from 'clsx' + +import s from './checkbox.module.scss' + +import { Check } from '@/assets/icons' +import { Typography } from '@/components' + +export type CheckboxProps = { + className?: string + checked?: boolean + onChange?: (checked: boolean) => void + disabled?: boolean + required?: boolean + label?: string + id?: string + position?: 'left' +} + +export const Checkbox: FC = ({ + checked, + onChange, + position, + disabled, + required, + label, + id, + className, +}) => { + const classNames = { + container: clsx(s.container, className), + buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left), + root: s.root, + indicator: s.indicator, + label: clsx(s.label, disabled && s.disabled), + } + + return ( +
+ + +
+ + {checked && ( + + + + )} + +
+ {label} +
+
+
+ ) +} diff --git a/src/components/ui/checkbox/index.ts b/src/components/ui/checkbox/index.ts new file mode 100644 index 0000000..31d7689 --- /dev/null +++ b/src/components/ui/checkbox/index.ts @@ -0,0 +1 @@ +export * from './checkbox' diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 14757e7..92e7644 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -1 +1,4 @@ export * from './button' +export * from './card' +export * from './typography' +export * from './checkbox' diff --git a/src/components/ui/text-field/index.ts b/src/components/ui/text-field/index.ts new file mode 100644 index 0000000..e5f7300 --- /dev/null +++ b/src/components/ui/text-field/index.ts @@ -0,0 +1 @@ +export * from './text-field' diff --git a/src/components/ui/text-field/text-field.module.scss b/src/components/ui/text-field/text-field.module.scss new file mode 100644 index 0000000..ae34ae8 --- /dev/null +++ b/src/components/ui/text-field/text-field.module.scss @@ -0,0 +1,71 @@ +.root { + width: 100%; +} + +.fieldContainer { + position: relative; + width: 100%; +} + +.field { + width: 100%; + padding: 6px 12px; + + font-family: inherit; + font-size: 16px; + font-weight: 400; + line-height: 24px; + color: var(--color-light-100); + + background: transparent; + border: 1px solid var(--color-dark-300); + outline: 0; + + transition: border-color 0.2s; + + &::placeholder { + color: var(--color-dark-100); + } + + &:focus-visible { + border-color: var(--color-info-700); + outline: 1px solid var(--color-info-700); + } + + &:hover { + background: var(--color-dark-700); + } + + &.error { + color: var(--color-danger-300); + border-color: var(--color-danger-300); + } +} + +.label { + margin-bottom: 1px; + color: var(--color-dark-100); +} + +.showPassword { + cursor: pointer; + + position: absolute; + top: 50%; + right: 0; + bottom: 50%; + transform: translateY(-50%); + + width: 20px; + height: 20px; + margin-right: 12px; + padding: 0; + + background: transparent; + border: 0; + outline: 0; + + &:focus-visible { + outline: var(--outline-focus); + } +} diff --git a/src/components/ui/text-field/text-field.stories.ts b/src/components/ui/text-field/text-field.stories.ts new file mode 100644 index 0000000..cbb0a88 --- /dev/null +++ b/src/components/ui/text-field/text-field.stories.ts @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { TextField } from './' + +const meta = { + title: 'Components/TextField', + component: TextField, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + label: 'Label', + placeholder: 'Placeholder', + }, +} + +export const Password: Story = { + args: { + label: 'Label', + placeholder: 'Password', + type: 'password', + }, +} + +export const Error: Story = { + args: { + label: 'Input with error', + value: 'Wrong value', + errorMessage: 'Error message', + }, +} diff --git a/src/components/ui/text-field/text-field.tsx b/src/components/ui/text-field/text-field.tsx new file mode 100644 index 0000000..2ce052e --- /dev/null +++ b/src/components/ui/text-field/text-field.tsx @@ -0,0 +1,94 @@ +import { ChangeEvent, ComponentProps, ComponentPropsWithoutRef, forwardRef, useState } from 'react' + +import { clsx } from 'clsx' + +import s from './text-field.module.scss' + +import { VisibilityOff, Eye } from '@/assets' +import { Typography } from '@/components' + +export type TextFieldProps = { + onValueChange?: (value: string) => void + containerProps?: ComponentProps<'div'> + labelProps?: ComponentProps<'label'> + errorMessage?: string + label?: string +} & ComponentPropsWithoutRef<'input'> + +export const TextField = forwardRef( + ( + { + className, + errorMessage, + placeholder, + type, + containerProps, + labelProps, + label, + onChange, + onValueChange, + ...restProps + }, + ref + ) => { + const [showPassword, setShowPassword] = useState(false) + + const isShowPasswordButtonShown = type === 'password' + + const finalType = getFinalType(type, showPassword) + + function handleChange(e: ChangeEvent) { + onChange?.(e) + onValueChange?.(e.target.value) + } + + const classNames = { + root: clsx(s.root, containerProps?.className), + fieldContainer: clsx(s.fieldContainer), + field: clsx(s.field, !!errorMessage && s.error, className), + label: clsx(s.label, labelProps?.className), + error: clsx(s.error), + } + + return ( +
+ {label && ( + + {label} + + )} +
+ + {isShowPasswordButtonShown && ( + + )} +
+ + + {errorMessage} + +
+ ) + } +) + +function getFinalType(type: ComponentProps<'input'>['type'], showPassword: boolean) { + if (type === 'password' && showPassword) { + return 'text' + } + + return type +} diff --git a/src/components/ui/typography/index.ts b/src/components/ui/typography/index.ts new file mode 100644 index 0000000..324025e --- /dev/null +++ b/src/components/ui/typography/index.ts @@ -0,0 +1 @@ +export * from './typography' diff --git a/src/components/ui/typography/typography.module.scss b/src/components/ui/typography/typography.module.scss new file mode 100644 index 0000000..f2323aa --- /dev/null +++ b/src/components/ui/typography/typography.module.scss @@ -0,0 +1,72 @@ +@mixin typography($fontSize, $lineHeight, $fontWeight) { + margin: 0; + + font-size: $fontSize; + font-weight: $fontWeight; + line-height: $lineHeight; + color: var(--color-text-primary); +} + +.large { + @include typography(var(--font-size-xxl), var(--line-height-l), var(--font-weight-bold)); +} + +.h1 { + @include typography(var(--font-size-xl), var(--line-height-l), var(--font-weight-bold)); +} + +.h2 { + @include typography(var(--font-size-l), var(--line-height-m), var(--font-weight-bold)); +} + +.h3 { + @include typography(var(--font-size-m), var(--line-height-m), var(--font-weight-bold)); +} + +.body1 { + @include typography(var(--font-size-m), var(--line-height-m), var(--font-weight-regular)); +} + +.subtitle1 { + @include typography(var(--font-size-m), var(--line-height-m), var(--font-weight-bold)); +} + +.body2 { + @include typography(var(--font-size-s), var(--line-height-m), var(--font-weight-regular)); +} + +.subtitle2 { + @include typography(var(--font-size-s), var(--line-height-m), var(--font-weight-bold)); +} + +.overline { + @include typography(var(--font-size-xs), var(--line-height-s), var(--font-weight-bold)); +} + +.caption { + @include typography(var(--font-size-xs), var(--line-height-s), var(--font-weight-regular)); +} + +.link1, +.link1:visited { + @include typography(var(--font-size-s), var(--line-height-m), var(--font-weight-regular)); + + cursor: pointer; + color: var(--color-text-link); + text-decoration: underline; +} + +.link2, +.link2:visited { + @include typography(var(--font-size-xs), var(--line-height-s), var(--font-weight-regular)); + + cursor: pointer; + color: var(--color-text-link); + text-decoration: underline; +} + +.error { + @include typography(var(--font-size-xs), var(--line-height-m), var(--font-weight-regular)); + + color: var(--color-danger-300); +} diff --git a/src/components/ui/typography/typography.stories.tsx b/src/components/ui/typography/typography.stories.tsx new file mode 100644 index 0000000..c729896 --- /dev/null +++ b/src/components/ui/typography/typography.stories.tsx @@ -0,0 +1,123 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Typography } from './' + +const meta = { + title: 'Components/Typography', + component: Typography, + tags: ['autodocs'], + argTypes: { + variant: { + options: [ + 'large', + 'h1', + 'h2', + 'h3', + 'body1', + 'body2', + 'subtitle1', + 'subtitle2', + 'caption', + 'overline', + 'link1', + 'link2', + 'error', + ], + control: { type: 'radio' }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Large: Story = { + args: { + children: 'Card content', + variant: 'large', + }, +} + +export const H1: Story = { + args: { + children: 'Card content', + variant: 'h1', + }, +} + +export const H2: Story = { + args: { + children: 'Card content', + variant: 'h2', + }, +} + +export const H3: Story = { + args: { + children: 'Card content', + variant: 'h3', + }, +} + +export const Body1: Story = { + args: { + children: 'Card content', + variant: 'body1', + }, +} + +export const Body2: Story = { + args: { + children: 'Card content', + variant: 'body2', + }, +} + +export const Subtitle1: Story = { + args: { + children: 'Card content', + variant: 'subtitle1', + }, +} + +export const Subtitle2: Story = { + args: { + children: 'Card content', + variant: 'subtitle2', + }, +} + +export const Caption: Story = { + args: { + children: 'Card content', + variant: 'caption', + }, +} + +export const Overline: Story = { + args: { + children: 'Card content', + variant: 'overline', + }, +} + +export const Link1: Story = { + args: { + children: 'Card content', + variant: 'link1', + }, +} + +export const Link2: Story = { + args: { + children: 'Card content', + variant: 'link2', + }, +} + +export const Error: Story = { + args: { + children: 'Card content', + variant: 'error', + }, +} diff --git a/src/components/ui/typography/typography.tsx b/src/components/ui/typography/typography.tsx new file mode 100644 index 0000000..b82f58a --- /dev/null +++ b/src/components/ui/typography/typography.tsx @@ -0,0 +1,37 @@ +import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react' + +import { clsx } from 'clsx' + +import s from './typography.module.scss' + +export interface TextProps { + as?: T + variant?: + | 'large' + | 'h1' + | 'h2' + | 'h3' + | 'body1' + | 'body2' + | 'subtitle1' + | 'subtitle2' + | 'caption' + | 'overline' + | 'link1' + | 'link2' + | 'error' + children?: ReactNode + className?: string +} + +export function Typography({ + as, + className, + variant = 'body1', + ...restProps +}: TextProps & Omit, keyof TextProps>) { + const classNames = clsx(s.text, s[variant], className) + const Component = as || 'p' + + return +} diff --git a/src/styles/_tokens.scss b/src/styles/_tokens.scss new file mode 100644 index 0000000..0fdbcaa --- /dev/null +++ b/src/styles/_tokens.scss @@ -0,0 +1,13 @@ +:root { + /* outlines */ + --outline-focus: 2px solid var(--color-outline-focus); + + /* transitions */ + --transition-duration-basic: 200ms; + + /* border-radius */ + --border-radius-s: 0.25rem; + + /* header */ + --header-height: 60px; +} diff --git a/src/styles/index.scss b/src/styles/index.scss index 7063224..ac8e54f 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -1,3 +1,4 @@ @forward 'colors'; @forward 'typography'; @forward 'boilerplate'; +@forward 'tokens';