From d1c41a2fa785b3c46cd7b1b3a2a9c7b714577ab4 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Tue, 2 Apr 2024 15:26:41 +0800 Subject: [PATCH] refactor(console): improve invitation email input field (#5615) --- .../InviteEmailsInput/index.module.scss | 16 +- .../TenantMembers/InviteEmailsInput/index.tsx | 148 ++++++++++-------- 2 files changed, 93 insertions(+), 71 deletions(-) diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.module.scss b/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.module.scss index 5ed7b320c4a..d614fad961f 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.module.scss +++ b/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.module.scss @@ -4,8 +4,8 @@ display: flex; align-items: flex-start; justify-content: space-between; - min-height: 96px; - padding: 0 _.unit(2) 0 _.unit(3); + min-height: 102px; + padding: _.unit(1.5) _.unit(3); background: var(--color-layer-1); border: 1px solid var(--color-border); border-radius: 8px; @@ -17,11 +17,12 @@ cursor: pointer; position: relative; - &.multiple { + .wrapper { + display: flex; + align-items: center; justify-content: flex-start; flex-wrap: wrap; gap: _.unit(2); - padding: _.unit(1.5) _.unit(3); cursor: text; .tag { @@ -58,8 +59,7 @@ color: var(--color-text); font: var(--font-body-2); background: transparent; - flex-grow: 1; - padding: _.unit(0.5); + flex: 1; &::placeholder { color: var(--color-placeholder); @@ -81,6 +81,10 @@ } } +canvas { + display: none; +} + .errorMessage { font: var(--font-body-2); color: var(--color-error); diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.tsx index 78cd717c93e..25512f51f99 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/index.tsx @@ -2,7 +2,7 @@ import { emailRegEx } from '@logto/core-kit'; import { generateStandardShortId } from '@logto/shared/universal'; import { conditional, type Nullable } from '@silverhand/essentials'; import classNames from 'classnames'; -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import Close from '@/assets/icons/close.svg'; @@ -23,6 +23,13 @@ type Props = { placeholder?: string; }; +/** + * The body-2 font declared in @logto/core-kit/scss/fonts. It is referenced here to calculate + * the width of the input text, which determines the minimum width of the input field. + */ +const fontBody2 = + '400 14px / 20px -apple-system, system-ui, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji'; + function InviteEmailsInput({ className, values, @@ -35,6 +42,18 @@ function InviteEmailsInput({ const [currentValue, setCurrentValue] = useState(''); const { setError, clearErrors } = useFormContext(); const { parseEmailOptions } = useEmailInputUtils(); + const [minInputWidth, setMinInputWidth] = useState(0); + const canvasRef = useRef(null); + + useEffect(() => { + // Render placeholder text in canvas to calculate its width in CSS pixels. + const ctx = canvasRef.current?.getContext('2d'); + if (!ctx) { + return; + } + ctx.font = fontBody2; + setMinInputWidth(ctx.measureText(currentValue).width); + }, [currentValue]); const onChange = (values: InviteeEmailItem[]) => { const { values: parsedValues, errorMessage } = parseEmailOptions(values); @@ -67,12 +86,7 @@ function InviteEmailsInput({ return ( <>
{ @@ -82,75 +96,79 @@ function InviteEmailsInput({ ref.current?.focus(); }} > - {values.map((option) => ( - { - ref.current?.focus(); - }} - > - {option.value} - + {values.map((option) => ( + { - handleDelete(option); + ref.current?.focus(); }} - onKeyDown={onKeyDownHandler(() => { - handleDelete(option); - })} > - - - - ))} - { - if (event.key === 'Backspace' && currentValue === '') { - if (focusedValueId) { - onChange(values.filter(({ id }) => id !== focusedValueId)); - setFocusedValueId(null); - } else { - setFocusedValueId(values.at(-1)?.id ?? null); + {option.value} + { + handleDelete(option); + }} + onKeyDown={onKeyDownHandler(() => { + handleDelete(option); + })} + > + + + + ))} + { + if (event.key === 'Backspace' && currentValue === '') { + if (focusedValueId) { + onChange(values.filter(({ id }) => id !== focusedValueId)); + setFocusedValueId(null); + } else { + setFocusedValueId(values.at(-1)?.id ?? null); + } + ref.current?.focus(); } + if (event.key === ' ' || event.code === 'Space' || event.key === 'Enter') { + // Focusing on input + if (currentValue !== '' && document.activeElement === ref.current) { + handleAdd(currentValue); + } + // Do not react to "Enter" + event.preventDefault(); + } + }} + onChange={({ currentTarget: { value } }) => { + setCurrentValue(value); + setFocusedValueId(null); + }} + onFocus={() => { ref.current?.focus(); - } - if (event.key === ' ' || event.code === 'Space' || event.key === 'Enter') { - // Focusing on input - if (currentValue !== '' && document.activeElement === ref.current) { + }} + onBlur={() => { + if (currentValue !== '') { handleAdd(currentValue); } - // Do not react to "Enter" - event.preventDefault(); - } - }} - onChange={({ currentTarget: { value } }) => { - setCurrentValue(value); - setFocusedValueId(null); - }} - onFocus={() => { - ref.current?.focus(); - }} - onBlur={() => { - if (currentValue !== '') { - handleAdd(currentValue); - } - setFocusedValueId(null); - }} - /> + setFocusedValueId(null); + }} + /> +
{Boolean(error) && typeof error === 'string' && (
{error}
)} + ); }