Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
382e3ae
feat(KeyboardShortcuts): initial commit
maxpatiiuk Jul 13, 2024
1b4d5d6
feat(KeyboardShortcuts): complete base features
maxpatiiuk Jul 13, 2024
9309bd6
refactor(KeyboardShortcuts): move code into separate folder
maxpatiiuk Jul 13, 2024
979975a
refactor(KeyboardShortcuts): use key codes over key names
maxpatiiuk Jul 13, 2024
be5b560
refactor(KeyboardShortcuts): rename from "Keyboard"
maxpatiiuk Jul 13, 2024
98269a5
fix(KeyboardShortcuts): polish UI
maxpatiiuk Jul 14, 2024
09f8a31
feat(KeyboardShortcuts): begin integrating with UI
maxpatiiuk Jul 14, 2024
ec31b19
fix(KeyboardShortcuts): correctly localize shortcuts on windows
maxpatiiuk Jul 14, 2024
83fb8a5
feat(TreeView): improve layout
maxpatiiuk Jul 21, 2024
9e41157
feat(KeyboardShortcuts): Add shortcuts for forms, tree and queries
maxpatiiuk Jul 21, 2024
7f08bdd
fix(KeyboardShortcuts): prevent adding duplicate shortcuts
maxpatiiuk Dec 25, 2024
3b2afc1
fix(KeyboardShortcuts): don't permit binding to Shift+Tab
maxpatiiuk Dec 25, 2024
139d255
feat(KeyboardShortcuts): add custom URL shortcuts
maxpatiiuk Dec 26, 2024
580584e
fix(KeyboardShortcuts): respond to review feedback
maxpatiiuk Jul 26, 2025
ea892c2
refactor(QueryBuilder): self-review code
maxpatiiuk Jul 26, 2025
db1a62f
docs(KeyboardShortcuts): add instructions on adding new shortcuts
maxpatiiuk Jul 26, 2025
408db75
build(KeyboardShortcuts): avoid cyclical import
maxpatiiuk Jul 26, 2025
186cc9f
tests(Preferences): fix localization tests
maxpatiiuk Jul 26, 2025
29e1cfd
feat(keyboard-shortcuts): merge dormant shortcut branch
grantfitzsimmons Sep 14, 2026
e093dc0
fix(keyboard-shortcuts): resolve merge conflicts
grantfitzsimmons Sep 14, 2026
49b3f3e
fix(keyboard-shortcuts): import link localization
grantfitzsimmons Sep 14, 2026
8ab203c
fix(keyboard-shortcuts): restore preference paths
grantfitzsimmons Sep 14, 2026
a4d07d6
fix(keyboard-shortcuts): restore query builder shortcuts
grantfitzsimmons Sep 14, 2026
87ade76
fix(keyboard-shortcuts): restore tree shortcuts
grantfitzsimmons Sep 14, 2026
d911d75
feat(keyboard-shortcuts): expose user tool shortcuts
grantfitzsimmons Sep 14, 2026
aa8c127
feat(keyboard-shortcuts): show user tools in preferences
grantfitzsimmons Sep 14, 2026
3bc8c77
fix(preferences): balance navigation and content columns
grantfitzsimmons Sep 14, 2026
57bbbbe
fix(keyboard-shortcuts): separate user tools from custom pages
grantfitzsimmons Sep 14, 2026
bf417ee
feat(dialogs): add close and save shortcuts
grantfitzsimmons Sep 14, 2026
b067b8b
feat(keyboard-shortcuts): configure dialog shortcuts
grantfitzsimmons Sep 14, 2026
8aa43d4
fix(keyboard-shortcuts): activate user tool URLs
grantfitzsimmons Sep 21, 2026
e5d1f65
feat(keyboard-shortcuts): add form action shortcuts
grantfitzsimmons Sep 21, 2026
bc661cc
feat(keyboard-shortcuts): include navigation menu items
grantfitzsimmons Sep 21, 2026
4489e8e
fix(keyboard-shortcuts): preserve menu navigation guards
grantfitzsimmons Sep 21, 2026
9f27c23
fix(keyboard-shortcuts): configure attachment sliders
grantfitzsimmons Sep 21, 2026
76750d7
fix(keyboard-shortcuts): preserve overlay navigation
grantfitzsimmons Sep 22, 2026
d15b9b1
fix(keyboard-shortcuts): route internal user tools in app
grantfitzsimmons Sep 22, 2026
f2c391b
test(keyboard-shortcuts): update shortcut snapshots
grantfitzsimmons Sep 22, 2026
816e411
Merge branch 'main' into issue-1746
grantfitzsimmons Sep 22, 2026
0c7c74a
fix(preferences): label keyboard shortcuts page
grantfitzsimmons Sep 22, 2026
c2c8b6b
refactor(preferences): merge keyboard shortcuts into user preferences
grantfitzsimmons Sep 22, 2026
eefade0
refactor(preferences): group shortcuts in submenu
grantfitzsimmons Sep 22, 2026
a1f40ab
Lint code with ESLint and Prettier
grantfitzsimmons Sep 22, 2026
bb855b3
Merge branch 'main' into issue-1746
grantfitzsimmons Sep 22, 2026
56438f0
feat(preferences): clarify shortcut sections
grantfitzsimmons Sep 22, 2026
0723f74
fix(preferences): separate URL shortcuts under user tools
grantfitzsimmons Sep 22, 2026
b626b50
feat(preferences): separate URL shortcut section
grantfitzsimmons Sep 22, 2026
b80080b
Lint code with ESLint and Prettier
grantfitzsimmons Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('CreateAppResource', () => {

// This is a lot more cleaner than the inner HTML
expect(getByRole('dialog').textContent).toMatchInlineSnapshot(
`"Select Resource TypeTypeDocumentationLabelDocumentationReportDocumentationDefault User PreferencesDocumentationLeaflet LayersDocumentationRSS Export FeedDocumentationExpress Search ConfigDocumentationType SearchesDocumentationWeb LinksDocumentationField FormattersDocumentationRecord FormattersDocumentationData Entry TablesDocumentationInteractions TablesDocumentationOther XML ResourceOther JSON ResourceOther Properties ResourceOther ResourceCancel"`
`"Select Resource TypeTypeDocumentationLabelDocumentation(opens in a new tab)ReportDocumentation(opens in a new tab)Default User PreferencesDocumentation(opens in a new tab)Leaflet LayersDocumentation(opens in a new tab)RSS Export FeedDocumentation(opens in a new tab)Express Search ConfigDocumentation(opens in a new tab)Type SearchesDocumentation(opens in a new tab)Web LinksDocumentation(opens in a new tab)Field FormattersDocumentation(opens in a new tab)Record FormattersDocumentation(opens in a new tab)Data Entry TablesDocumentation(opens in a new tab)Interactions TablesDocumentation(opens in a new tab)Other XML ResourceOther JSON ResourceOther Properties ResourceOther ResourceCancel"`
);
});

