New inputs

This commit is contained in:
Maxime Lalo 2024-07-19 16:32:40 +02:00
parent 3129e1cbeb
commit b1a17d537e
12 changed files with 227 additions and 495 deletions

View File

@ -108,7 +108,7 @@ export default abstract class BaseField<P extends IProps, S extends IState = ISt
let errors: JSX.Element[] = []; let errors: JSX.Element[] = [];
Object.entries(this.state.validationError.constraints).forEach(([key, value]) => { Object.entries(this.state.validationError.constraints).forEach(([key, value]) => {
errors.push( errors.push(
<Typography key={key} typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_ERROR_600}> <Typography key={key} typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.INPUT_ERROR}>
{value} {value}
</Typography>, </Typography>,
); );

View File

@ -1,94 +0,0 @@
@import "@Themes/constants.scss";
.root {
position: relative;
&[data-is-disabled="true"] {
opacity: var(--opacity-disabled, 0.3);
.input-container {
cursor: not-allowed;
border: 1px solid var(--input-main-border-default, #d7dce0);
&::placeholder {
background: var(--input-background, #fff);
}
&:hover {
border: 1px solid var(--input-main-border-default, #d7dce0);
}
.input {
cursor: not-allowed;
}
}
}
.label {
padding: 0px var(--spacing-2, 16px);
}
.input-container {
display: flex;
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
align-items: center;
gap: var(--spacing-2, 16px);
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-default, #d7dce0);
background: var(--input-background, #fff);
&:hover {
border: 1px solid var(--input-main-border-hovered, #b4bec5);
}
&:not([data-value=""]) {
border: 1px solid var(--input-main-border-filled, #6d7e8a);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
&[data-is-errored="true"] {
border: 1px solid var(--input-error, #dc2625);
}
&:focus,
&:focus-within,
&:focus-visible {
border: 1px solid var(--input-main-border-focused, #005bcb);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
.input {
display: flex;
padding: 0px var(--spacing-2, 16px);
align-items: center;
gap: 8px;
flex: 1 0 0;
border: none;
height: 94px;
&::placeholder {
color: var(--input-placeholder-empty, #6d7e8a);
/* text/md/regular */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-regular, 400);
line-height: normal;
letter-spacing: 0.08px;
}
color: var(--input-placeholder-filled, #24282e);
/* text/md/semibold */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-semibold, 600);
line-height: normal;
letter-spacing: 0.08px;
}
}
}

View File

@ -1,46 +0,0 @@
import React from "react";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { ReactNode } from "react";
import BaseField, { IProps as IBaseFieldProps } from "../../BaseField";
import classes from "./classes.module.scss";
import classnames from "classnames";
export type IProps = IBaseFieldProps & {};
export default class NewTextAreaField extends BaseField<IProps> {
constructor(props: IProps) {
super(props);
this.state = this.getDefaultState();
}
public override render(): ReactNode {
const value = this.state.value ?? "";
console.log(this.hasError());
return (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
<div className={classes["root"]} data-is-disabled={this.props.disabled}>
<label>
{this.props.label && (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.INPUT_LABEL} className={classes["label"]}>
{this.props.label}
</Typography>
)}
<div className={classes["input-container"]} data-value={value} data-is-errored={this.hasError().toString()}>
<textarea
onChange={this.onChange}
className={classnames(classes["input"], this.props.className)}
placeholder={this.props.placeholder}
value={value}
onFocus={this.onFocus}
onBlur={this.onBlur}
name={this.props.name}
disabled={this.props.disabled}
/>
</div>
</label>
</div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography>
);
}
}

View File

@ -1,112 +0,0 @@
@import "@Themes/constants.scss";
.root {
position: relative;
&[data-is-disabled="true"] {
opacity: var(--opacity-disabled, 0.3);
.input-container {
cursor: not-allowed;
border: 1px solid var(--input-main-border-default, #d7dce0);
&::placeholder {
background: var(--input-background, #fff);
}
&:hover {
border: 1px solid var(--input-main-border-default, #d7dce0);
}
.input {
cursor: not-allowed;
}
}
}
.label {
padding: 0px var(--spacing-2, 16px);
}
.input-container {
display: flex;
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
align-items: center;
gap: var(--spacing-2, 16px);
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-default, #d7dce0);
background: var(--input-background, #fff);
&:hover {
border: 1px solid var(--input-main-border-hovered, #b4bec5);
}
&:not([data-value=""]) {
border: 1px solid var(--input-main-border-filled, #6d7e8a);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
&[data-is-errored="true"] {
border: 1px solid var(--input-error, #dc2625);
}
&:focus,
&:focus-within,
&:focus-visible {
border: 1px solid var(--input-main-border-focused, #005bcb);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
.input {
display: flex;
padding: 0px var(--spacing-2, 16px);
align-items: center;
gap: 8px;
flex: 1 0 0;
border: none;
&::placeholder {
color: var(--input-placeholder-empty, #6d7e8a);
/* text/md/regular */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-regular, 400);
line-height: normal;
letter-spacing: 0.08px;
}
&[type="number"] {
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For Chrome, Safari, and Opera */
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For IE 10+ */
&::-ms-inner-spin-button,
&::-ms-outer-spin-button {
display: none;
}
}
}
}
.copy-icon {
cursor: pointer;
height: 24px;
width: 24px;
}
}

View File

@ -1,63 +0,0 @@
import React from "react";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { ReactNode } from "react";
import CopyIcon from "@Assets/Icons/copy.svg";
import BaseField, { IProps as IBaseFieldProps } from "../../BaseField";
import classes from "./classes.module.scss";
import classnames from "classnames";
import Image from "next/image";
export type IProps = IBaseFieldProps & {
canCopy?: boolean;
password?: boolean;
};
export default class NewTextField extends BaseField<IProps> {
constructor(props: IProps) {
super(props);
this.state = this.getDefaultState();
}
public override render(): ReactNode {
const value = this.state.value ?? "";
console.log(this.hasError());
return (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
<div className={classes["root"]} data-is-disabled={this.props.disabled}>
<label>
{this.props.label && (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.INPUT_LABEL} className={classes["label"]}>
{this.props.label}
</Typography>
)}
<div className={classes["input-container"]} data-value={value} data-is-errored={this.hasError().toString()}>
<input
onChange={this.onChange}
className={classnames(classes["input"], this.props.className)}
placeholder={this.props.placeholder}
value={value}
onFocus={this.onFocus}
onBlur={this.onBlur}
name={this.props.name}
disabled={this.props.disabled}
type={this.props.password ? "password" : "text"}
/>
{this.props.canCopy && (
<div className={classes["copy-icon"]} onClick={this.onCopyClick}>
<Image src={CopyIcon} alt="Copy icon" />
</div>
)}
</div>
</label>
</div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography>
);
}
private onCopyClick = (): void => {
if (this.props.canCopy) {
navigator.clipboard.writeText(this.state.value ?? "");
}
};
}

View File

@ -3,89 +3,103 @@
.root { .root {
position: relative; position: relative;
.input { &[data-is-disabled="true"] {
z-index: 1; opacity: var(--opacity-disabled, 0.3);
display: flex; .input-container {
flex-direction: row;
align-items: center;
padding: 24px;
gap: 10px;
width: 100%;
height: 70px;
border: 1px solid var(--color-neutral-200);
&:disabled {
cursor: not-allowed; cursor: not-allowed;
} border: 1px solid var(--input-main-border-default, #d7dce0);
&::placeholder {
&:focus { background: var(--input-background, #fff);
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&:not([data-value=""]) {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
&[type="number"] {
&:focus {
~ .fake-placeholder {
transform: translateY(-35px);
}
} }
&:not([data-value=""]) { &:hover {
~ .fake-placeholder { border: 1px solid var(--input-main-border-default, #d7dce0);
transform: translateY(-35px);
}
} }
&::-webkit-inner-spin-button, .input {
&::-webkit-outer-spin-button { cursor: not-allowed;
-webkit-appearance: none;
margin: 0;
} }
/* For Chrome, Safari, and Opera */
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For IE 10+ */
&::-ms-inner-spin-button,
&::-ms-outer-spin-button {
display: none;
}
}
&:not([data-value=""]) {
~ .fake-placeholder {
transform: translateY(-35px);
}
}
~ .fake-placeholder {
z-index: 2;
top: 35%;
margin-left: 8px;
padding: 0 16px;
pointer-events: none;
position: absolute;
background: var(--color-generic-white);
transition: transform 0.3s ease-in-out;
} }
} }
&[data-is-errored="true"] { .label {
padding: 0px var(--spacing-2, 16px);
}
.input-container {
display: flex;
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
align-items: center;
gap: var(--spacing-2, 16px);
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-default, #d7dce0);
background: var(--input-background, #fff);
&:hover {
border: 1px solid var(--input-main-border-hovered, #b4bec5);
}
&:not([data-value=""]) {
border: 1px solid var(--input-main-border-filled, #6d7e8a);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
&[data-is-errored="true"] {
border: 1px solid var(--input-error, #dc2625);
}
&:focus,
&:focus-within,
&:focus-visible {
border: 1px solid var(--input-main-border-focused, #005bcb);
.input {
font-weight: var(--font-text-weight-semibold, 600);
}
}
.input { .input {
border: 1px solid var(--color-error-600); display: flex;
~ .fake-placeholder { padding: 0px var(--spacing-2, 16px);
color: var(--color-error-600);
align-items: center;
gap: 8px;
flex: 1 0 0;
border: none;
&::placeholder {
color: var(--input-placeholder-empty, #6d7e8a);
/* text/md/regular */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-regular, 400);
line-height: normal;
letter-spacing: 0.08px;
}
&[type="number"] {
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For Chrome, Safari, and Opera */
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* For IE 10+ */
&::-ms-inner-spin-button,
&::-ms-outer-spin-button {
display: none;
}
} }
} }
} }
@ -94,9 +108,9 @@
cursor: pointer; cursor: pointer;
height: 24px; height: 24px;
width: 24px; width: 24px;
position: absolute; }
top: 50%;
right: 24px; .errors-container {
transform: translate(0, -50%); margin-top: 8px;
} }
} }

View File

@ -21,31 +21,34 @@ export default class TextField extends BaseField<IProps> {
public override render(): ReactNode { public override render(): ReactNode {
const value = this.state.value ?? ""; const value = this.state.value ?? "";
return ( return (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}> <div className={classes["root"]} data-is-disabled={this.props.disabled}>
<div className={classes["root"]} data-is-errored={this.hasError().toString()}> <label>
<input {this.props.label && (
onChange={this.onChange} <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.INPUT_LABEL} className={classes["label"]}>
data-value={value} {this.props.label}
data-has-validation-errors={(this.state.validationError === null).toString()} </Typography>
className={classnames(classes["input"], this.props.className)}
value={value}
onFocus={this.onFocus}
onBlur={this.onBlur}
name={this.props.name}
disabled={this.props.disabled}
type={this.props.password ? "password" : "text"}
/>
<div className={classes["fake-placeholder"]}>
{this.props.placeholder} {!this.props.required && " (Facultatif)"}
</div>
{this.props.canCopy && (
<div className={classes["copy-icon"]} onClick={this.onCopyClick}>
<Image src={CopyIcon} alt="Copy icon" />
</div>
)} )}
</div> <div className={classes["input-container"]} data-value={value} data-is-errored={this.hasError().toString()}>
<input
onChange={this.onChange}
className={classnames(classes["input"], this.props.className)}
placeholder={this.props.placeholder}
value={value}
onFocus={this.onFocus}
onBlur={this.onBlur}
name={this.props.name}
disabled={this.props.disabled}
type={this.props.password ? "password" : "text"}
/>
{this.props.canCopy && (
<div className={classes["copy-icon"]} onClick={this.onCopyClick}>
<Image src={CopyIcon} alt="Copy icon" />
</div>
)}
</div>
</label>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>} {this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography> </div>
); );
} }

View File

@ -3,61 +3,92 @@
.root { .root {
position: relative; position: relative;
.textarea { &[data-is-disabled="true"] {
resize: none; opacity: var(--opacity-disabled, 0.3);
height: auto; .input-container {
box-sizing: border-box;
font-family: "Inter", sans-serif;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
&:read-only {
cursor: not-allowed; cursor: not-allowed;
} border: 1px solid var(--input-main-border-default, #d7dce0);
z-index: 1; &::placeholder {
display: flex; background: var(--input-background, #fff);
flex-direction: row;
align-items: center;
padding: 24px;
gap: 10px;
width: 100%;
height: 100px;
border: 1px solid var(--color-neutral-200);
~ .fake-placeholder {
z-index: 2;
top: -12px;
margin-left: 8px;
padding: 0 16px;
pointer-events: none;
position: absolute;
background: var(--color-generic-white);
transform: translateY(35px);
transition: transform 0.3s ease-in-out;
}
&:focus {
~ .fake-placeholder {
transform: translateY(0px);
} }
&:hover {
border: 1px solid var(--input-main-border-default, #d7dce0);
}
.input {
cursor: not-allowed;
}
}
}
.label {
padding: 0px var(--spacing-2, 16px);
}
.input-container {
display: flex;
padding: var(--spacing-2, 16px) var(--spacing-sm, 8px);
align-items: center;
gap: var(--spacing-2, 16px);
border-radius: var(--input-radius, 0px);
border: 1px solid var(--input-main-border-default, #d7dce0);
background: var(--input-background, #fff);
&:hover {
border: 1px solid var(--input-main-border-hovered, #b4bec5);
} }
&:not([data-value=""]) { &:not([data-value=""]) {
~ .fake-placeholder { border: 1px solid var(--input-main-border-filled, #6d7e8a);
transform: translateY(0px); .input {
font-weight: var(--font-text-weight-semibold, 600);
} }
} }
}
&[data-is-errored="true"] { &[data-is-errored="true"] {
.textarea { border: 1px solid var(--input-error, #dc2625);
border: 1px solid var(--color-error-600); }
~ .fake-placeholder {
color: var(--color-error-600); &:focus,
&:focus-within,
&:focus-visible {
border: 1px solid var(--input-main-border-focused, #005bcb);
.input {
font-weight: var(--font-text-weight-semibold, 600);
} }
} }
.input {
display: flex;
padding: 0px var(--spacing-2, 16px);
align-items: center;
gap: 8px;
flex: 1 0 0;
border: none;
height: 94px;
&::placeholder {
color: var(--input-placeholder-empty, #6d7e8a);
/* text/md/regular */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-regular, 400);
line-height: normal;
letter-spacing: 0.08px;
}
color: var(--input-placeholder-filled, #24282e);
/* text/md/semibold */
font-family: var(--font-text-family, Poppins);
font-size: 16px;
font-style: normal;
font-weight: var(--font-text-weight-semibold, 600);
line-height: normal;
letter-spacing: 0.08px;
}
} }
} }

View File

@ -1,8 +1,7 @@
import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import React from "react"; import React from "react";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { ReactNode } from "react"; import { ReactNode } from "react";
import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import classnames from "classnames"; import classnames from "classnames";
@ -18,30 +17,29 @@ export default class TextAreaField extends BaseField<IProps> {
const value = this.state.value ?? ""; const value = this.state.value ?? "";
return ( return (
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}> <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
<div className={classes["root"]} data-is-errored={this.hasError().toString()}> <div className={classes["root"]} data-is-disabled={this.props.disabled}>
<textarea <label>
name={this.props.name} {this.props.label && (
rows={4} <Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.INPUT_LABEL} className={classes["label"]}>
data-value={value} {this.props.label}
onChange={this.onChange} </Typography>
className={classnames(classes["textarea"], this.props.className)} )}
value={value} <div className={classes["input-container"]} data-value={value} data-is-errored={this.hasError().toString()}>
readOnly={this.props.readonly} <textarea
onFocus={this.onFocus} onChange={this.onChange}
onBlur={this.onBlur} className={classnames(classes["input"], this.props.className)}
/> placeholder={this.props.placeholder}
<div className={classes["fake-placeholder"]}> value={value}
{this.props.placeholder} {!this.props.required && " (Facultatif)"} onFocus={this.onFocus}
</div> onBlur={this.onBlur}
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>} name={this.props.name}
disabled={this.props.disabled}
/>
</div>
</label>
</div> </div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography> </Typography>
); );
} }
public override componentDidMount() {
this.setState({
value: this.props.defaultValue ?? "",
});
}
} }

View File

@ -146,6 +146,7 @@ export enum ETypoColor {
TABS_CONTRAST_ACTIVATED = "--tabs-contrast-actived", TABS_CONTRAST_ACTIVATED = "--tabs-contrast-actived",
INPUT_LABEL = "--input-label", INPUT_LABEL = "--input-label",
INPUT_ERROR = "--input-error",
} }
export default function Typography(props: IProps) { export default function Typography(props: IProps) {

View File

@ -13,9 +13,9 @@ import classes from "./classes.module.scss";
import useOpenable from "@Front/Hooks/useOpenable"; import useOpenable from "@Front/Hooks/useOpenable";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
import NewTextField from "@Front/Components/DesignSystem/Form/NewFields/NewTextField";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import NewTextAreaField from "@Front/Components/DesignSystem/Form/NewFields/NewTextAreaField"; import TextField from "@Front/Components/DesignSystem/Form/TextField";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
export default function DesignSystem() { export default function DesignSystem() {
const { isOpen, open, close } = useOpenable(); const { isOpen, open, close } = useOpenable();
@ -69,11 +69,11 @@ export default function DesignSystem() {
<div className={classes["components"]}> <div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Inputs</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Inputs</Typography>
<Form className={classes["inputs"]}> <Form className={classes["inputs"]}>
<NewTextField label="Label" placeholder="Placeholder" canCopy /> <TextField label="Label" placeholder="Placeholder" canCopy />
<NewTextField label="Without copy" placeholder="Placeholder" /> <TextField label="Without copy" placeholder="Placeholder" />
<NewTextField label="Disabled" placeholder="Placeholder" disabled canCopy /> <TextField label="Disabled" placeholder="Placeholder" disabled canCopy />
<NewTextField label="Disabled without copy" placeholder="Placeholder" disabled /> <TextField label="Disabled without copy" placeholder="Placeholder" disabled />
<NewTextAreaField label="Textarea" placeholder="Placeholder" /> <TextAreaField label="Textarea" placeholder="Placeholder" />
</Form> </Form>
<Typography typo={ETypo.TEXT_LG_BOLD}>Modal</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Modal</Typography>

View File

@ -5,8 +5,8 @@ import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import Button from "@Front/Components/DesignSystem/Button"; import Button from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import TextField from "@Front/Components/DesignSystem/Form/TextField";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import MultiSelect from "@Front/Components/DesignSystem/MultiSelect"; import MultiSelect from "@Front/Components/DesignSystem/MultiSelect";
import RadioBox from "@Front/Components/DesignSystem/RadioBox"; import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";