Merge pull request #14585 from overleaf/msm-sso-config-modal

[web] SSO Config modal

GitOrigin-RevId: e704afa61fe14390b64ce29a27ccbce7e884b396
This commit is contained in:
Miguel Serrano
2023-09-13 08:03:46 +00:00
committed by Copybot
parent 4b72908940
commit 0f30edf69f
10 changed files with 502 additions and 6 deletions
@@ -1,20 +1,38 @@
/* eslint-disable jsx-a11y/label-has-for */
/* eslint-disable jsx-a11y/label-has-associated-control */
import { useRef, useEffect } from 'react'
import classNames from 'classnames'
import { useSelect } from 'downshift'
import Icon from './icon'
import { useTranslation } from 'react-i18next'
type SelectProps<T> = {
export type SelectProps<T> = {
// The items rendered as dropdown options.
items: T[]
itemToString: (item: T | null) => string
// Stringifies an item of type T. The resulting string is rendered as a dropdown option.
itemToString: (item: T | null | undefined) => string
// Caption for the dropdown.
label?: string
// Attribute used to identify the component inside a Form. This name is used to
// retrieve FormData when the form is submitted. The value of the FormData entry
// is the string returned by `itemToString(selectedItem)`.
name?: string
// Hint text displayed in the initial render.
defaultText?: string
itemToSubtitle?: (item: T | null) => string
// Initial selected item, displayed in the initial render. When both `defaultText`
// and `defaultItem` are set the latter is ignored.
defaultItem?: T
// Stringifies an item. The resulting string is rendered as a subtitle in a dropdown option.
itemToSubtitle?: (item: T | null | undefined) => string
// Stringifies an item. The resulting string is rendered as a React `key` for each item.
itemToKey: (item: T) => string
// Callback invoked after the selected item is updated.
onSelectedItemChanged?: (item: T | null | undefined) => void
// When `true` item selection is disabled.
disabled?: boolean
// When `true` displays an "Optional" subtext after the `label` caption.
optionalLabel?: boolean
// When `true` displays a spinner next to the `label` caption.
loading?: boolean
}
@@ -22,7 +40,9 @@ export const Select = <T,>({
items,
itemToString = item => (item === null ? '' : String(item)),
label,
name,
defaultText = 'Items',
defaultItem,
itemToSubtitle,
itemToKey,
onSelectedItemChanged,
@@ -48,8 +68,37 @@ export const Select = <T,>({
}
},
})
const rootRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (!name || !rootRef.current) return
const parentForm: HTMLFormElement | null | undefined =
rootRef.current?.closest('form')
if (!parentForm) return
function handleFormDataEvent(event: FormDataEvent) {
const data = event.formData
const key = name as string // can't be undefined due to early exit in the effect
if (selectedItem || defaultItem) {
data.append(key, itemToString(selectedItem || defaultItem))
}
}
parentForm.addEventListener('formdata', handleFormDataEvent)
return () => {
parentForm.removeEventListener('formdata', handleFormDataEvent)
}
}, [name, itemToString, selectedItem, defaultItem])
let value: string | undefined
if (selectedItem || defaultItem) {
value = itemToString(selectedItem || defaultItem)
} else {
value = defaultText
}
return (
<div className="select-wrapper">
<div className="select-wrapper" ref={rootRef}>
<div>
{label ? (
<label {...getLabelProps()}>
@@ -59,14 +108,14 @@ export const Select = <T,>({
({t('optional')})
</span>
)}{' '}
{loading && <Icon fw type="spinner" spin />}
{loading && <Icon data-testid="spinner" fw type="spinner" spin />}
</label>
) : null}
<div
className={classNames({ disabled }, 'select-trigger')}
{...getToggleButtonProps({ disabled })}
>
<div>{selectedItem ? itemToString(selectedItem) : defaultText}</div>
<div>{value}</div>
<div>
{isOpen ? (
<Icon type="chevron-up" fw />