From 3a75eb12265ac83b9d88549b1b7f593905e5912d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 10 May 2021 14:39:10 +0100 Subject: [PATCH] Fix handling of enter/return in space creation menu --- .../views/spaces/SpaceBasicSettings.tsx | 50 ++++++++------- .../views/spaces/SpaceCreateMenu.tsx | 61 ++++++++++++++++--- src/i18n/strings/en_EN.json | 1 + 3 files changed, 83 insertions(+), 29 deletions(-) diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index bc378ab956..ec40f7bed8 100644 --- a/src/components/views/spaces/SpaceBasicSettings.tsx +++ b/src/components/views/spaces/SpaceBasicSettings.tsx @@ -32,17 +32,11 @@ interface IProps { setTopic(topic: string): void; } -const SpaceBasicSettings = ({ +export const SpaceAvatar = ({ avatarUrl, avatarDisabled = false, setAvatar, - name = "", - nameDisabled = false, - setName, - topic = "", - topicDisabled = false, - setTopic, -}: IProps) => { +}: Pick<IProps, "avatarUrl" | "avatarDisabled" | "setAvatar">) => { const avatarUploadRef = useRef<HTMLInputElement>(); const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache @@ -81,20 +75,34 @@ const SpaceBasicSettings = ({ } } + return <div className="mx_SpaceBasicSettings_avatarContainer"> + { avatarSection } + <input type="file" ref={avatarUploadRef} onChange={(e) => { + if (!e.target.files?.length) return; + const file = e.target.files[0]; + setAvatar(file); + const reader = new FileReader(); + reader.onload = (ev) => { + setAvatarDataUrl(ev.target.result as string); + }; + reader.readAsDataURL(file); + }} accept="image/*" /> + </div>; +}; + +const SpaceBasicSettings = ({ + avatarUrl, + avatarDisabled = false, + setAvatar, + name = "", + nameDisabled = false, + setName, + topic = "", + topicDisabled = false, + setTopic, +}: IProps) => { return <div className="mx_SpaceBasicSettings"> - <div className="mx_SpaceBasicSettings_avatarContainer"> - { avatarSection } - <input type="file" ref={avatarUploadRef} onChange={(e) => { - if (!e.target.files?.length) return; - const file = e.target.files[0]; - setAvatar(file); - const reader = new FileReader(); - reader.onload = (ev) => { - setAvatarDataUrl(ev.target.result as string); - }; - reader.readAsDataURL(file); - }} accept="image/*" /> - </div> + <SpaceAvatar avatarUrl={avatarUrl} avatarDisabled={avatarDisabled} setAvatar={setAvatar} /> <Field name="spaceName" diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 9c42b9c7c4..491a9bffee 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useContext, useState} from "react"; +import React, {useContext, useRef, useState} from "react"; import classNames from "classnames"; import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event"; @@ -23,9 +23,11 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {ChevronFace, ContextMenu} from "../../structures/ContextMenu"; import createRoom, {IStateEvent, Preset} from "../../../createRoom"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import SpaceBasicSettings from "./SpaceBasicSettings"; +import {SpaceAvatar} from "./SpaceBasicSettings"; import AccessibleButton from "../elements/AccessibleButton"; import FocusLock from "react-focus-lock"; +import Field from "../elements/Field"; +import withValidation from "../elements/Validation"; const SpaceCreateMenuType = ({ title, description, className, onClick }) => { return ( @@ -41,17 +43,39 @@ enum Visibility { Private, } +const spaceNameValidator = withValidation({ + rules: [ + { + key: "required", + test: async ({ value }) => !!value, + invalid: () => _t("Please enter a name for the space"), + }, + ], +}); + const SpaceCreateMenu = ({ onFinished }) => { const cli = useContext(MatrixClientContext); const [visibility, setVisibility] = useState<Visibility>(null); - const [name, setName] = useState(""); - const [avatar, setAvatar] = useState<File>(null); - const [topic, setTopic] = useState<string>(""); const [busy, setBusy] = useState<boolean>(false); - const onSpaceCreateClick = async () => { + const [name, setName] = useState(""); + const spaceNameField = useRef<Field>(); + const [avatar, setAvatar] = useState<File>(null); + const [topic, setTopic] = useState<string>(""); + + const onSpaceCreateClick = async (e) => { + e.preventDefault(); if (busy) return; + setBusy(true); + // require & validate the space name field + if (!await spaceNameField.current.validate({ allowEmpty: false })) { + spaceNameField.current.focus(); + spaceNameField.current.validate({ allowEmpty: false, focused: true }); + setBusy(false); + return; + } + const initialState: IStateEvent[] = [ { type: EventType.RoomHistoryVisibility, @@ -146,9 +170,30 @@ const SpaceCreateMenu = ({ onFinished }) => { } </p> - <SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} /> + <form className="mx_SpaceBasicSettings" onSubmit={onSpaceCreateClick}> + <SpaceAvatar setAvatar={setAvatar} /> - <AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name || busy}> + <Field + name="spaceName" + label={_t("Name")} + autoFocus={true} + value={name} + onChange={ev => setName(ev.target.value)} + ref={spaceNameField} + onValidate={spaceNameValidator} + /> + + <Field + name="spaceTopic" + element="textarea" + label={_t("Description")} + value={topic} + onChange={ev => setTopic(ev.target.value)} + rows={3} + /> + </form> + + <AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}> { busy ? _t("Creating...") : _t("Create") } </AccessibleButton> </React.Fragment>; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dcad970300..26275c4325 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -996,6 +996,7 @@ "Upload": "Upload", "Name": "Name", "Description": "Description", + "Please enter a name for the space": "Please enter a name for the space", "Create a space": "Create a space", "Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.", "Public": "Public",