From 8085b3bbde37cd2996386199196b54dd8c44a44e Mon Sep 17 00:00:00 2001 From: RedrockJS Date: Wed, 4 Jun 2025 14:59:10 +0300 Subject: [PATCH] feat: ui components --- public/svg/email.svg | 7 + public/svg/led-on.svg | 1 + public/svg/map.svg | 7 + public/svg/phone-call.svg | 7 + src/core/styles/variables.scss | 3 +- .../advanced-phone-input.tsx | 34 +++++ .../advancedPhoneInput.module.scss | 19 +++ src/shared/ui/advanced-phone-input/index.ts | 1 + src/shared/ui/button/button.module.scss | 20 ++- src/shared/ui/button/button.tsx | 16 +- src/shared/ui/index.ts | 2 + src/shared/ui/input/input.module.scss | 139 +++++------------- src/shared/ui/input/input.tsx | 71 +++------ src/shared/ui/phone-input/index.ts | 0 src/shared/ui/text-area/index.ts | 1 + src/shared/ui/text-area/text-area.module.scss | 103 +++++++++++++ src/shared/ui/text-area/text-area.tsx | 32 ++++ .../home/ui/contacts/contacts.module.scss | 69 +++++++-- src/views/home/ui/contacts/contacts.tsx | 37 ++++- src/views/home/ui/footer/footer.module.scss | 13 ++ src/views/home/ui/footer/footer.tsx | 43 +++--- src/views/home/ui/license/license.module.scss | 6 +- src/views/home/ui/license/license.tsx | 10 +- src/views/home/ui/offer/offer.module.scss | 20 ++- src/views/home/ui/offer/offer.tsx | 27 ++-- 25 files changed, 475 insertions(+), 213 deletions(-) create mode 100644 public/svg/email.svg create mode 100644 public/svg/led-on.svg create mode 100644 public/svg/map.svg create mode 100644 public/svg/phone-call.svg create mode 100644 src/shared/ui/advanced-phone-input/advanced-phone-input.tsx create mode 100644 src/shared/ui/advanced-phone-input/advancedPhoneInput.module.scss create mode 100644 src/shared/ui/advanced-phone-input/index.ts create mode 100644 src/shared/ui/phone-input/index.ts create mode 100644 src/shared/ui/text-area/index.ts create mode 100644 src/shared/ui/text-area/text-area.module.scss create mode 100644 src/shared/ui/text-area/text-area.tsx diff --git a/public/svg/email.svg b/public/svg/email.svg new file mode 100644 index 0000000..adecec7 --- /dev/null +++ b/public/svg/email.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/public/svg/led-on.svg b/public/svg/led-on.svg new file mode 100644 index 0000000..591ee6a --- /dev/null +++ b/public/svg/led-on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svg/map.svg b/public/svg/map.svg new file mode 100644 index 0000000..607bcb0 --- /dev/null +++ b/public/svg/map.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/public/svg/phone-call.svg b/public/svg/phone-call.svg new file mode 100644 index 0000000..70438ac --- /dev/null +++ b/public/svg/phone-call.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/core/styles/variables.scss b/src/core/styles/variables.scss index d01fdb0..331471b 100644 --- a/src/core/styles/variables.scss +++ b/src/core/styles/variables.scss @@ -22,4 +22,5 @@ $color-darkgray: #999999; $color-text: #333333; $color-text-light: #222222; $color-mark: #E96526; -$color-error: #FF0000; \ No newline at end of file +$color-error: #FF0000; +$color-gray-border: #555555; \ No newline at end of file diff --git a/src/shared/ui/advanced-phone-input/advanced-phone-input.tsx b/src/shared/ui/advanced-phone-input/advanced-phone-input.tsx new file mode 100644 index 0000000..179f8d1 --- /dev/null +++ b/src/shared/ui/advanced-phone-input/advanced-phone-input.tsx @@ -0,0 +1,34 @@ +import s from './advancedPhoneInput.module.scss'; +import { DetailedHTMLProps, InputHTMLAttributes } from 'react'; +import { clsx } from 'clsx'; +import { Button, Input } from '@shared/ui'; + +type AdvancedPhoneInputProps = { + containerClassName?: string; + inputClassName?: string; + buttonClassName?: string; + onClick?: () => void; + text: string; +} & DetailedHTMLProps, HTMLInputElement>; + +export default function AdvancedPhoneInput({ + containerClassName, + inputClassName, + buttonClassName, + onClick, + text, + ...props +}: AdvancedPhoneInputProps) { + return ( +
+ + +
+ ); +} diff --git a/src/shared/ui/advanced-phone-input/advancedPhoneInput.module.scss b/src/shared/ui/advanced-phone-input/advancedPhoneInput.module.scss new file mode 100644 index 0000000..01434a0 --- /dev/null +++ b/src/shared/ui/advanced-phone-input/advancedPhoneInput.module.scss @@ -0,0 +1,19 @@ +.Container { + position: relative; + //width: 100%; +} + +.Phone { + width: 100%; + position: relative; + z-index: 2; + padding-left: 2vw; +} + +.Button { + position: absolute; + right: 4px; + top: calc(50% - 25px); + z-index: 3; + min-height: calc(100% - 4px); +} \ No newline at end of file diff --git a/src/shared/ui/advanced-phone-input/index.ts b/src/shared/ui/advanced-phone-input/index.ts new file mode 100644 index 0000000..d9ceda9 --- /dev/null +++ b/src/shared/ui/advanced-phone-input/index.ts @@ -0,0 +1 @@ +export { default as AdvancedPhoneInput } from './advanced-phone-input'; diff --git a/src/shared/ui/button/button.module.scss b/src/shared/ui/button/button.module.scss index 18fb351..1202bdd 100644 --- a/src/shared/ui/button/button.module.scss +++ b/src/shared/ui/button/button.module.scss @@ -2,17 +2,22 @@ display: flex; align-items: center; justify-content: center; - padding: 13px 33px; + padding: 10px 32px; border-radius: 28px; + min-height: 55px; font-family: $font-open-sans; font-weight: $font-regular; font-size: 24px; - line-height: 1; + line-height: 100%; transition: all 0.15s linear; white-space: nowrap; + width: max-content; + &_fullWidth { + width: 100%; + } svg { width: 18px; @@ -24,6 +29,11 @@ &:hover { cursor: pointer; + box-shadow: 1px 1px 1px 0px $color-darkgray; + } + + &:active { + box-shadow: inset 1px 1px 2px 0px $color-darkgray; } &:hover svg { @@ -40,6 +50,12 @@ color: $color-white; } + &_ghost { + background: transparent; + color: $color-white; + border: 1px solid $color-gray-border; + } + &_disabled { cursor: not-allowed; } diff --git a/src/shared/ui/button/button.tsx b/src/shared/ui/button/button.tsx index 7e58bce..ea39f3d 100644 --- a/src/shared/ui/button/button.tsx +++ b/src/shared/ui/button/button.tsx @@ -1,5 +1,11 @@ import s from './button.module.scss'; -import { FunctionComponent, HTMLAttributes, ReactNode, SVGProps } from 'react'; +import { + ButtonHTMLAttributes, + DetailedHTMLProps, + FunctionComponent, + ReactNode, + SVGProps, +} from 'react'; import { clsx } from 'clsx'; type ButtonProps = { @@ -9,7 +15,11 @@ type ButtonProps = { Icon?: FunctionComponent>; onClick?: () => void; variant?: 'default' | 'orange' | 'ghost'; -} & HTMLAttributes; + fullWidth?: boolean; +} & DetailedHTMLProps< + ButtonHTMLAttributes, + HTMLButtonElement +>; export default function Button({ className, @@ -18,6 +28,7 @@ export default function Button({ Icon, disabled, variant = 'default', + fullWidth = false, ...props }: ButtonProps) { return ( @@ -26,6 +37,7 @@ export default function Button({ s.Button, disabled && s.Button_disabled, s['Button_' + variant], + fullWidth && s.Button_fullWidth, className, )} onClick={onClick} diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 362df51..b794500 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -1,3 +1,5 @@ export { Button } from './button'; export { Mark } from './mark'; export { Input } from './input'; +export { AdvancedPhoneInput } from './advanced-phone-input'; +export { TextArea } from './text-area'; diff --git a/src/shared/ui/input/input.module.scss b/src/shared/ui/input/input.module.scss index 5de0f03..7b7e2f8 100644 --- a/src/shared/ui/input/input.module.scss +++ b/src/shared/ui/input/input.module.scss @@ -1,113 +1,54 @@ -.input { - position: relative; +.Input { display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - width: 100%; - height: 42px; + background: $color-white; border: 1px solid $color-darkgray; border-radius: 28px; + padding: 10px 24px; + transition: border ease .5s; - //@include iftablet { - // height: rem(53px); - //} + font-family: $font-open-sans; + font-weight: $font-regular; + font-size: 24px; + line-height: 100%; + color: $color-text; + width: max-content; - &__self { + &:focus { + border-color: $color-orange; + transition: border-color ease .5s; + } + + &:hover { + border-color: $color-text; + transition: border-color ease .5s; + } + + &:focus:hover { + border-color: $color-orange; + transition: border-color ease .5s; + } + + &_fullWidth{ width: 100%; - height: 100%; - padding: 22px 16px 6px 16px; - font-size: 14px; - line-height: 100%; - color: $color-text; - opacity: 0.9; - - //@include iftablet { - // padding: rem(26px) rem(16px) rem(6px) rem(16px); - // font-size: rem(16px); - // line-height: 130%; - //} } - &__placeholder { - position: absolute; - top: 12px; - left: 16px; - font-size: 14px; - line-height: 130%; - color: $color-darkgray; - opacity: 0.4; - transition: all 0.15s ease-in; + &_ghost { + background: transparent; + color: $color-white; - //@include iftablet { - // top: rem(16px); - // font-size: rem(16px); - //} - } - - &__placeholder_active { - top: 6px; - left: 16px; - font-size: 11px; - color: $color-darkgray; - - //@include iftablet { - // top: rem(6px); - // left: rem(16px); - // font-size: rem(14px); - //} - } - - &--error { - border-color: $color-error !important; - } - - &__errorMessage { - margin-top: 10px; - - font-size: 12px; - line-height: 130%; - color: $color-error; - - //@include iftablet { - // margin-top: 6px; - //} - } - - &--disabled { - cursor: not-allowed; - color: $color-lightgray; - } - - &__password-rules { - display: flex; - align-items: center; - margin-top: 6px; - gap: 6px; - - //@include iftablet { - // gap: rem(10px); - //} - //@include ifdesktop { - // margin-top: rem(9px); - //} - - &__rule-block { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; + &:focus { + border-color: $color-orange; + transition: border-color ease .5s; } - &__rule-text { - font-family: $font-open-sans; - font-size: 11px; - line-height: 100%; - color: rgba(35, 48, 56, 0.7); + &:hover { + border-color: $color-white; + transition: border-color ease .5s; + } - //@include iftablet { - // font-size: rem(14px); - //} + &:focus:hover { + border-color: $color-orange; + transition: border-color ease .5s; } } -} +} \ No newline at end of file diff --git a/src/shared/ui/input/input.tsx b/src/shared/ui/input/input.tsx index 7c52e03..8f1beb2 100644 --- a/src/shared/ui/input/input.tsx +++ b/src/shared/ui/input/input.tsx @@ -2,67 +2,32 @@ import s from './input.module.scss'; -import React, { InputHTMLAttributes, useState } from 'react'; -import clsx from 'clsx'; +import { DetailedHTMLProps, InputHTMLAttributes } from 'react'; +import { clsx } from 'clsx'; -type InputPropsType = { +type InputProps = { + wrapperClassName?: string; className?: string; - placeholder: string; - errorMessage?: string | boolean; -} & InputHTMLAttributes; + fullWidth?: boolean; + variant?: 'default' | 'ghost'; +} & DetailedHTMLProps, HTMLInputElement>; const Input = ({ - placeholder, - errorMessage, - disabled, className, - onChange, + fullWidth = false, + variant = 'default', ...props -}: InputPropsType) => { - const [onFocus, setOnFocus] = useState(false); - - const hasValue = - typeof props.value === 'string' - ? props.value.length > 0 - : props.value !== undefined && props.value !== null; - +}: InputProps) => { return ( -
-
- - { - if (typeof props.onFocus !== 'undefined') { - props.onFocus(event); - } - setOnFocus(true); - }} - onBlur={(event) => { - if (typeof props.onBlur !== 'undefined') { - props.onBlur(event); - } - setOnFocus(false); - }} - disabled={disabled} - className={clsx(s.input__self, { [s['input--disabled']]: disabled })} - /> -
- {errorMessage && ( - {errorMessage} + + /> ); }; diff --git a/src/shared/ui/phone-input/index.ts b/src/shared/ui/phone-input/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/ui/text-area/index.ts b/src/shared/ui/text-area/index.ts new file mode 100644 index 0000000..5778f2d --- /dev/null +++ b/src/shared/ui/text-area/index.ts @@ -0,0 +1 @@ +export { default as TextArea } from './text-area'; diff --git a/src/shared/ui/text-area/text-area.module.scss b/src/shared/ui/text-area/text-area.module.scss new file mode 100644 index 0000000..e8ab19f --- /dev/null +++ b/src/shared/ui/text-area/text-area.module.scss @@ -0,0 +1,103 @@ +.Container { + display: flex; + flex-direction: column; + box-sizing: border-box; + width: max-content; + /* Allows the `resize` property to work on the div */ + overflow: hidden; + + &_fullWidth{ + width: 100%; + } +} + +.Area { + background: $color-white; + border: 1px solid $color-darkgray; + border-radius: 20px; + padding: 10px 24px; + transition: border ease .5s; + + font-family: $font-open-sans; + font-weight: $font-regular; + font-size: 24px; + line-height: 100%; + color: $color-text; + + resize: none; + display: block; + width: 100%; + flex-grow: 1; + + /* scrollbar */ + & { + /* Arrow mouse cursor over the scrollbar */ + cursor: auto; + } + &::-webkit-scrollbar { + width: 12px; + height: 12px; + } + &::-webkit-scrollbar-track, + &::-webkit-scrollbar-thumb { + background-clip: content-box; + border: 4px solid transparent; + border-radius: 12px; + } + &::-webkit-scrollbar-track { + background-color: #333; // цвет дорожки + } + &::-webkit-scrollbar-thumb { + background-color: #999; // цвет указателя + } + @media (hover: hover) { + :where(&:not(:disabled))::-webkit-scrollbar-thumb:hover { + background-color: #999; + } + } + &:where(:autofill) { + /* Reliably removes native autofill colors */ + background-clip: text; + -webkit-text-fill-color: #999; + } + + + &:focus { + border-color: $color-orange; + transition: border-color ease .5s; + } + + &:hover { + border-color: $color-text; + transition: border-color ease .5s; + } + + &:focus:hover { + border-color: $color-orange; + transition: border-color ease .5s; + } + + &:focus-visible { + outline: none; + } + + &_ghost { + background: transparent; + color: $color-white; + + &:focus { + border-color: $color-orange; + transition: border-color ease .5s; + } + + &:hover { + border-color: $color-white; + transition: border-color ease .5s; + } + + &:focus:hover { + border-color: $color-orange; + transition: border-color ease .5s; + } + } +} \ No newline at end of file diff --git a/src/shared/ui/text-area/text-area.tsx b/src/shared/ui/text-area/text-area.tsx new file mode 100644 index 0000000..ce89539 --- /dev/null +++ b/src/shared/ui/text-area/text-area.tsx @@ -0,0 +1,32 @@ +import s from './text-area.module.scss'; +import { DetailedHTMLProps, ReactNode, TextareaHTMLAttributes } from 'react'; +import { clsx } from 'clsx'; + +type TextAreaProps = { + className?: string; + children?: ReactNode; + variant?: 'default' | 'ghost'; + fullWidth?: boolean; +} & DetailedHTMLProps< + TextareaHTMLAttributes, + HTMLTextAreaElement +>; + +export default function TextArea({ + className, + children, + variant = 'default', + fullWidth = false, + ...props +}: TextAreaProps) { + return ( +
+ +
+ ); +} diff --git a/src/views/home/ui/contacts/contacts.module.scss b/src/views/home/ui/contacts/contacts.module.scss index cc1ef9b..27bf5c3 100644 --- a/src/views/home/ui/contacts/contacts.module.scss +++ b/src/views/home/ui/contacts/contacts.module.scss @@ -33,43 +33,71 @@ } .Form { - background: $color-white; - border-radius: 28px; + position: relative; + background: #292E3D; display: flex; flex-direction: row; padding: 60px 50px; + border-radius: 28px; + overflow: hidden; + margin-bottom: 80px; + &:after { + content: ''; + display: block; + width: 100%; + height: 100%; + position: absolute; + z-index: 1; + left: 0; + top: 0; + background-color: rgba(#163055, .6); + transition: 250ms background-color; + } + + .Background { + position: absolute; + object-fit: cover; + z-index: 1; + } + .Offer { flex: 2; display: flex; flex-direction: column; - .Title { + position: relative; + z-index: 2; font-family: $font-open-sans; font-weight: $font-semi-bold; font-size: 48px; line-height: 100%; - color: $color-text-light; + color: $color-white; margin-bottom: 80px; } .SubTitle { + position: relative; + z-index: 2; font-family: $font-open-sans; font-weight: $font-light; font-size: 32px; line-height: 100%; - color: $color-text-light; + color: $color-white; max-width: 720px; } } .Inputs { + position: relative; + z-index: 2; flex: 1; display: flex; flex-direction: column; - align-items: flex-start; + justify-content: center; + gap: 20px; } } @@ -80,7 +108,7 @@ border-radius: 28px; } - .Contacts{ + .Contacts { position: absolute; z-index: 2; top: -40px; @@ -88,15 +116,16 @@ border-radius: 28px; background: $color-white; width: 440px; - height: 540px; - padding: 40px 35px; + //height: 540px; + padding: 40px 35px 20px; display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; - .Title{ + .Title { + position: relative; font-family: $font-open-sans; font-weight: $font-semi-bold; font-size: 48px; @@ -105,14 +134,32 @@ text-align: right; margin-bottom: 32px; align-self: center; + + .LedOn { + position: absolute; + bottom: 6px; + left: 6px; + } } + .Address { + position: relative; font-family: $font-open-sans; font-weight: $font-regular; font-size: 32px; line-height: 130%; color: $color-text; - margin-bottom: 32px; + margin-bottom: 20px; + margin-left: 32px; + + .Icon { + position: absolute; + left: -48px; + top: 8px; + width: 32px; + height: 32px; + stroke: #E96526; + } } } } diff --git a/src/views/home/ui/contacts/contacts.tsx b/src/views/home/ui/contacts/contacts.tsx index da1c3d1..e628d5f 100644 --- a/src/views/home/ui/contacts/contacts.tsx +++ b/src/views/home/ui/contacts/contacts.tsx @@ -1,6 +1,5 @@ import s from './contacts.module.scss'; -import { Button, Mark } from '@shared/ui'; - +import { Button, Input, Mark } from '@shared/ui'; import Image from 'next/image'; import bogatyrLogo from '@public/images/logo-bogatyr.png'; @@ -8,6 +7,11 @@ import sochiparkLogo from '@public/images/logo-sochipark.png'; import chateauLogo from '@public/images/logo-chateau-de-talu.png'; import gazpromLogo from '@public/images/logo-gazprom.png'; import kraspolLogo from '@public/images/logo-kraspol.png'; +import bgForm from '@public/images/bg-form.jpg'; +import ledOn from '@public/svg/led-on.svg'; +import phoneCall from '@public/svg/phone-call.svg'; +import map from '@public/svg/map.svg'; +import email from '@public/svg/email.svg'; export default function Contacts() { return ( @@ -24,6 +28,15 @@ export default function Contacts() {
+ {''}

Остались вопросы?

@@ -32,9 +45,11 @@ export default function Contacts() {

- - - + + +
@@ -48,17 +63,25 @@ export default function Contacts() {

+ {''} Мы всегда
на связи

+ Краснодарский край
г.Сочи
ТЦ «Атриум»
ул. Навагинская д.9«Д»
этаж 3, офис 35

-

+7 (988) 400 93 93

-

office@firecheck.ru

+

+ + +7 (988) 400 93 93 +

+

+ + office@firecheck.ru +

diff --git a/src/views/home/ui/footer/footer.module.scss b/src/views/home/ui/footer/footer.module.scss index bc97d22..163bdaa 100644 --- a/src/views/home/ui/footer/footer.module.scss +++ b/src/views/home/ui/footer/footer.module.scss @@ -7,6 +7,18 @@ align-items: flex-start; gap: 20px; + .Block{ + display: flex; + flex-direction: column; + gap: 20px; + max-width: 40vw; + } + + .SendBtn { + align-self: flex-end; + max-width: 33%; + } + .Header { font-family: $font-open-sans; font-weight: $font-semi-bold; @@ -23,6 +35,7 @@ display: flex; flex-direction: row; align-items: center; + gap: 20px; .Policy { font-family: $font-open-sans; diff --git a/src/views/home/ui/footer/footer.tsx b/src/views/home/ui/footer/footer.tsx index b19bb9a..ef81551 100644 --- a/src/views/home/ui/footer/footer.tsx +++ b/src/views/home/ui/footer/footer.tsx @@ -1,26 +1,35 @@ import s from './footer.module.scss'; -import { Button, Mark } from '@shared/ui'; +import { Button, Input, Mark, TextArea } from '@shared/ui'; +import Link from 'next/link'; export default function Footer() { return (
-

- Давайте обсудим ваши задачи -

- - - - - +
+

+ Давайте обсудим ваши задачи +

+ + +