diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index ff51e28b7b..6b71f42671 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -103,6 +103,10 @@ $SpaceRoomViewInnerWidth: 428px;
padding: 8px 22px;
margin-left: 16px;
}
+
+ input.mx_AccessibleButton {
+ border: none; // override default styles
+ }
}
.mx_Field {
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 5db54815b7..eb9b8665d8 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -385,7 +385,9 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
/>;
});
- const onNextClick = async () => {
+ const onNextClick = async (ev) => {
+ ev.preventDefault();
+ if (busy) return;
setError("");
setBusy(true);
try {
@@ -410,7 +412,10 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
setBusy(false);
};
- let onClick = onFinished;
+ let onClick = (ev) => {
+ ev.preventDefault();
+ onFinished();
+ };
let buttonLabel = _t("Skip for now");
if (roomNames.some(name => name.trim())) {
onClick = onNextClick;
@@ -422,16 +427,20 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
{ description }
{ error && { error }
}
- { fields }
+
- { buttonLabel }
-
+ element="input"
+ type="submit"
+ form="mx_SpaceSetupFirstRooms"
+ value={buttonLabel}
+ />
;
};
@@ -575,7 +584,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
/>;
});
- const onNextClick = async () => {
+ const onNextClick = async (ev) => {
+ ev.preventDefault();
+ if (busy) return;
setError("");
for (let i = 0; i < fieldRefs.length; i++) {
const fieldRef = fieldRefs[i];
@@ -609,7 +620,10 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
setBusy(false);
};
- let onClick = onFinished;
+ let onClick = (ev) => {
+ ev.preventDefault();
+ onFinished();
+ };
let buttonLabel = _t("Skip for now");
if (emailAddresses.some(name => name.trim())) {
onClick = onNextClick;
@@ -623,7 +637,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
{ error && { error }
}
- { fields }
+
;
};
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) => {
const avatarUploadRef = useRef();
const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache
@@ -81,20 +75,34 @@ const SpaceBasicSettings = ({
}
}
+ return
+ { avatarSection }
+ {
+ 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/*" />
+
;
+};
+
+const SpaceBasicSettings = ({
+ avatarUrl,
+ avatarDisabled = false,
+ setAvatar,
+ name = "",
+ nameDisabled = false,
+ setName,
+ topic = "",
+ topicDisabled = false,
+ setTopic,
+}: IProps) => {
return
-
- { avatarSection }
- {
- 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/*" />
-
+
{
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(null);
- const [name, setName] = useState("");
- const [avatar, setAvatar] = useState(null);
- const [topic, setTopic] = useState("");
const [busy, setBusy] = useState(false);
- const onSpaceCreateClick = async () => {
+ const [name, setName] = useState("");
+ const spaceNameField = useRef();
+ const [avatar, setAvatar] = useState(null);
+ const [topic, setTopic] = useState("");
+
+ 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 }) => {
}
-
+
+
+
{ busy ? _t("Creating...") : _t("Create") }
;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index f60770acf6..4b574a1bf9 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",