Expand Down
68 changes: 53 additions & 15 deletions specifyweb/frontend/js_src/lib/components/Atoms/DataEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import type { LocalizedString } from 'typesafe-i18n';
import { commonText } from '../../localization/common';
import { formsText } from '../../localization/forms';
import type { RA } from '../../utils/types';
import { localized } from '../../utils/types';
import type { AnySchema } from '../DataModel/helperTypes';
import type { SpecifyResource } from '../DataModel/legacyTypes';
import type { ViewDescription } from '../FormParse';
import type { cellAlign, cellVerticalAlign } from '../FormParse/cells';
import { userPreferences } from '../Preferences/userPreferences';
import { Button } from './Button';
import { className } from './className';
import type { icons } from './Icons';
Expand All @@ -19,23 +21,24 @@ const dataEntryButton = (
className: string,
title: LocalizedString,
icon: keyof typeof icons
) =>
function (
) => {
const component = (
props: Omit<TagProps<'button'>, 'children' | 'type'> & {
readonly onClick:
| ((event: React.MouseEvent<HTMLButtonElement>) => void)
| undefined;
}
): JSX.Element {
return (
<Button.Icon
className={`${className} ${props.className ?? ''}`}
icon={icon}
title={title}
{...props}
/>
);
};
): JSX.Element => (
<Button.Icon
className={`${className} ${props.className ?? ''}`}
icon={icon}
title={title}
{...props}
/>
);
Object.defineProperty(component, 'name', { value: icon });
return component;
};

