Skip to content

Commit

Permalink
Ajout d'un textarea avec auto resize
Browse files Browse the repository at this point in the history
  • Loading branch information
mariheck committed Jul 11, 2024
1 parent a509f6b commit 474844b
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 17 deletions.
29 changes: 29 additions & 0 deletions packages/ui/src/design-system/Textarea/AutoResizedTextarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {Ref, RefObject, forwardRef, useEffect, useState} from 'react';
import {useAutoResize} from './useAutoResize';
import {Textarea, TextareaProps} from './Textarea';

export const AutoResizedTextarea = forwardRef(
(props: TextareaProps, ref?: Ref<HTMLTextAreaElement>) => {
// Valeur locale, nécessaire pour le refresh des valeurs textareaRef et shadowRef
const [value, setValue] = useState<string | undefined>(props.value);

useEffect(() => setValue(value), [props.value]);

const {textareaRef, shadowRef} = useAutoResize(
value?.toString(),
ref as RefObject<HTMLTextAreaElement>
);

const handleChange = evt => {
props.onChange?.(evt.target.value);
setValue(evt.target.value);
};

return (
<div className="flex items-start w-full">
<div ref={shadowRef} className="w-px -mr-px" />
<Textarea ref={textareaRef} onChange={handleChange} resize="none" />
</div>
);
}
);
15 changes: 9 additions & 6 deletions packages/ui/src/design-system/Textarea/Textarea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Meta, StoryObj} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {Textarea} from './Textarea';
import {useRef, useState} from 'react';
import {Input} from '@design-system/Input';
import {AutoResizedTextarea} from './AutoResizedTextarea';

const meta: Meta<typeof Textarea> = {
component: Textarea,
Expand All @@ -22,12 +22,10 @@ const meta: Meta<typeof Textarea> = {
{...args}
ref={ref}
value={value}
onChange={e => {
console.log(e);

action('onChange')(e.target);
onChange={(evt: any) => {
action('onChange')(evt.target);
action('ref.current.value')(ref.current.value);
// setValue(e);
setValue(evt.target.value);
}}
/>
);
Expand Down Expand Up @@ -75,3 +73,8 @@ export const SmallVariant: Story = {
displaySize: 'sm',
},
};

/** Avec redimensionnement automatique du champ lors de la saisie */
export const WithAutoResize: Story = {
render: () => <AutoResizedTextarea />,
};
4 changes: 1 addition & 3 deletions packages/ui/src/design-system/Textarea/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export type TextareaProps = HTMLAttributes<HTMLTextAreaElement> & {
export const Textarea = forwardRef(
(
{
value,
displaySize = 'md',
state = 'default',
resize = 'vertical',
Expand All @@ -51,7 +50,7 @@ export const Textarea = forwardRef(
return (
<div
className={classNames(
'inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5',
'flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5',
borderColor,
containerClassname
)}
Expand All @@ -60,7 +59,6 @@ export const Textarea = forwardRef(
{...props}
ref={ref}
disabled={disabled}
value={value}
className={classNames(
'grow text-grey-8 px-4 outline-none ',
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`design-system/Textarea Default smoke-test 1`] = `
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<textarea class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y">
</textarea>
</div>
`;

exports[`design-system/Textarea SmallVariant smoke-test 1`] = `
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<textarea placeholder="Small"
class="grow text-grey-8 px-4 outline-none text-sm py-2 resize-y"
>
</textarea>
</div>
`;

exports[`design-system/Textarea WithAutoResize smoke-test 1`] = `
<div class="flex items-start w-full">
<div class="w-px -mr-px"
style="height: 48px;"
>
</div>
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<textarea class="grow text-grey-8 px-4 outline-none text-md py-3 resize-none"
style="height: 48px;"
>
</textarea>
</div>
</div>
`;

exports[`design-system/Textarea WithColorBorders smoke-test 1`] = `
<div class="flex flex-col gap-4">
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-info-1">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-info-1">
<textarea placeholder="Info"
class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y"
>
</textarea>
</div>
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-error-1">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-error-1">
<textarea placeholder="Error"
class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y"
>
</textarea>
</div>
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-success-1">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-success-1">
<textarea placeholder="Success"
class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y"
>
</textarea>
</div>
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-warning-1">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-warning-1">
<textarea placeholder="Warning"
class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y"
>
Expand All @@ -46,7 +61,7 @@ exports[`design-system/Textarea WithColorBorders smoke-test 1`] = `
`;

exports[`design-system/Textarea WithPlaceholder smoke-test 1`] = `
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<textarea placeholder="Saisir une valeur"
class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y"
>
Expand All @@ -55,7 +70,7 @@ exports[`design-system/Textarea WithPlaceholder smoke-test 1`] = `
`;

exports[`design-system/Textarea WithValue smoke-test 1`] = `
<div class="inline-flex items-stretch border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<div class="flex items-stretch w-full border border-solid rounded-lg bg-grey-1 overflow-hidden focus-within:border-primary-5 border-grey-4">
<textarea class="grow text-grey-8 px-4 outline-none text-md py-3 resize-y">
Test
</textarea>
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/design-system/Textarea/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './Textarea';
export * from './AutoResizedTextarea';
43 changes: 43 additions & 0 deletions packages/ui/src/design-system/Textarea/useAutoResize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {RefObject, useLayoutEffect, useRef} from 'react';

/**
* Rend un champ de texte avec redimensionnement automatique en fonction de son contenu
* Copié / adapté depuis:
* https://www.kindacode.com/article/react-typescript-create-an-autosize-textarea-from-scratch/
* https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/#comment-1794830
*/

export const useAutoResize = (
value?: string,
forwardRef?: RefObject<HTMLTextAreaElement> | null
) => {
// Référence locale associée à la balise <textarea/>
const ref = useRef<HTMLTextAreaElement | null>(null);

// Référence associée à la balise invisible <div/>
// Cette div permet de conserver la hauteur pour éviter
// les sauts dans la page à la mise à jour du texte
const shadowRef = useRef<HTMLDivElement | null>(null);

// Référence associée à la balise <textarea>
// (locale ou issue des props)
const textareaRef = forwardRef ? forwardRef : ref;

// Permet de set la taille du textarea au changement de valeur
useLayoutEffect(() => {
if (textareaRef && textareaRef.current && shadowRef && shadowRef.current) {
// Initialise la hauteur du textarea à 0px
textareaRef.current.style.height = '0px';

// Met à jour la hauteur en fonction du contenu
textareaRef.current.style.height =
textareaRef.current.scrollHeight + 'px';

// Une div invisible qui permet de conserver la hauteur pour éviter
// les sauts dans la page à la mise à jour du texte
shadowRef.current.style.height = textareaRef.current.scrollHeight + 'px';
}
}, [value]);

return {textareaRef, shadowRef};
};

0 comments on commit 474844b

Please sign in to comment.