summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar tobi <tobi.smethurst@protonmail.com>2025-05-06 08:06:52 +0000
committerLibravatar tobi <kipvandenbos@noreply.codeberg.org>2025-05-06 08:06:52 +0000
commit4a6b3575013278aca47cd1084458091b4055d2d1 (patch)
tree672b61dc617c0b88bd162f593daa1ce6fdf9d697
parent[chore] fix testrig with new otel setup (#4135) (diff)
downloadgotosocial-4a6b3575013278aca47cd1084458091b4055d2d1.tar.xz
[bugfix] Fixes to tablist, fileinput, checkbox (#4139)
Some fixes to various frontend things: - Fix signup checkbox being height 0 on webkit - closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4136 - Fix wonky file input on chrome and webkit - closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4138 - Make tablist in interaction policies keyboard accessible with proper left/right + focus handling, see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/tablist_role Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4139 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
-rw-r--r--web/source/css/base.css5
-rw-r--r--web/source/settings/components/form/inputs.tsx8
-rw-r--r--web/source/settings/views/moderation/domain-permissions/form.tsx7
-rw-r--r--web/source/settings/views/user/posts/interaction-policy-settings/index.tsx112
4 files changed, 86 insertions, 46 deletions
diff --git a/web/source/css/base.css b/web/source/css/base.css
index 2122e5aae..615616725 100644
--- a/web/source/css/base.css
+++ b/web/source/css/base.css
@@ -493,9 +493,8 @@ section.with-form {
gap: 0.4rem;
& > input {
- height: 100%;
- width: 5%;
- min-width: 1.2rem;
+ height: 1rem;
+ width: 1rem;
align-self: center;
}
}
diff --git a/web/source/settings/components/form/inputs.tsx b/web/source/settings/components/form/inputs.tsx
index 1495e18f7..654f1a08f 100644
--- a/web/source/settings/components/form/inputs.tsx
+++ b/web/source/settings/components/form/inputs.tsx
@@ -122,10 +122,6 @@ export function FileInput({ label, field, ...props }: FileInputProps) {
const ref = useRef<HTMLInputElement>(null);
const { onChange, infoComponent } = field;
const id = nanoid();
- const onClick = (e) => {
- e.preventDefault();
- ref.current?.click();
- };
return (
<div className="form-field file">
@@ -133,11 +129,9 @@ export function FileInput({ label, field, ...props }: FileInputProps) {
className="label-wrapper"
htmlFor={id}
tabIndex={0}
- onClick={onClick}
onKeyDown={(e) => {
if (e.key === "Enter") {
- e.preventDefault();
- onClick(e);
+ ref.current?.click();
}
}}
role="button"
diff --git a/web/source/settings/views/moderation/domain-permissions/form.tsx b/web/source/settings/views/moderation/domain-permissions/form.tsx
index 807648438..1c0eadba7 100644
--- a/web/source/settings/views/moderation/domain-permissions/form.tsx
+++ b/web/source/settings/views/moderation/domain-permissions/form.tsx
@@ -71,9 +71,6 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
}, [exportResult]);
const importFileRef = useRef<HTMLInputElement>(null);
- const importFileOnClick = () => {
- importFileRef.current?.click();
- };
return (
<>
@@ -109,11 +106,9 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp
<label
className={`button with-icon${form.permType.value === undefined || form.permType.value.length === 0 ? " disabled" : ""}`}
tabIndex={0}
- onClick={importFileOnClick}
onKeyDown={(e) => {
if (e.key === "Enter") {
- e.preventDefault();
- importFileOnClick();
+ importFileRef.current?.click();
}
}}
role="button"
diff --git a/web/source/settings/views/user/posts/interaction-policy-settings/index.tsx b/web/source/settings/views/user/posts/interaction-policy-settings/index.tsx
index 143cf0865..3c4d51422 100644
--- a/web/source/settings/views/user/posts/interaction-policy-settings/index.tsx
+++ b/web/source/settings/views/user/posts/interaction-policy-settings/index.tsx
@@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import React, { useCallback, useMemo } from "react";
+import React, { forwardRef, useCallback, useMemo, useRef } from "react";
import {
useDefaultInteractionPoliciesQuery,
useResetDefaultInteractionPoliciesMutation,
@@ -191,57 +191,109 @@ function InteractionPoliciesForm({ defaultPolicies }: InteractionPoliciesFormPro
// A tablist of tab buttons, one for each visibility.
function PolicyPanelsTablist({ selectedVis }: { selectedVis: TextFormInputHook}) {
+ const publicRef = useRef<HTMLButtonElement>(null);
+ const unlistedRef = useRef<HTMLButtonElement>(null);
+ const privateRef = useRef<HTMLButtonElement>(null);
+
return (
<div className="tab-buttons" role="tablist">
<Tab
- thisVisibility="public"
label="Public"
selectedVis={selectedVis}
+ prevVis="private"
+ thisVis="public"
+ nextVis="unlisted"
+ prevRef={privateRef}
+ thisRef={publicRef}
+ nextRef={unlistedRef}
/>
<Tab
- thisVisibility="unlisted"
label="Unlisted"
selectedVis={selectedVis}
+ prevVis="public"
+ thisVis="unlisted"
+ nextVis="private"
+ prevRef={publicRef}
+ thisRef={unlistedRef}
+ nextRef={privateRef}
/>
<Tab
- thisVisibility="private"
label="Followers-only"
selectedVis={selectedVis}
+ prevVis="unlisted"
+ thisVis="private"
+ nextVis="public"
+ prevRef={unlistedRef}
+ thisRef={privateRef}
+ nextRef={publicRef}
/>
</div>
);
}
interface TabProps {
- thisVisibility: string;
- label: string,
- selectedVis: TextFormInputHook
+ label: string;
+ selectedVis: TextFormInputHook;
+ prevVis: string;
+ thisVis: string;
+ nextVis: string;
+ prevRef: React.RefObject<HTMLButtonElement>;
+ thisRef: React.RefObject<HTMLButtonElement>;
+ nextRef: React.RefObject<HTMLButtonElement>;
}
// One tab in a tablist, corresponding to the given thisVisibility.
-function Tab({ thisVisibility, label, selectedVis }: TabProps) {
- const selected = useMemo(() => {
- return selectedVis.value === thisVisibility;
- }, [selectedVis, thisVisibility]);
-
- return (
- <button
- id={`tab-${thisVisibility}`}
- title={label}
- role="tab"
- className={`tab-button ${selected && "active"}`}
- onClick={(e) => {
- e.preventDefault();
- selectedVis.setter(thisVisibility);
- }}
- aria-selected={selected}
- aria-controls={`panel-${thisVisibility}`}
- tabIndex={selected ? 0 : -1}
- >
- {label}
- </button>
- );
-}
+const Tab = forwardRef(
+ function Tab({
+ label,
+ selectedVis,
+ prevVis,
+ thisVis,
+ nextVis,
+ prevRef,
+ thisRef,
+ nextRef,
+ }: TabProps) {
+ const selected = useMemo(() => {
+ return selectedVis.value === thisVis;
+ }, [selectedVis, thisVis]);
+
+ return (
+ <button
+ id={`tab-${thisVis}`}
+ title={label}
+ role="tab"
+ ref={thisRef}
+ className={`tab-button ${selected && "active"}`}
+ onClick={(e) => {
+ // Allow tab to be clicked.
+ e.preventDefault();
+ selectedVis.setter(thisVis);
+ }}
+ onKeyDown={(e) => {
+ // Allow cycling through
+ // tabs with arrow keys.
+ if (e.key === "ArrowLeft") {
+ // Select and set
+ // focus on previous tab.
+ selectedVis.setter(prevVis);
+ prevRef.current?.focus();
+ } else if (e.key === "ArrowRight") {
+ // Select and set
+ // focus on next tab.
+ selectedVis.setter(nextVis);
+ nextRef.current?.focus();
+ }
+ }}
+ aria-selected={selected}
+ aria-controls={`panel-${thisVis}`}
+ tabIndex={selected ? 0 : -1}
+ >
+ {label}
+ </button>
+ );
+ }
+);
interface PolicyPanelProps {
policyForm: PolicyForm;