Add jump to date functionality to date headers in timeline v2 (#7339)
Fix https://github.com/vector-im/element-web/issues/7677 Utilizes MSC3030: https://github.com/matrix-org/matrix-doc/pull/3030 https://user-images.githubusercontent.com/558581/150060664-79627573-f4fd-497c-b726-dc3485854bd0.png
This commit is contained in:
parent
efa1667d7e
commit
7fa27f5834
17 changed files with 630 additions and 44 deletions
|
@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
|
||||
import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { debounce } from "lodash";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import { IFieldState, IValidationResult } from "./Validation";
|
||||
import { ComponentClass } from "../../../@types/common";
|
||||
|
||||
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
||||
const VALIDATION_THROTTLE_MS = 200;
|
||||
|
@ -78,26 +79,45 @@ interface IProps {
|
|||
}
|
||||
|
||||
export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> {
|
||||
// The ref pass through to the input
|
||||
inputRef?: RefObject<HTMLInputElement>;
|
||||
// The element to create. Defaults to "input".
|
||||
element?: "input";
|
||||
componentClass?: undefined;
|
||||
// The input's value. This is a controlled component, so the value is required.
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ISelectProps extends IProps, SelectHTMLAttributes<HTMLSelectElement> {
|
||||
// The ref pass through to the select
|
||||
inputRef?: RefObject<HTMLSelectElement>;
|
||||
// To define options for a select, use <Field><option ... /></Field>
|
||||
element: "select";
|
||||
componentClass?: undefined;
|
||||
// The select's value. This is a controlled component, so the value is required.
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ITextareaProps extends IProps, TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
// The ref pass through to the textarea
|
||||
inputRef?: RefObject<HTMLTextAreaElement>;
|
||||
element: "textarea";
|
||||
componentClass?: undefined;
|
||||
// The textarea's value. This is a controlled component, so the value is required.
|
||||
value: string;
|
||||
}
|
||||
|
||||
type PropShapes = IInputProps | ISelectProps | ITextareaProps;
|
||||
export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes<HTMLInputElement> {
|
||||
// The ref pass through to the input
|
||||
inputRef?: RefObject<HTMLInputElement>;
|
||||
element: "input";
|
||||
// The custom component to render
|
||||
componentClass: ComponentClass;
|
||||
// The input's value. This is a controlled component, so the value is required.
|
||||
value: string;
|
||||
}
|
||||
|
||||
type PropShapes = IInputProps | ISelectProps | ITextareaProps | INativeOnChangeInputProps;
|
||||
|
||||
interface IState {
|
||||
valid: boolean;
|
||||
|
@ -108,7 +128,7 @@ interface IState {
|
|||
|
||||
export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||
private id: string;
|
||||
private input: HTMLInputElement;
|
||||
private inputRef: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
|
||||
|
||||
public static readonly defaultProps = {
|
||||
element: "input",
|
||||
|
@ -146,7 +166,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
}
|
||||
|
||||
public focus() {
|
||||
this.input.focus();
|
||||
this.inputRef.current?.focus();
|
||||
// programmatic does not fire onFocus handler
|
||||
this.setState({
|
||||
focused: true,
|
||||
|
@ -197,7 +217,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
if (!this.props.onValidate) {
|
||||
return;
|
||||
}
|
||||
const value = this.input ? this.input.value : null;
|
||||
const value = this.inputRef.current?.value ?? null;
|
||||
const { valid, feedback } = await this.props.onValidate({
|
||||
value,
|
||||
focused,
|
||||
|
@ -228,13 +248,13 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
|
||||
public render() {
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const { element, prefixComponent, postfixComponent, className, onValidate, children,
|
||||
const { element, componentClass, inputRef, prefixComponent, postfixComponent, className, onValidate, children,
|
||||
tooltipContent, forceValidity, tooltipClassName, list, validateOnBlur, validateOnChange, validateOnFocus,
|
||||
usePlaceholderAsHint, forceTooltipVisible,
|
||||
...inputProps } = this.props;
|
||||
|
||||
// Set some defaults for the <input> element
|
||||
const ref = input => this.input = input;
|
||||
this.inputRef = inputRef || React.createRef();
|
||||
|
||||
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
||||
inputProps.id = this.id; // this overwrites the id from props
|
||||
|
||||
|
@ -243,9 +263,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
inputProps.onBlur = this.onBlur;
|
||||
|
||||
// Appease typescript's inference
|
||||
const inputProps_ = { ...inputProps, ref, list };
|
||||
const inputProps_ = { ...inputProps, ref: this.inputRef, list };
|
||||
|
||||
const fieldInput = React.createElement(this.props.element, inputProps_, children);
|
||||
const fieldInput = React.createElement(this.props.componentClass || this.props.element, inputProps_, children);
|
||||
|
||||
let prefixContainer = null;
|
||||
if (prefixComponent) {
|
||||
|
@ -257,17 +277,22 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
}
|
||||
|
||||
const hasValidationFlag = forceValidity !== null && forceValidity !== undefined;
|
||||
const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, {
|
||||
// If we have a prefix element, leave the label always at the top left and
|
||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||
// properly.
|
||||
mx_Field_labelAlwaysTopLeft: prefixComponent || usePlaceholderAsHint,
|
||||
mx_Field_placeholderIsHint: usePlaceholderAsHint,
|
||||
mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true,
|
||||
mx_Field_invalid: hasValidationFlag
|
||||
? !forceValidity
|
||||
: onValidate && this.state.valid === false,
|
||||
});
|
||||
const fieldClasses = classNames(
|
||||
"mx_Field",
|
||||
`mx_Field_${this.props.element}`,
|
||||
className,
|
||||
{
|
||||
// If we have a prefix element, leave the label always at the top left and
|
||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||
// properly.
|
||||
mx_Field_labelAlwaysTopLeft: prefixComponent || usePlaceholderAsHint,
|
||||
mx_Field_placeholderIsHint: usePlaceholderAsHint,
|
||||
mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true,
|
||||
mx_Field_invalid: hasValidationFlag
|
||||
? !forceValidity
|
||||
: onValidate && this.state.valid === false,
|
||||
},
|
||||
);
|
||||
|
||||
// Handle displaying feedback on validity
|
||||
// FIXME: Using an import will result in test failures
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue