diff options
Diffstat (limited to 'web/source')
21 files changed, 390 insertions, 149 deletions
diff --git a/web/source/css/_colors.css b/web/source/css/_colors.css index f8fb979a1..77f562df4 100644 --- a/web/source/css/_colors.css +++ b/web/source/css/_colors.css @@ -80,6 +80,7 @@ $profile-bg: $gray4; $button-bg: $blue2; $button-fg: $gray1; $button-hover-bg: $blue3; +$button-focus-border: $blue3; $button-danger-bg: $error3; $button-danger-fg: $white1; diff --git a/web/source/css/_media-wrapper.css b/web/source/css/_media-wrapper.css index a567cb0fd..55ad6eba0 100644 --- a/web/source/css/_media-wrapper.css +++ b/web/source/css/_media-wrapper.css @@ -74,6 +74,14 @@ div.blurhash-container > canvas { display: none; } + + /* + Hide focus outline on click + to avoid ugly artifacts. + */ + &:focus { + outline: none; + } } summary { @@ -109,6 +117,16 @@ .hide { display: none; } + + &:focus-visible { + /* + Can't rely on media having background with + decent contrast so inset and use button-fg + instead so focus is definitely visible. + */ + outline: 0.25rem dashed $button-fg; + outline-offset: -0.25rem; + } } .show.sensitive { @@ -126,6 +144,21 @@ } } + a.photoswipe-slide { + display: inline-block; + height: 100%; + width: 100%; + + /* + Inset outline to avoid outline + being hidden by overflow: hidden. + */ + &:focus-visible { + outline: $button-focus-outline; + outline-offset: -0.25rem; + } + } + video.plyr-video, .plyr { position: absolute; height: 100%; diff --git a/web/source/css/_profile-header.css b/web/source/css/_profile-header.css index b4ebadf8d..cba67ffa1 100644 --- a/web/source/css/_profile-header.css +++ b/web/source/css/_profile-header.css @@ -81,6 +81,25 @@ height: $avatar-size; width: $avatar-size; + /* + Link to open media in slide + should fill entire media wrapper. + */ + a.photoswipe-slide { + display: inline-block; + height: 100%; + width: 100%; + + /* + Offset to avoid clashing with + thick border around avatars. + */ + &:focus-visible { + outline: $button-focus-outline; + outline-offset: 0.25rem; + } + } + .avatar { /* Fit 100% of the wrapper. diff --git a/web/source/css/base.css b/web/source/css/base.css index 765453ac2..6a5a6dd36 100644 --- a/web/source/css/base.css +++ b/web/source/css/base.css @@ -68,6 +68,40 @@ $br-inner: 0.2rem; */ $fa-fw: 1.28571429em; +/* + Outline to give links when they're + focused (ie., by clicking or tabbing to them). +*/ +$link-focus-outline: 0.25rem dotted $link-fg; + +/* + Outline to give buttons when they're + focused (ie., by clicking or tabbing to them). +*/ +$button-focus-outline: 0.25rem dashed $button-focus-border; + +/* + Outline to give input elements like radio buttons + and checkboxes when they're focused (ie., by clicking + or tabbing to them). +*/ +$input-clickable-focus-outline: 0.25rem dashed $input-focus-border; + +/* + Outline to give summary elements when they're + focused (ie., by clicking or tabbing to them). +*/ +$summary-focus-outline: 0.25rem dotted $link-fg; + +/* + Outline to give <pre> elements when they're + focused (ie., by clicking or tabbing to them). + + This is used when we've got a preformatted + code block with a scroll bar inside of it. +*/ +$pre-focus-outline: 0.25rem dashed $link-fg; + /****************************************** ***** SECTION 2: BASIC GLOBAL STYLING ***** *******************************************/ @@ -88,6 +122,9 @@ body { a { color: $link-fg; + &:focus-visible { + outline: $link-focus-outline; + } } /* @@ -144,6 +181,14 @@ main { &:hover { background: $button-hover-bg; } + + &:focus-visible { + outline: $button-focus-outline; + } +} + +summary:focus-visible { + outline: $summary-focus-outline; } /* @@ -164,6 +209,11 @@ input, select, textarea, .input { border-color: $input-focus-border; } + &[type=checkbox]:focus-visible, + &[type=radio]:focus-visible { + outline: $input-clickable-focus-outline; + } + &:invalid, .invalid & { border-color: $input-error-border; } @@ -342,6 +392,10 @@ pre, pre[class*="language-"] { white-space: pre; overflow-x: auto; + &:focus { + outline: $pre-focus-outline; + } + /* Code inside a pre block, ie., diff --git a/web/source/css/status.css b/web/source/css/status.css index ec6cac3e5..6f2c458f4 100644 --- a/web/source/css/status.css +++ b/web/source/css/status.css @@ -299,6 +299,14 @@ position: absolute; z-index: 0; + + &:focus-visible { + /* + Inset focus to compensate for themes where + statuses have a really thick border. + */ + outline-offset: -0.25rem; + } } &:first-child { diff --git a/web/source/frontend/index.js b/web/source/frontend/index.js index 5a6224994..6d4b1470d 100644 --- a/web/source/frontend/index.js +++ b/web/source/frontend/index.js @@ -143,11 +143,23 @@ lightbox.on('uiRegister', function() { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener'); pswp.on('change', () => { - el.href = pswp.currSlide.data.parentStatus - ? pswp.currSlide.data.parentStatus - : pswp.currSlide.data.element.dataset.pswpParentStatus; + switch (true) { + case pswp.currSlide.data.parentStatus !== undefined: + // Link to parent status. + el.href = pswp.currSlide.data.parentStatus; + break; + case pswp.currSlide.data.element !== undefined && + pswp.currSlide.data.element.dataset.pswpParentStatus !== undefined: + // Link to parent status. + el.href = pswp.currSlide.data.element.dataset.pswpParentStatus; + break; + default: + // Link to profile. + const location = window.location; + el.href = "//" + location.host + location.pathname; + } }); - } + } }); }); @@ -163,26 +175,63 @@ function dynamicSpoiler(className, updateFunc) { }); } -dynamicSpoiler("text-spoiler", (spoiler) => { - const button = spoiler.querySelector(".button"); +dynamicSpoiler("text-spoiler", (details) => { + const summary = details.children[0]; + const button = details.querySelector(".button"); + + // Use button inside summary to + // toggle post body visibility. + button.tabIndex = "0"; + button.setAttribute("aria-role", "button"); + button.onclick = () => { + details.click(); + }; + + // Let enter also trigger the button + // (for those using keyboard to navigate). + button.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + summary.click(); + } + }); + // Change button text depending on + // whether spoiler is open or closed rn. return () => { - button.textContent = spoiler.open + button.textContent = details.open ? "Show less" : "Show more"; }; }); -dynamicSpoiler("media-spoiler", (spoiler) => { - const eye = spoiler.querySelector(".eye.button"); - const video = spoiler.querySelector(".plyr-video"); +dynamicSpoiler("media-spoiler", (details) => { + const summary = details.children[0]; + const button = details.querySelector(".eye.button"); + const video = details.querySelector(".plyr-video"); const loopingAuto = !reduceMotion.matches && video != null && video.classList.contains("gifv"); + // Use button *instead of summary* + // to toggle media visibility. + summary.tabIndex = "-1"; + button.tabIndex = "0"; + button.setAttribute("aria-role", "button"); + button.onclick = () => { + details.click(); + }; + + // Let enter also trigger the button + // (for those using keyboard to navigate). + button.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + summary.click(); + } + }); + return () => { - if (spoiler.open) { - eye.setAttribute("aria-label", "Hide media"); + if (details.open) { + button.setAttribute("aria-label", "Hide media"); } else { - eye.setAttribute("aria-label", "Show media"); + button.setAttribute("aria-label", "Show media"); if (video && !loopingAuto) { video.pause(); } diff --git a/web/source/settings/components/form/inputs.tsx b/web/source/settings/components/form/inputs.tsx index 498499db6..c26b88f6a 100644 --- a/web/source/settings/components/form/inputs.tsx +++ b/web/source/settings/components/form/inputs.tsx @@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React from "react"; +import React, { useRef } from "react"; import type { ReactNode, @@ -119,23 +119,36 @@ export interface FileInputProps extends React.DetailedHTMLProps< } export function FileInput({ label, field, ...props }: FileInputProps) { - const { onChange, ref, infoComponent } = field; + const ref = useRef<HTMLInputElement>(null); + const { onChange, infoComponent } = field; const id = nanoid(); + const onClick = () => { + ref.current?.click(); + }; return ( <div className="form-field file"> - <label className="label-label" htmlFor={id}> - {label} - </label> - <label className="label-button" htmlFor={id}> - <div className="file-input button">Browse</div> + <label + className="label-wrapper" + htmlFor={id} + tabIndex={0} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} + role="button" + > + <div className="label-label"> + {label} + </div> + <div className="label-button"> + <div className="file-input button">Browse</div> + </div> </label> <input id={id} type="file" className="hidden" onChange={onChange} - ref={ref ? ref as RefObject<HTMLInputElement> : undefined} + ref={ref} {...props} /> {infoComponent} diff --git a/web/source/settings/components/status.tsx b/web/source/settings/components/status.tsx index 701a9f8b7..04c5933d5 100644 --- a/web/source/settings/components/status.tsx +++ b/web/source/settings/components/status.tsx @@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React from "react"; +import React, { useRef } from "react"; import { useVerifyCredentialsQuery } from "../lib/query/login"; import { MediaAttachment, Status as StatusType } from "../lib/types/status"; import sanitize from "sanitize-html"; @@ -122,10 +122,26 @@ function StatusBody({ status }: { status: StatusType }) { content = sanitize(status.content); } + const detailsRef = useRef<HTMLDetailsElement>(null); + const detailsOnClick = () => { + detailsRef.current?.click(); + }; + + const summaryRef = useRef<HTMLElement>(null); + const summaryOnClick = () => { + summaryRef.current?.click(); + }; + return ( <div className="status-body"> - <details className="text-spoiler"> - <summary> + <details + className="text-spoiler" + ref={detailsRef} + > + <summary + tabIndex={-1} + ref={summaryRef} + > <div className="spoiler-content" lang={status.language} @@ -140,6 +156,8 @@ function StatusBody({ status }: { status: StatusType }) { role="button" tabIndex={0} aria-label="Toggle content visibility" + onClick={detailsOnClick} + onKeyDown={e => e.key === "Enter" && summaryOnClick()} > Toggle content visibility </span> @@ -183,23 +201,41 @@ function StatusMedia({ status }: { status: StatusType }) { } function StatusMediaEntry({ media }: { media: MediaAttachment }) { + const detailsRef = useRef<HTMLDetailsElement>(null); + const detailsOnClick = () => { + detailsRef.current?.click(); + }; + + const summaryRef = useRef<HTMLElement>(null); + const summaryOnClick = () => { + summaryRef.current?.click(); + }; + return ( <div className="media-wrapper"> <details className="image-spoiler media-spoiler"> - <summary> - <div className="show sensitive button" aria-hidden="true">Show media</div> - <span className="eye button" role="button" tabIndex={0} aria-label="Toggle show media"> + <summary tabIndex={-1} ref={summaryRef}> + <div + className="show sensitive button" + role="button" + tabIndex={0} + aria-hidden="true" + onClick={detailsOnClick} + onKeyDown={e => e.key === "Enter" && summaryOnClick()} + > + Show media + </div> + <span + className="eye button" + role="button" + tabIndex={0} + aria-label="Toggle show media" + onClick={detailsOnClick} + onKeyDown={e => e.key === "Enter" && summaryOnClick()} + > <i className="hide fa fa-fw fa-eye-slash" aria-hidden="true"></i> <i className="show fa fa-fw fa-eye" aria-hidden="true"></i> </span> - <img - src={media.preview_url} - loading="lazy" - alt={media.description} - title={media.description} - width={media.meta.small.width} - height={media.meta.small.height} - /> </summary> <a href={media.url} diff --git a/web/source/settings/components/username-lozenge.tsx b/web/source/settings/components/username-lozenge.tsx index 9f955cf22..44d627ed3 100644 --- a/web/source/settings/components/username-lozenge.tsx +++ b/web/source/settings/components/username-lozenge.tsx @@ -150,19 +150,21 @@ function ReadyUsernameLozenge({ account, linkTo, backLocation, classNames }: Rea if (linkTo) { className += " pseudolink"; + const onClick = () => { + // When clicking on an account, direct + // to the detail view for that account. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; return ( <span className={className} - onClick={() => { - // When clicking on an account, direct - // to the detail view for that account. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/lib/form/file.tsx b/web/source/settings/lib/form/file.tsx index cf9407827..5ecca3084 100644 --- a/web/source/settings/lib/form/file.tsx +++ b/web/source/settings/lib/form/file.tsx @@ -84,7 +84,7 @@ export default function useFileInput( } const infoComponent = ( - <span className="form-info"> + <span className="form-info text-cutoff"> {info ? info : initialInfo diff --git a/web/source/settings/style.css b/web/source/settings/style.css index 2afb44c42..a87d4813c 100644 --- a/web/source/settings/style.css +++ b/web/source/settings/style.css @@ -567,40 +567,34 @@ form { } .form-field.file { - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: auto auto; - grid-template-areas: - "label-label label-label" - "label-button file-info" - ; - - .label-label { - grid-area: label-label; - } + display: flex; + position: relative; + overflow: hidden; - .label-button { - grid-area: label-button; + .label-wrapper { + width: fit-content; + display: flex; + flex-direction: column; + &:focus-visible { + outline: 0.15rem dashed $button-focus-border; + outline-offset: -0.15rem; + } } .form-info { - grid-area: file-info; + position: absolute; + font-weight: initial; + align-self: end; + margin-left: 4.25rem; + margin-bottom: 0.3rem; + .error { padding: 0.1rem; - line-height: 1.4rem; + line-height: 1.4rem; } } } -span.form-info { - flex: 1 1 auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0.3rem 0; - font-weight: initial; -} - .checkbox-list { .header, .entry { display: grid; @@ -1337,6 +1331,9 @@ button.tab-button { .pseudolink { cursor: pointer; text-decoration: none; + &:focus-visible { + outline: 0.15rem dotted $button-focus-border; + } } .info-list { @@ -1738,7 +1735,6 @@ button.tab-button { .mutation-button { width: 100%; - overflow-x: hidden; button { font-size: 1rem; diff --git a/web/source/settings/views/admin/http-header-permissions/overview.tsx b/web/source/settings/views/admin/http-header-permissions/overview.tsx index b2d8b7372..0b708aa8c 100644 --- a/web/source/settings/views/admin/http-header-permissions/overview.tsx +++ b/web/source/settings/views/admin/http-header-permissions/overview.tsx @@ -65,20 +65,23 @@ export default function HeaderPermsOverview() { } = useGetHeaderAllowsQuery(NoArg, { skip: permType !== "allow" }); const itemToEntry = (perm: HeaderPermission) => { + const onClick = () => { + // When clicking on a header perm, + // go to the detail view for perm. + setLocation(`/${permType}s/${perm.id}`, { + // Store the back location in + // history so the detail view + // can use it to return here. + state: { backLocation: location } + }); + }; + return ( <dl key={perm.id} className="entry pseudolink" - onClick={() => { - // When clicking on a header perm, - // go to the detail view for perm. - setLocation(`/${permType}s/${perm.id}`, { - // Store the back location in - // history so the detail view - // can use it to return here. - state: { backLocation: location } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx b/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx index 19dbe0d88..36bc7ee01 100644 --- a/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx +++ b/web/source/settings/views/moderation/domain-permissions/drafts/index.tsx @@ -211,21 +211,24 @@ function DraftListEntry({ permDraft, linkTo, backLocation }: DraftEntryProps) { const title = `${permTypeUpper} ${domain}`; + const onClick = () => { + // When clicking on a draft, direct + // to the detail view for that draft. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink domain-permission-draft entry ${permType}`} aria-label={title} title={title} - onClick={() => { - // When clicking on a draft, direct - // to the detail view for that draft. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/moderation/domain-permissions/excludes/index.tsx b/web/source/settings/views/moderation/domain-permissions/excludes/index.tsx index 915d6f5cc..207f94d61 100644 --- a/web/source/settings/views/moderation/domain-permissions/excludes/index.tsx +++ b/web/source/settings/views/moderation/domain-permissions/excludes/index.tsx @@ -186,21 +186,24 @@ function ExcludeListEntry({ permExclude, linkTo, backLocation }: ExcludeEntryPro return <ErrorC error={new Error("id was undefined")} />; } + const onClick = () => { + // When clicking on a exclude, direct + // to the detail view for that exclude. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink domain-permission-exclude entry`} aria-label={`Exclude ${domain}`} title={`Exclude ${domain}`} - onClick={() => { - // When clicking on a exclude, direct - // to the detail view for that exclude. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/moderation/domain-permissions/form.tsx b/web/source/settings/views/moderation/domain-permissions/form.tsx index 204e9510c..cf1447cfd 100644 --- a/web/source/settings/views/moderation/domain-permissions/form.tsx +++ b/web/source/settings/views/moderation/domain-permissions/form.tsx @@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React from "react"; +import React, { useRef } from "react"; import { useEffect } from "react"; import { useExportDomainListMutation } from "../../../lib/query/admin/domain-permissions/export"; @@ -70,6 +70,11 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [exportResult]); + const importFileRef = useRef<HTMLInputElement>(null); + const importFileOnClick = () => { + importFileRef.current?.click(); + }; + return ( <> <h1>Import / Export domain permissions</h1> @@ -101,7 +106,13 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp showError={false} disabled={form.permType.value === undefined || form.permType.value.length === 0} /> - <label className={`button with-icon${form.permType.value === undefined || form.permType.value.length === 0 ? " disabled" : ""}`}> + <label + className={`button with-icon${form.permType.value === undefined || form.permType.value.length === 0 ? " disabled" : ""}`} + tabIndex={0} + onClick={importFileOnClick} + onKeyDown={e => e.key === "Enter" && importFileOnClick()} + role="button" + > <i className="fa fa-fw " aria-hidden="true" /> Import file <input @@ -110,6 +121,7 @@ export default function ImportExportForm({ form, submitParse, parseResult }: Imp onChange={fileChanged} accept="application/json,text/plain,text/csv" disabled={form.permType.value === undefined || form.permType.value.length === 0} + ref={importFileRef} /> </label> <b /> {/* grid filler */} diff --git a/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx b/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx index 8668caa4b..808d03dcf 100644 --- a/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx +++ b/web/source/settings/views/moderation/domain-permissions/subscriptions/common.tsx @@ -109,21 +109,24 @@ export function SubscriptionListEntry({ permSub, linkTo, backLocation }: Subscri successfullyFetchedAtStr = new Date(successfullyFetchedAt).toDateString(); } + const onClick = () => { + // When clicking on a subscription, direct + // to the detail view for that subscription. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink domain-permission-subscription entry`} aria-label={ariaLabel} title={ariaLabel} - onClick={() => { - // When clicking on a subscription, direct - // to the detail view for that subscription. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/moderation/reports/search.tsx b/web/source/settings/views/moderation/reports/search.tsx index 0ae3ec0e0..bae0a2004 100644 --- a/web/source/settings/views/moderation/reports/search.tsx +++ b/web/source/settings/views/moderation/reports/search.tsx @@ -184,21 +184,24 @@ function ReportListEntry({ report, linkTo, backLocation }: ReportEntryProps) { const created = new Date(report.created_at).toLocaleString(); const title = `${status}. @${target.account.acct} was reported by @${from.account.acct} on ${created}. Reason: "${comment}"`; + const onClick = () => { + // When clicking on a report, direct + // to the detail view for that report. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink report entry${report.action_taken ? " resolved" : ""}`} aria-label={title} title={title} - onClick={() => { - // When clicking on a report, direct - // to the detail view for that report. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/user/applications/search.tsx b/web/source/settings/views/user/applications/search.tsx index 819d96391..afbb0d326 100644 --- a/web/source/settings/views/user/applications/search.tsx +++ b/web/source/settings/views/user/applications/search.tsx @@ -139,21 +139,24 @@ function ApplicationListEntry({ app, linkTo, backLocation }: ApplicationListEntr const created = useCreated(app); const redirectURIs = useRedirectURIs(app); + const onClick = () => { + // When clicking on an app, direct + // to the detail view for that app. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink application entry`} aria-label={`${app.name}`} title={`${app.name}`} - onClick={() => { - // When clicking on an app, direct - // to the detail view for that app. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/user/export-import/export.tsx b/web/source/settings/views/user/export-import/export.tsx index be302fec8..b4cf7355b 100644 --- a/web/source/settings/views/user/export-import/export.tsx +++ b/web/source/settings/views/user/export-import/export.tsx @@ -102,7 +102,6 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat Following {exportStats.following_count} account{ exportStats.following_count !== 1 && "s" } </span> <MutationButton - className="text-cutoff" label="Download following.csv" type="button" onClick={() => exportFollowing()} @@ -116,7 +115,6 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat Followed by {exportStats.followers_count} account{ exportStats.followers_count !== 1 && "s" } </span> <MutationButton - className="text-cutoff" label="Download followers.csv" type="button" onClick={() => exportFollowers()} @@ -130,7 +128,6 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat Created {exportStats.lists_count} list{ exportStats.lists_count !== 1 && "s" } </span> <MutationButton - className="text-cutoff" label="Download lists.csv" type="button" onClick={() => exportLists()} @@ -144,7 +141,6 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat Blocking {exportStats.blocks_count} account{ exportStats.blocks_count !== 1 && "s" } </span> <MutationButton - className="text-cutoff" label="Download blocks.csv" type="button" onClick={() => exportBlocks()} @@ -158,7 +154,6 @@ export default function Export({ exportStats }: { exportStats: AccountExportStat Muting {exportStats.mutes_count} account{ exportStats.mutes_count !== 1 && "s" } </span> <MutationButton - className="text-cutoff" label="Download mutes.csv" type="button" onClick={() => exportMutes()} diff --git a/web/source/settings/views/user/interactions/search.tsx b/web/source/settings/views/user/interactions/search.tsx index b97899c51..5d2da7563 100644 --- a/web/source/settings/views/user/interactions/search.tsx +++ b/web/source/settings/views/user/interactions/search.tsx @@ -174,21 +174,24 @@ function ReqsListEntry({ req, linkTo, backLocation }: ReqsListEntryProps) { const ourContent = useContent(req.status); const theirContent = useContent(req.reply); + const onClick = () => { + // When clicking on a request, direct + // to the detail view for that request. + setLocation(linkTo, { + // Store the back location in history so + // the detail view can use it to return to + // this page (including query parameters). + state: { backLocation: backLocation } + }); + }; + return ( <span className={`pseudolink entry interaction-request`} aria-label={label} title={label} - onClick={() => { - // When clicking on a request, direct - // to the detail view for that request. - setLocation(linkTo, { - // Store the back location in history so - // the detail view can use it to return to - // this page (including query parameters). - state: { backLocation: backLocation } - }); - }} + onClick={onClick} + onKeyDown={e => e.key === "Enter" && onClick()} role="link" tabIndex={0} > diff --git a/web/source/settings/views/user/profile/profile.tsx b/web/source/settings/views/user/profile/profile.tsx index 6f99a17db..36e307193 100644 --- a/web/source/settings/views/user/profile/profile.tsx +++ b/web/source/settings/views/user/profile/profile.tsx @@ -167,6 +167,7 @@ function ProfileForm({ data: profile }: ProfileFormProps) { <MutationButton className="delete-header-button" label="Delete header" + tabIndex={0} disabled={noHeader} result={deleteHeaderRes} onClick={(e) => { @@ -179,7 +180,7 @@ function ProfileForm({ data: profile }: ProfileFormProps) { }} /> </fieldset> - + <fieldset className="file-input-with-image-description"> <legend>Avatar</legend> <FileInput @@ -197,6 +198,7 @@ function ProfileForm({ data: profile }: ProfileFormProps) { <MutationButton className="delete-avatar-button" label="Delete avatar" + tabIndex={0} disabled={noAvatar} result={deleteAvatarRes} onClick={(e) => { |
