fix(ui): tooltip positioning issues (#6439)

This commit is contained in:
Alessio Gravili
2024-05-20 16:37:53 -04:00
committed by GitHub
parent e682cb1b04
commit ed4766188d
31 changed files with 409 additions and 411 deletions

View File

@@ -84,14 +84,14 @@ const _RichText: React.FC<
width,
}}
>
<div className={`${baseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${baseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
<LexicalProvider
editorConfig={editorConfig}

View File

@@ -106,10 +106,6 @@
line-height: unset;
}
&__error-wrap {
position: relative;
}
&__rows {
display: flex;
flex-direction: column;

View File

@@ -1,8 +1,6 @@
@import '../scss/styles.scss';
.rich-text-lexical {
display: flex;
.errorBoundary {
pre {
text-wrap: unset;

View File

@@ -315,14 +315,14 @@ const RichTextField: React.FC<
width,
}}
>
<div className={`${baseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${baseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<Slate
editor={editor}
key={JSON.stringify({ initialValue, path })} // makes sure slate is completely re-rendered when initialValue changes, bypassing the slate-internal value memoization. That way, external changes to the form will update the editor

View File

@@ -6,7 +6,7 @@
opacity: 0;
background-color: var(--theme-elevation-800);
position: absolute;
z-index: 2;
z-index: 3;
left: 50%;
padding: base(0.2) base(0.4);
color: var(--theme-elevation-0);
@@ -52,8 +52,9 @@
}
}
&--position-top {
bottom: 100%;
top: calc(var(--base) * -0.6 - 13px);
transform: translate3d(-50%, calc(var(--caret-size) * -1), 0);
&::after {
@@ -62,8 +63,9 @@
}
}
&--position-bottom {
top: 100%;
bottom: calc(var(--base) * -0.6 - 13px);
transform: translate3d(-50%, var(--caret-size), 0);
&::after {

View File

@@ -11,6 +11,10 @@ export type Props = {
className?: string
delay?: number
show?: boolean
/**
* If the tooltip position should not change depending on if the toolbar is outside the boundingRef. @default false
*/
staticPositioning?: boolean
}
export const Tooltip: React.FC<Props> = (props) => {
@@ -21,6 +25,7 @@ export const Tooltip: React.FC<Props> = (props) => {
className,
delay = 350,
show: showFromProps = true,
staticPositioning = false,
} = props
const [show, setShow] = React.useState(showFromProps)
@@ -28,11 +33,14 @@ export const Tooltip: React.FC<Props> = (props) => {
const getTitleAttribute = (content) => (typeof content === 'string' ? content : '')
const [ref, intersectionEntry] = useIntersect({
const [ref, intersectionEntry] = useIntersect(
{
root: boundingRef?.current || null,
rootMargin: '-145px 0px 0px 100px',
threshold: 0,
})
},
staticPositioning,
)
useEffect(() => {
let timerId: NodeJS.Timeout
@@ -52,22 +60,25 @@ export const Tooltip: React.FC<Props> = (props) => {
}, [showFromProps, delay])
useEffect(() => {
if (staticPositioning) return
setPosition(intersectionEntry?.isIntersecting ? 'top' : 'bottom')
}, [intersectionEntry])
}, [intersectionEntry, staticPositioning])
// The first aside is always on top. The purpose of that is that it can reliably be used for the interaction observer (as it's not moving around), to calculate the position of the actual tooltip.
return (
<React.Fragment>
{!staticPositioning && (
<aside
aria-hidden="true"
className={['tooltip', className, `tooltip--caret-${alignCaret}`, 'tooltip--position-top']
.filter(Boolean)
.join(' ')}
ref={ref}
title={getTitleAttribute(children)}
style={{ opacity: '0' }}
>
<div className="tooltip-content">{children}</div>
</aside>
)}
<aside
className={[
'tooltip',

View File

@@ -54,10 +54,6 @@
}
}
&__error-wrap {
position: relative;
}
&__row-header {
display: flex;
align-items: center;

View File

@@ -202,11 +202,7 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
.join(' ')}
id={`field-${path.replace(/\./g, '__')}`}
>
{showError && (
<div className={`${baseClass}__error-wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
</div>
)}
{showError && <FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />}
<header className={`${baseClass}__header`}>
<div className={`${baseClass}__header-wrap`}>
<div className={`${baseClass}__header-content`}>

View File

@@ -75,10 +75,6 @@
line-height: unset;
}
&__error-wrap {
position: relative;
}
&__rows {
display: flex;
flex-direction: column;

View File

@@ -215,11 +215,7 @@ const _BlocksField: React.FC<BlocksFieldProps> = (props) => {
.join(' ')}
id={`field-${path.replace(/\./g, '__')}`}
>
{showError && (
<div className={`${baseClass}__error-wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
</div>
)}
{showError && <FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />}
<header className={`${baseClass}__header`}>
<div className={`${baseClass}__header-wrap`}>
<div className={`${baseClass}__heading-with-error`}>

View File

@@ -10,10 +10,6 @@
margin-bottom: 0.2em;
max-width: fit-content;
}
&__error-wrap {
position: relative;
}
}
.checkbox-input {

View File

@@ -97,9 +97,7 @@ const CheckboxField: React.FC<CheckboxFieldProps> = (props) => {
width,
}}
>
<div className={`${baseClass}__error-wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
</div>
<CheckboxInput
AfterInput={AfterInput}
BeforeInput={BeforeInput}

View File

@@ -87,14 +87,14 @@ const CodeField: React.FC<CodeFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<CodeEditor
defaultLanguage={prismToMonacoLanguageMap[language] || language}

View File

@@ -50,12 +50,13 @@ export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
.filter(Boolean)
.join(' ')}
>
<FieldError path={path} />
<FieldLabel
htmlFor="field-confirm-password"
label={t('authentication:confirmPassword')}
required
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError path={path} />
<input
autoComplete="off"
disabled={!!disabled}
@@ -66,5 +67,6 @@ export const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
value={(value as string) || ''}
/>
</div>
</div>
)
}

View File

@@ -1,11 +1,5 @@
@import '../../scss/styles.scss';
.date-time-field {
&__error-wrap {
position: relative;
}
}
html[data-theme='light'] {
.date-time-field {
&--has-error {

View File

@@ -90,16 +90,14 @@ const DateTimeField: React.FC<DateFieldProps> = (props) => {
width,
}}
>
<div className={`${baseClass}__error-wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
</div>
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${baseClass}__input-wrapper`} id={`field-${path.replace(/\./g, '__')}`}>
<div className={`${fieldBaseClass}__wrap`} id={`field-${path.replace(/\./g, '__')}`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<DatePickerField
{...datePickerProps}

View File

@@ -77,14 +77,14 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<input
autoComplete={autoComplete}

View File

@@ -130,14 +130,15 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<CodeEditor
defaultLanguage="json"

View File

@@ -161,6 +161,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{hasMany ? (
<ReactSelect
@@ -218,6 +219,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
</div>
)
}

View File

@@ -67,13 +67,15 @@ const PasswordField: React.FC<PasswordFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<input
autoComplete={autoComplete}
disabled={formProcessing || disabled}
@@ -84,6 +86,7 @@ const PasswordField: React.FC<PasswordFieldProps> = (props) => {
value={(value as string) || ''}
/>
</div>
</div>
)
}

View File

@@ -1,10 +1,6 @@
@import '../../scss/styles.scss';
.radio-group {
&__error-wrap {
position: relative;
}
&--layout-horizontal {
ul {
display: flex;

View File

@@ -99,15 +99,15 @@ const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
width,
}}
>
<div className={`${baseClass}__error-wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
</div>
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
{options.map((option) => {
let optionValue = ''
@@ -151,6 +151,7 @@ const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
</div>
)
}

View File

@@ -471,13 +471,15 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{!errorLoading && (
<div className={`${baseClass}__wrap`}>
<ReactSelect
@@ -582,6 +584,7 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
</div>
)
}

View File

@@ -149,14 +149,15 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<ReactSelect
disabled={readOnly}

View File

@@ -60,13 +60,15 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{hasMany ? (
<ReactSelect
className={`field-${path.replace(/\./g, '__')}`}
@@ -116,5 +118,6 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
</div>
)
}

View File

@@ -54,13 +54,14 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
<div className="textarea-inner">
@@ -85,5 +86,6 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
</div>
)
}

View File

@@ -133,13 +133,15 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
width,
}}
>
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} {...(errorProps || {})} />
{collection?.upload && (
<React.Fragment>
{fileDoc && !missingFile && (
@@ -184,6 +186,7 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
{!readOnly && <DocumentDrawer onSave={onSave} />}
{!readOnly && <ListDrawer onSelect={onSelect} />}
</div>
</div>
)
}

View File

@@ -2,8 +2,6 @@
.field-error.tooltip {
font-family: var(--font-body);
top: 0;
bottom: auto;
left: auto;
max-width: 75%;
right: calc(var(--base) * 0.5);
@@ -12,5 +10,6 @@
&::after {
border-top-color: var(--theme-error-500);
border-bottom-color: var(--theme-error-500);
}
}

View File

@@ -30,9 +30,9 @@ const DefaultFieldError: React.FC<ErrorProps> = (props) => {
const message = messageFromProps || errorMessage
const showMessage = showErrorFromProps || (hasSubmitted && valid === false)
if (showMessage) {
if (showMessage && message?.length) {
return (
<Tooltip alignCaret={alignCaret} className={baseClass} delay={0}>
<Tooltip alignCaret={alignCaret} className={baseClass} delay={0} staticPositioning>
{message}
</Tooltip>
)

View File

@@ -11,8 +11,13 @@
--spacing-field: 0;
}
.field-type__wrap {
position: relative;
}
& > .field-type {
margin-bottom: var(--spacing-field);
position: relative;
&[type='hidden'] {
margin-bottom: 0;

View File

@@ -12,7 +12,7 @@ export const useIntersect = (
const [node, setNode] = useState(null)
const observer = useRef(
typeof window !== 'undefined' && 'IntersectionObserver' in window
typeof window !== 'undefined' && 'IntersectionObserver' in window && !disable
? new window.IntersectionObserver(([ent]) => updateEntry(ent), {
root,
rootMargin,