export const columnDefinitionsToCss = (
columns: RA<number | undefined>,
Expand All @@ -54,6 +57,12 @@ export const columnDefinitionsToCss = (
* This is called DataEntry instead of Form because "Form" is already taken
*/

const DataEntryAdd = dataEntryButton(
className.dataEntryAdd,
commonText.add(),
'plus'
);

export const DataEntry = {
Grid: wrap<
'div',
Expand Down Expand Up @@ -145,7 +154,29 @@ export const DataEntry = {
})
),
SubFormTitle: wrap('DataEntry.SubFormTitle', 'h3', className.formTitle),
Add: dataEntryButton(className.dataEntryAdd, commonText.add(), 'plus'),
Add({
enableShortcut,
onClick: handleClick,
title = commonText.add(),
...rest
}: Omit<Parameters<typeof DataEntryAdd>[0], 'onClick'> & {
readonly onClick: (() => void) | undefined;
readonly enableShortcut: boolean;
}): JSX.Element {
const addButtonShortcut = userPreferences.useKeyboardShortcut(
'form',
'recordSet',
'addResource',
enableShortcut && rest.disabled !== true ? handleClick : undefined
);
return (
<DataEntryAdd
title={`${title}${addButtonShortcut}`}
onClick={handleClick}
{...rest}
/>
);
},
View: dataEntryButton(className.dataEntryView, commonText.view(), 'eye'),
Edit: dataEntryButton(className.dataEntryEdit, commonText.edit(), 'pencil'),
Clone: dataEntryButton(
Expand All @@ -170,14 +201,21 @@ export const DataEntry = {
readonly className?: string;
readonly resource: SpecifyResource<AnySchema> | undefined;
}): JSX.Element | null {
const ref = React.useRef<HTMLAnchorElement>(null);
const keyboardShortcut = userPreferences.useKeyboardShortcut(
'form',
'dialogs',
'openRelatedRecordInNewTab',
resource === undefined ? undefined : (): void => ref.current?.click()
);
return typeof resource === 'object' && !resource.isNew() ? (
<Link.NewTab
aria-label={commonText.openInNewTab()}
className={`${className.dataEntryVisit} ${localClassName}`}
forwardRef={ref}
href={resource.viewUrl()}
title={commonText.openInNewTab()}
title={localized(`${commonText.openInNewTab()}${keyboardShortcut}`)}
/>
) : null;
},
};
/* eslint-enable @typescript-eslint/naming-convention */
10 changes: 4 additions & 6 deletions specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const icons = {
collection: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" /></svg>,
cube: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M11 17a1 1 0 001.447.894l4-2A1 1 0 0017 15V9.236a1 1 0 00-1.447-.894l-4 2a1 1 0 00-.553.894V17zM15.211 6.276a1 1 0 000-1.788l-4.764-2.382a1 1 0 00-.894 0L4.789 4.488a1 1 0 000 1.788l4.764 2.382a1 1 0 00.894 0l4.764-2.382zM4.447 8.342A1 1 0 003 9.236V15a1 1 0 00.553.894l4 2A1 1 0 009 17v-5.764a1 1 0 00-.553-.894l-4-2z" /></svg>,
cubeTransparent: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M9.504 1.132a1 1 0 01.992 0l1.75 1a1 1 0 11-.992 1.736L10 3.152l-1.254.716a1 1 0 11-.992-1.736l1.75-1zM5.618 4.504a1 1 0 01-.372 1.364L5.016 6l.23.132a1 1 0 11-.992 1.736L4 7.723V8a1 1 0 01-2 0V6a.996.996 0 01.52-.878l1.734-.99a1 1 0 011.364.372zm8.764 0a1 1 0 011.364-.372l1.733.99A1.002 1.002 0 0118 6v2a1 1 0 11-2 0v-.277l-.254.145a1 1 0 11-.992-1.736l.23-.132-.23-.132a1 1 0 01-.372-1.364zm-7 4a1 1 0 011.364-.372L10 8.848l1.254-.716a1 1 0 11.992 1.736L11 10.58V12a1 1 0 11-2 0v-1.42l-1.246-.712a1 1 0 01-.372-1.364zM3 11a1 1 0 011 1v1.42l1.246.712a1 1 0 11-.992 1.736l-1.75-1A1 1 0 012 14v-2a1 1 0 011-1zm14 0a1 1 0 011 1v2a1 1 0 01-.504.868l-1.75 1a1 1 0 11-.992-1.736L16 13.42V12a1 1 0 011-1zm-9.618 5.504a1 1 0 011.364-.372l.254.145V16a1 1 0 112 0v.277l.254-.145a1 1 0 11.992 1.736l-1.735.992a.995.995 0 01-1.022 0l-1.735-.992a1 1 0 01-.372-1.364z" fillRule="evenodd" /></svg>,
cursorClick: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M6.672 1.911a1 1 0 10-1.932.518l.259.966a1 1 0 001.932-.518l-.26-.966zM2.429 4.74a1 1 0 10-.517 1.932l.966.259a1 1 0 00.517-1.932l-.966-.26zm8.814-.569a1 1 0 00-1.415-1.414l-.707.707a1 1 0 101.415 1.415l.707-.708zm-7.071 7.072l.707-.707A1 1 0 003.465 9.12l-.708.707a1 1 0 001.415 1.415zm3.2-5.171a1 1 0 00-1.3 1.3l4 10a1 1 0 001.823.075l1.38-2.759 3.018 3.02a1 1 0 001.414-1.415l-3.019-3.02 2.76-1.379a1 1 0 00-.076-1.822l-10-4z" fillRule="evenodd" /></svg>,
database: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M3 12v3c0 1.657 3.134 3 7 3s7-1.343 7-3v-3c0 1.657-3.134 3-7 3s-7-1.343-7-3z" /><path d="M3 7v3c0 1.657 3.134 3 7 3s7-1.343 7-3V7c0 1.657-3.134 3-7 3S3 8.657 3 7z" /><path d="M17 5c0 1.657-3.134 3-7 3S3 6.657 3 5s3.134-3 7-3 7 1.343 7 3z" /></svg>,
document: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" fillRule="evenodd" /></svg>,
documentReport: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm2 10a1 1 0 10-2 0v3a1 1 0 102 0v-3zm2-3a1 1 0 011 1v5a1 1 0 11-2 0v-5a1 1 0 011-1zm4-1a1 1 0 10-2 0v7a1 1 0 102 0V8z" fillRule="evenodd" /></svg>,
Expand All @@ -86,13 +87,10 @@ export const icons = {
exclamationCircle: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" fillRule="evenodd" /></svg>,
externalLink: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" /><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" /></svg>,
eye: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z" /><path clipRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" fillRule="evenodd" /></svg>,
filter: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" fillRule="evenodd" /></svg>,
fingerPrint: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M6.625 2.655A9 9 0 0119 11a1 1 0 11-2 0 7 7 0 00-9.625-6.492 1 1 0 11-.75-1.853zM4.662 4.959A1 1 0 014.75 6.37 6.97 6.97 0 003 11a1 1 0 11-2 0 8.97 8.97 0 012.25-5.953 1 1 0 011.412-.088z" fillRule="evenodd" /><path clipRule="evenodd" d="M5 11a5 5 0 1110 0 1 1 0 11-2 0 3 3 0 10-6 0c0 1.677-.345 3.276-.968 4.729a1 1 0 11-1.838-.789A9.964 9.964 0 005 11zm8.921 2.012a1 1 0 01.831 1.145 19.86 19.86 0 01-.545 2.436 1 1 0 11-1.92-.558c.207-.713.371-1.445.49-2.192a1 1 0 011.144-.83z" fillRule="evenodd" /><path clipRule="evenodd" d="M10 10a1 1 0 011 1c0 2.236-.46 4.368-1.29 6.304a1 1 0 01-1.838-.789A13.952 13.952 0 009 11a1 1 0 011-1z" fillRule="evenodd" /></svg>,
gallery:<svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clipRule="evenodd" d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" fillRule="evenodd" />
</svg>,
globe: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clipRule="evenodd" d="M4.083 9h1.946c.089-1.546.383-2.97.837-4.118A6.004 6.004 0 004.083 9zM10 2a8 8 0 100 16 8 8 0 000-16zm0 2c-.076 0-.232.032-.465.262-.238.234-.497.623-.737 1.182-.389.907-.673 2.142-.766 3.556h3.936c-.093-1.414-.377-2.649-.766-3.556-.24-.56-.5-.948-.737-1.182C10.232 4.032 10.076 4 10 4zm3.971 5c-.089-1.546-.383-2.97-.837-4.118A6.004 6.004 0 0115.917 9h-1.946zm-2.003 2H8.032c.093 1.414.377 2.649.766 3.556.24.56.5.948.737 1.182.233.23.389.262.465.262.076 0 .232-.032.465-.262.238-.234.498-.623.737-1.182.389-.907.673-2.142.766-3.556zm1.166 4.118c.454-1.147.748-2.572.837-4.118h1.946a6.004 6.004 0 01-2.783 4.118zm-6.268 0C6.412 13.97 6.118 12.546 6.03 11H4.083a6.004 6.004 0 002.783 4.118z" fillRule="evenodd" />
</svg>,
gallery:<svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" fillRule="evenodd" /></svg>,
globe: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M4.083 9h1.946c.089-1.546.383-2.97.837-4.118A6.004 6.004 0 004.083 9zM10 2a8 8 0 100 16 8 8 0 000-16zm0 2c-.076 0-.232.032-.465.262-.238.234-.497.623-.737 1.182-.389.907-.673 2.142-.766 3.556h3.936c-.093-1.414-.377-2.649-.766-3.556-.24-.56-.5-.948-.737-1.182C10.232 4.032 10.076 4 10 4zm3.971 5c-.089-1.546-.383-2.97-.837-4.118A6.004 6.004 0 0115.917 9h-1.946zm-2.003 2H8.032c.093 1.414.377 2.649.766 3.556.24.56.5.948.737 1.182.233.23.389.262.465.262.076 0 .232-.032.465-.262.238-.234.498-.623.737-1.182.389-.907.673-2.142.766-3.556zm1.166 4.118c.454-1.147.748-2.572.837-4.118h1.946a6.004 6.004 0 01-2.783 4.118zm-6.268 0C6.412 13.97 6.118 12.546 6.03 11H4.083a6.004 6.004 0 002.783 4.118z" fillRule="evenodd" /></svg>,
hashtag: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="M9.243 3.03a1 1 0 01.727 1.213L9.53 6h2.94l.56-2.243a1 1 0 111.94.486L14.53 6H17a1 1 0 110 2h-2.97l-1 4H15a1 1 0 110 2h-2.47l-.56 2.242a1 1 0 11-1.94-.485L10.47 14H7.53l-.56 2.242a1 1 0 11-1.94-.485L5.47 14H3a1 1 0 110-2h2.97l1-4H5a1 1 0 110-2h2.47l.56-2.243a1 1 0 011.213-.727zM9.03 8l-1 4h2.938l1-4H9.031z" fillRule="evenodd" /></svg>,
// This icon is not from Heroicons. It was drawn by @grantfitzsimmons
history: <svg aria-hidden className={iconClassName} fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.043,6.984c-0.382-0.935-0.942-1.753-1.665-2.433c-0.767-0.722-1.572-1.249-2.459-1.611 c-0.911-0.391-1.94-0.597-2.979-0.597c-1.147,0-2.207,0.251-3.239,0.768C5.87,3.526,5.136,4.05,4.512,4.671V3.746 c0-0.47-0.397-0.867-0.867-0.867c-0.486,0-0.867,0.381-0.867,0.867v2.866c0,0.577,0.469,1.046,1.045,1.046h2.825 c0.51,0,0.909-0.399,0.909-0.908c0-0.471-0.355-0.826-0.826-0.826H5.752C6.191,5.5,6.688,5.131,7.258,4.802 c0.797-0.46,1.688-0.71,2.579-0.723c1.683-0.052,3.085,0.532,4.291,1.698c1.178,1.139,1.775,2.532,1.775,4.141 c0,1.729-0.555,3.125-1.696,4.267c-1.18,1.18-2.616,1.778-4.267,1.778c-1.647,0-3.016-0.568-4.181-1.733 c-0.983-1.022-1.542-2.259-1.661-3.685C4.041,10.084,3.677,9.75,3.233,9.75c-0.257,0-0.499,0.104-0.662,0.288 c-0.158,0.176-0.23,0.406-0.205,0.638c0.127,1.823,0.858,3.413,2.171,4.727c1.496,1.495,3.313,2.254,5.402,2.254 c0.977,0,1.947-0.18,2.983-0.558c0.977-0.443,1.805-1.001,2.46-1.655c0.74-0.739,1.282-1.547,1.659-2.469 c0.395-0.966,0.595-1.979,0.595-3.016C17.638,8.937,17.443,7.964,17.043,6.984z"/> <path d="M10.022,5.595H9.94c-0.426,0-0.785,0.359-0.785,0.784v3.333c0,0.411,0.158,0.796,0.445,1.084l2.345,2.303 c0.175,0.175,0.402,0.264,0.637,0.264c0.2,0,0.405-0.064,0.587-0.194c0.258-0.181,0.328-0.4,0.341-0.553 c0.021-0.251-0.09-0.519-0.31-0.737l-2.31-2.231V6.462C10.89,5.976,10.509,5.595,10.022,5.595z"/></svg>,
Expand Down
11 changes: 10 additions & 1 deletion specifyweb/frontend/js_src/lib/components/Atoms/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import type { LocalizedString } from 'typesafe-i18n';

import { commonText } from '../../localization/common';
import type { IR, RA, RR } from '../../utils/types';
import { className } from './className';
import type { IconProps } from './Icons';
Expand Down Expand Up @@ -46,7 +47,15 @@ export const Link = {
children: (
<>
{props.children}
<span aria-hidden>{icons.externalLink}</span>
<span
title={
(props.children === undefined ? props.title : undefined) ??
commonText.opensInNewTab()
}
>
<span className="sr-only">{commonText.opensInNewTab()}</span>
{icons.externalLink}
</span>
</>
),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ snapshot(DataEntry.Footer, { children: 'Test' });
snapshot(DataEntry.SubForm, { children: 'Test' });
snapshot(DataEntry.SubFormHeader, { children: 'Test' });
snapshot(DataEntry.SubFormTitle, { children: 'Test' });
snapshot(DataEntry.Add, { onClick: f.never });
snapshot(DataEntry.Add, { onClick: f.never, enableShortcut: true });
snapshot(DataEntry.View, { onClick: f.never });
snapshot(DataEntry.Edit, { onClick: f.never });
snapshot(DataEntry.Clone, { onClick: f.never });
Expand Down
Loading
Loading