diff --git a/src/components/Button/index.js b/src/components/Button/index.js
index bfe08a34..8697b06c 100644
--- a/src/components/Button/index.js
+++ b/src/components/Button/index.js
@@ -1,13 +1,14 @@
import { Component } from 'pet-dex-utilities';
import './index.scss';
-const events = ['click', 'text:change', 'enable', 'disable'];
+const events = ['click', 'text:change', 'enable', 'disable', 'id:changed'];
const html = `
`;
export default function Button({
+ id = '',
text = '',
isFullWidth = false,
isDisabled = false,
@@ -17,6 +18,7 @@ export default function Button({
this.setText(text);
this.setIsFullWidth(isFullWidth);
this.setIsDisabled(isDisabled);
+ this.setID(id);
const $button = this.selected.get('button');
@@ -76,4 +78,13 @@ Button.prototype = Object.assign(Button.prototype, Component.prototype, {
if (this.isDisabled()) return;
this.emit('click');
},
+
+ getID() {
+ return this.selected.get('button').id;
+ },
+
+ setID(id = '') {
+ this.selected.get('button').id = id;
+ this.emit('id:changed', id);
+ },
});
diff --git a/src/components/Dropdown/index.js b/src/components/Dropdown/index.js
index 75856a83..8987ae64 100644
--- a/src/components/Dropdown/index.js
+++ b/src/components/Dropdown/index.js
@@ -14,6 +14,8 @@ const events = [
'unselect',
'value:change',
'value:clear',
+ 'id:changed',
+ 'error',
];
const html = `
@@ -33,12 +35,14 @@ const html = `
export default function Dropdown({
items = [],
placeholder = 'Select an option',
+ id = '',
} = {}) {
Component.call(this, { html, events });
this.placeholder = placeholder;
this.items = new Map();
this.selectedItem = null;
+ this.setID(id);
this.onSelect = (item) => {
const existsAndIsDifferent =
@@ -191,4 +195,13 @@ Dropdown.prototype = Object.assign(Dropdown.prototype, Component.prototype, {
placeholder: this.getPlaceholder(),
};
},
+
+ getID() {
+ return this.selected.get('dropdown-toggle').id;
+ },
+
+ setID(id = '') {
+ this.selected.get('dropdown-toggle').id = id;
+ this.emit('id:changed', id);
+ },
});
diff --git a/src/components/Dropdown/index.scss b/src/components/Dropdown/index.scss
index af3c4283..f5d1165b 100644
--- a/src/components/Dropdown/index.scss
+++ b/src/components/Dropdown/index.scss
@@ -2,9 +2,10 @@
@use '~styles/fonts.scss' as fonts;
.dropdown {
- width: fit-content;
+ width: 100%;
font-family: fonts.$primaryFont;
+
color: colors.$gray800;
font-size: 1.4rem;
font-weight: fonts.$semiBold;
@@ -41,7 +42,7 @@
list-style: none;
- background-color: rgb(255, 255, 255);
+ background-color: colors.$white;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2);
border-radius: 1.4rem;
@@ -50,20 +51,19 @@
}
&__toggle {
- width: 100%;
-
display: flex;
gap: 2rem;
justify-content: space-between;
- padding: 1rem;
+ padding: 1.8rem 1.6rem;
+
+ border: 1px solid colors.$gray200;
box-sizing: border-box;
- background-color: rgb(255, 255, 255);
+ background-color: colors.$white;
- box-shadow: 0 0 5px 0 rgb(216, 216, 216);
border-radius: 1.4rem;
.dropdown__icon {
diff --git a/src/components/RegisterForm/components/Field/index.js b/src/components/RegisterForm/components/Field/index.js
new file mode 100644
index 00000000..435eb84e
--- /dev/null
+++ b/src/components/RegisterForm/components/Field/index.js
@@ -0,0 +1,69 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = [
+ 'label:changed',
+ 'error:message',
+ 'content:changed',
+ 'error:visible',
+ 'error:resolved',
+];
+
+const html = `
+
+`;
+
+export default function Field({ label = '', error = '', content } = {}) {
+ Component.call(this, { html, events });
+ this.setLabel(label);
+ this.setError(error);
+ this.setContent(content);
+}
+
+Field.prototype = Object.assign(Field.prototype, Component.prototype, {
+ getLabel() {
+ return this.selected.get('field-label').innerText;
+ },
+
+ setLabel(label = '') {
+ this.selected.get('field-label').innerText = label;
+ this.emit('label:changed', label);
+ },
+
+ getError() {
+ return this.selected.get('field-error').innerText;
+ },
+
+ setError(error = '') {
+ this.selected.get('field-error').innerText = error;
+ this.emit('error:message', error);
+ },
+
+ showError(error) {
+ this.selected.get('field-error').classList.add('field__error--show-error');
+ this.emit('error:visible', error);
+ },
+
+ resolveError(error) {
+ this.selected
+ .get('field-error')
+ .classList.remove('field__error--show-error');
+ this.emit('error:resolved', error);
+ },
+
+ getContent() {
+ return this.content;
+ },
+
+ setContent(content) {
+ if (content?.mount == null) return;
+
+ this.content = content;
+ this.content.mount(this.selected.get('field-input'));
+ this.emit('content:changed', this.content);
+ },
+});
diff --git a/src/components/RegisterForm/components/Field/index.scss b/src/components/RegisterForm/components/Field/index.scss
new file mode 100644
index 00000000..0ac91a5a
--- /dev/null
+++ b/src/components/RegisterForm/components/Field/index.scss
@@ -0,0 +1,29 @@
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+
+.field {
+ &__label {
+ display: block;
+
+ color: colors.$gray800;
+
+ font-size: fonts.$xs;
+ font-weight: fonts.$medium;
+
+ padding: 0 0 0.5rem 0.5rem;
+ }
+
+ &__error {
+ display: none;
+
+ color: colors.$error100;
+
+ text-align: justify;
+
+ margin: 0.8rem 0 0 0.5rem;
+
+ &--show-error {
+ display: block;
+ }
+ }
+}
diff --git a/src/components/RegisterForm/images/facebook-icon.svg b/src/components/RegisterForm/images/facebook-icon.svg
new file mode 100644
index 00000000..6681697f
--- /dev/null
+++ b/src/components/RegisterForm/images/facebook-icon.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/components/RegisterForm/images/google-icon.svg b/src/components/RegisterForm/images/google-icon.svg
new file mode 100644
index 00000000..cf464af1
--- /dev/null
+++ b/src/components/RegisterForm/images/google-icon.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/src/components/RegisterForm/index.js b/src/components/RegisterForm/index.js
new file mode 100644
index 00000000..3cad6787
--- /dev/null
+++ b/src/components/RegisterForm/index.js
@@ -0,0 +1,286 @@
+import { Component } from 'pet-dex-utilities';
+import Field from './components/Field/index';
+import TextInput from '../TextInput';
+import Dropdown from '../Dropdown';
+import Button from '../Button/index';
+import {
+ isNameValid,
+ isBirthValid,
+ isLocalValid,
+ isEmailValid,
+ isPhoneValid,
+ isPasswordValid,
+} from '../../utils/validations';
+import googleIcon from './images/google-icon.svg';
+import facebookIcon from './images/facebook-icon.svg';
+import './index.scss';
+
+const events = ['register'];
+
+const html = `
+
+`;
+
+export default function RegisterForm() {
+ Component.call(this, { html, events });
+
+ const $formButton = this.selected.get('form-button');
+ const $fields = this.selected.get('fields');
+
+ const name = new Field({
+ label: 'Nome',
+ error: 'Informe seu nome',
+ content: new TextInput({
+ id: 'name',
+ placeholder: 'Devhat',
+ }),
+ });
+
+ const surname = new Field({
+ label: 'Sobrenome',
+ error: 'Informe seu sobrenome',
+ content: new TextInput({
+ id: 'surname',
+ placeholder: 'Devhat',
+ }),
+ });
+
+ const birth = new Field({
+ label: 'Data de nascimento',
+ error: 'Informe sua data de nascimento',
+ content: new TextInput({
+ id: 'birth',
+ placeholder: '13/12/1995',
+ }),
+ });
+
+ const local = new Field({
+ label: 'Cidade',
+ error: 'Informe sua cidade',
+ content: new Dropdown({
+ items: [
+ {
+ text: 'Fortaleza',
+ value: 'FOR',
+ },
+ {
+ text: 'São Paulo',
+ value: 'SP',
+ },
+ ],
+ id: 'local',
+ placeholder: 'São Paulo, SP',
+ }),
+ });
+
+ const email = new Field({
+ label: 'E-mail',
+ error: 'Informe um E-mail válido',
+ content: new TextInput({
+ id: 'email',
+ placeholder: 'dev@devhat.com.br',
+ type: 'email',
+ }),
+ });
+
+ const phone = new Field({
+ label: 'Telefone',
+ error: 'Informe um número de telefone válido',
+ content: new TextInput({
+ id: 'phone',
+ placeholder: '(11) 92875-3356',
+ }),
+ });
+
+ const password = new Field({
+ label: 'Senha',
+ error:
+ 'Senha inválida. Sua senha deve conter no mínimo 10 caracteres, incluindo pelo menos um caractere especial e uma letra maiúscula.',
+ content: new TextInput({
+ id: 'password',
+ placeholder: '*********',
+ type: 'password',
+ }),
+ });
+
+ const repeatPassword = new Field({
+ label: 'Confirmar senha',
+ error: 'Senha inválida.',
+ content: new TextInput({
+ id: 'repeat-password',
+ placeholder: '*********',
+ type: 'password',
+ }),
+ });
+
+ const registerButton = new Button({
+ id: 'register-button',
+ text: 'Cadastrar',
+ isFullWidth: true,
+ isDisabled: false,
+ });
+
+ name.mount($fields);
+ surname.mount($fields);
+ birth.mount($fields);
+ local.mount($fields);
+ email.mount($fields);
+ phone.mount($fields);
+ password.mount($fields);
+ repeatPassword.mount($fields);
+ registerButton.mount($formButton);
+
+ registerButton.listen('click', () => {
+ const nameValue = name.getContent().getValue();
+ const surnameValue = surname.getContent().getValue();
+ const birthValue = birth.getContent().getValue();
+ const localValue = local.getContent().getValue();
+ const emailValue = email.getContent().getValue();
+ const phoneValue = phone.getContent().getValue();
+ const passwordValue = password.getContent().getValue();
+ const repeatPasswordValue = repeatPassword.getContent().getValue();
+
+ let nameValid = true;
+ let surnameValid = true;
+ let birthValid = true;
+ let localValid = true;
+ let emailValid = true;
+ let phoneValid = true;
+ let passwordValid = true;
+ let repeatPasswordValid = true;
+
+ if (!isNameValid(nameValue)) {
+ nameValid = false;
+
+ name.showError();
+ name.getContent().inputError();
+ }
+
+ if (!isNameValid(surnameValue)) {
+ surnameValid = false;
+
+ surname.showError();
+ surname.getContent().inputError();
+ }
+
+ if (!isBirthValid(birthValue)) {
+ birthValid = false;
+
+ birth.showError();
+ birth.getContent().inputError();
+ }
+
+ if (!isLocalValid(localValue)) {
+ localValid = false;
+
+ local.showError();
+ }
+
+ if (!isEmailValid(emailValue)) {
+ emailValid = false;
+
+ email.showError();
+ email.getContent().inputError();
+ }
+
+ if (!isPhoneValid(phoneValue)) {
+ phoneValid = false;
+
+ phone.showError();
+ phone.getContent().inputError();
+ }
+
+ if (!isPasswordValid(passwordValue)) {
+ passwordValid = false;
+
+ password.showError();
+ password.getContent().inputError();
+ }
+
+ if (
+ !isPasswordValid(repeatPasswordValue) ||
+ repeatPasswordValue !== passwordValue
+ ) {
+ repeatPasswordValid = false;
+
+ repeatPassword.showError();
+ repeatPassword.getContent().inputError();
+ }
+
+ if (nameValid) {
+ name.resolveError();
+ }
+
+ if (surnameValid) {
+ surname.resolveError();
+ }
+
+ if (birthValid) {
+ birth.resolveError();
+ }
+
+ if (localValid) {
+ local.resolveError();
+ }
+
+ if (emailValid) {
+ email.resolveError();
+ }
+
+ if (phoneValid) {
+ phone.resolveError();
+ }
+
+ if (passwordValid) {
+ password.resolveError();
+ }
+
+ if (repeatPasswordValid) {
+ repeatPassword.resolveError();
+ }
+
+ if (
+ nameValid &&
+ surnameValid &&
+ birthValid &&
+ localValid &&
+ emailValid &&
+ phoneValid &&
+ passwordValid &&
+ repeatPasswordValid
+ ) {
+ this.register();
+ }
+ });
+}
+
+RegisterForm.prototype = Object.assign(
+ RegisterForm.prototype,
+ Component.prototype,
+ {
+ register() {
+ this.emit('register');
+ },
+ },
+);
diff --git a/src/components/RegisterForm/index.scss b/src/components/RegisterForm/index.scss
new file mode 100644
index 00000000..9278920c
--- /dev/null
+++ b/src/components/RegisterForm/index.scss
@@ -0,0 +1,143 @@
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.register-form {
+ display: flex;
+ flex-direction: column;
+
+ align-items: start;
+
+ justify-content: center;
+
+ font-family: fonts.$primaryFont;
+
+ margin: 0 auto;
+
+ &__title {
+ color: colors.$gray800;
+ font-size: fonts.$lg;
+ font-weight: fonts.$bold;
+
+ margin-bottom: 3.2rem;
+ }
+
+ &__socials,
+ &__divisor {
+ max-width: 45rem;
+ }
+
+ &__socials {
+ width: 100%;
+
+ display: flex;
+ flex-direction: row;
+ gap: 2rem;
+ }
+
+ &__social {
+ max-width: 21.7rem;
+
+ display: flex;
+ flex-direction: row;
+
+ flex-grow: 1;
+
+ gap: 1rem;
+
+ align-items: center;
+
+ justify-content: center;
+
+ font-family: fonts.$fifthFont;
+ font-size: fonts.$sm;
+ font-weight: fonts.$medium;
+
+ margin-bottom: 3.2rem;
+
+ padding: 1.8rem 0;
+
+ border: 0;
+
+ background-color: colors.$secondary100;
+
+ box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 8%);
+
+ border-radius: 10px;
+
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.02);
+
+ transition: all 0.4s ease-in-out;
+ }
+ }
+
+ &__social-img {
+ width: 2.4rem;
+ height: 2.4rem;
+ }
+
+ &__divisor {
+ width: 100%;
+
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ justify-content: center;
+
+ margin-bottom: 3rem;
+ }
+
+ &__divisor-line {
+ width: 100%;
+ height: 1px;
+
+ border: 0;
+
+ background-color: colors.$gray200;
+ }
+
+ &__divisor-text {
+ font-size: fonts.$sm;
+
+ margin: 0 2.9rem;
+ }
+
+ &__form {
+ width: 100%;
+ }
+
+ &__form-fields {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 3rem;
+
+ margin-bottom: 3rem;
+ }
+
+ &__form-button {
+ max-width: 45rem;
+ }
+}
+
+@include breakpoints.from667 {
+ .register-form {
+ max-width: 72.5rem;
+
+ &__title {
+ font-size: fonts.$xl2;
+ }
+
+ &__form-fields {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 3rem;
+
+ margin-bottom: 6rem;
+ }
+ }
+}
diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js
index 8bc6057c..1af3798f 100644
--- a/src/components/TextInput/index.js
+++ b/src/components/TextInput/index.js
@@ -11,6 +11,7 @@ const events = [
'enabled',
'value:change',
'type:change',
+ 'id:changed',
];
const html = `
@@ -23,6 +24,7 @@ const html = `
`;
export default function TextInput({
+ id = '',
placeholder = '',
assetUrl,
assetPosition,
@@ -42,6 +44,7 @@ export default function TextInput({
input.classList.add(assetPosition);
this.setValue(value);
this.setType(type);
+ this.setID(id);
if (type === 'password') {
iconBtn.classList.remove('input-text-container__button--hidden');
@@ -110,4 +113,13 @@ TextInput.prototype = Object.assign(TextInput.prototype, Component.prototype, {
this.selected.get('input-text').type = type;
this.emit('type:change', type);
},
+
+ getID() {
+ return this.selected.get('input-text').id;
+ },
+
+ setID(id = '') {
+ this.selected.get('input-text').id = id;
+ this.emit('id:changed', id);
+ },
});
diff --git a/src/components/TextInput/index.scss b/src/components/TextInput/index.scss
index 8c2c034d..e3fad0ba 100644
--- a/src/components/TextInput/index.scss
+++ b/src/components/TextInput/index.scss
@@ -6,6 +6,8 @@
border: 1px solid colors.$gray200;
+ background-color: colors.$white;
+
border-radius: 14px;
transition: border-color 0.3s ease-in-out;
@@ -17,7 +19,6 @@
&:has(> &__input.standard.input-error) {
border-color: colors.$error100;
- background-color: colors.$error100;
outline-color: colors.$error100;
}
@@ -50,6 +51,7 @@
box-sizing: border-box;
+ background-color: transparent;
background-repeat: no-repeat;
background-size: auto 60%;
filter: opacity(0.85);
@@ -83,8 +85,7 @@
}
&.input-error {
- background-color: colors.$error100;
- filter: opacity(0.75);
+ color: colors.$secondary100;
}
}
diff --git a/src/layouts/index.html b/src/layouts/index.html
index f7dd1311..9c2cfa1d 100644
--- a/src/layouts/index.html
+++ b/src/layouts/index.html
@@ -7,7 +7,7 @@
Pet Hat
diff --git a/src/stories/RegisterForm.stories.js b/src/stories/RegisterForm.stories.js
new file mode 100644
index 00000000..a7e3f03d
--- /dev/null
+++ b/src/stories/RegisterForm.stories.js
@@ -0,0 +1,15 @@
+import RegisterForm from '../components/RegisterForm';
+
+export default {
+ title: 'Components/RegisterForm',
+ render: () => {
+ const registerForm = new RegisterForm();
+ const $container = document.createElement('div');
+ registerForm.mount($container);
+
+ return $container;
+ },
+ argTypes: {},
+};
+
+export const Default = {};
diff --git a/src/styles/colors.scss b/src/styles/colors.scss
index 0dbdbfc8..a2bddb97 100644
--- a/src/styles/colors.scss
+++ b/src/styles/colors.scss
@@ -21,6 +21,10 @@ $success200: rgb(49, 138, 94);
// error
$error100: rgb(179, 38, 30);
+// White
+
+$white: rgb(255, 255, 255);
+
// neutrals (Gray)
$gray100: rgb(236, 239, 242);
$gray150: rgb(236, 239, 242);
diff --git a/src/utils/validations.js b/src/utils/validations.js
new file mode 100644
index 00000000..36a529b1
--- /dev/null
+++ b/src/utils/validations.js
@@ -0,0 +1,42 @@
+export function isNameValid(name) {
+ const nameRegex = /[a-zA-Z]$/;
+
+ return nameRegex.test(name);
+}
+
+export function isBirthValid(birth) {
+ const birthRegex = /(\d{2})\/?(\d{2})\/?(\d{4})$/;
+
+ return birthRegex.test(birth);
+}
+
+export function isEmailValid(email) {
+ const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-z]{2,}/;
+
+ return emailRegex.test(email);
+}
+
+export function isPhoneValid(phone) {
+ const phoneRegex = /(\d{2})\d{5}\d{4}/;
+
+ return phoneRegex.test(phone);
+}
+
+export function isPasswordValid(password) {
+ const hasMinLength = password.length >= 10;
+ const hasUppercase = /[A-Z]/g.test(password);
+ const hasNumber = /[0-9]/g.test(password);
+ const hasSpecialCharacter = /[!@#$%^&*{}<>;'(),.?":|]/g.test(password);
+
+ return hasMinLength && hasUppercase && hasNumber && hasSpecialCharacter;
+}
+
+export function isLocalValid(local) {
+ let isFilled = true;
+
+ if (local === '' || local === undefined) {
+ isFilled = false;
+ }
+
+ return isFilled;
+}