From b9bf2e58cbfa46e972012ed5bdadf3460e455da7 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 1 Oct 2022 13:39:45 +0200 Subject: [PATCH] feat: Add cursor style user preference (#4199) * feat: Add cursor style user preference * Remove headings for now --- app/components/Breadcrumb.tsx | 1 + app/components/Button.tsx | 2 +- app/components/CommandBarItem.tsx | 2 +- app/components/ContextMenu/MenuItem.tsx | 2 +- app/components/DocumentCard.tsx | 1 + app/components/DocumentListItem.tsx | 1 + app/components/Facepile.tsx | 2 +- app/components/GroupListItem.tsx | 2 +- app/components/Header.tsx | 2 +- app/components/HoverPreviewDocument.tsx | 2 +- app/components/InputSelect.tsx | 2 +- app/components/List/Item.tsx | 2 +- app/components/NudeButton.tsx | 2 +- app/components/SearchListItem.tsx | 1 + app/components/Sidebar/components/Header.tsx | 2 +- .../Sidebar/components/SidebarButton.tsx | 2 +- app/components/Sidebar/components/SidebarLink.tsx | 2 +- app/components/Sidebar/components/Toggle.tsx | 2 +- app/components/Switch.tsx | 2 +- app/components/Theme.tsx | 6 ++++-- app/editor/components/BlockMenuItem.tsx | 2 +- app/editor/components/LinkSearchResult.tsx | 2 +- app/editor/components/ToolbarButton.tsx | 2 +- app/models/User.ts | 4 +++- .../Document/components/ReferenceListItem.tsx | 1 + app/scenes/Search/components/RecentSearches.tsx | 1 + app/scenes/Settings/Preferences.tsx | 14 ++++++++++++++ app/scenes/Settings/components/DropToImport.tsx | 2 +- app/scenes/Settings/components/ImageInput.tsx | 2 +- app/scenes/Settings/components/ImageUpload.tsx | 4 ++-- app/scenes/Settings/components/UserListItem.tsx | 2 +- server/routes/api/users.ts | 12 ++++++------ shared/editor/components/Styles.ts | 8 ++++---- shared/editor/nodes/Image.tsx | 2 +- shared/i18n/locales/en_US/translation.json | 2 ++ shared/styles/globals.ts | 5 ++++- shared/types.ts | 1 + 37 files changed, 68 insertions(+), 38 deletions(-) diff --git a/app/components/Breadcrumb.tsx b/app/components/Breadcrumb.tsx index d6c8bc324b..8b4e9a2d4a 100644 --- a/app/components/Breadcrumb.tsx +++ b/app/components/Breadcrumb.tsx @@ -67,6 +67,7 @@ const Item = styled(Link)<{ $highlight: boolean; $withIcon: boolean }>` display: flex; flex-shrink: 1; min-width: 0; + cursor: var(--pointer); color: ${(props) => props.theme.text}; font-size: 15px; height: 24px; diff --git a/app/components/Button.tsx b/app/components/Button.tsx index 02515f1010..1289b6e5cb 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -30,7 +30,7 @@ const RealButton = styled(ActionButton)` height: 32px; text-decoration: none; flex-shrink: 0; - cursor: pointer; + cursor: var(--pointer); user-select: none; appearance: none !important; diff --git a/app/components/CommandBarItem.tsx b/app/components/CommandBarItem.tsx index 1f22412e1b..c205e25a7a 100644 --- a/app/components/CommandBarItem.tsx +++ b/app/components/CommandBarItem.tsx @@ -98,7 +98,7 @@ const Item = styled.div<{ active?: boolean }>` display: flex; align-items: center; justify-content: space-between; - cursor: pointer; + cursor: var(--pointer); text-overflow: ellipsis; white-space: nowrap; diff --git a/app/components/ContextMenu/MenuItem.tsx b/app/components/ContextMenu/MenuItem.tsx index 0618bc3651..681be9e5da 100644 --- a/app/components/ContextMenu/MenuItem.tsx +++ b/app/components/ContextMenu/MenuItem.tsx @@ -139,7 +139,7 @@ export const MenuAnchorCSS = css` color: ${props.theme.white}; background: ${props.dangerous ? props.theme.danger : props.theme.primary}; box-shadow: none; - cursor: pointer; + cursor: var(--pointer); svg { fill: ${props.theme.white}; diff --git a/app/components/DocumentCard.tsx b/app/components/DocumentCard.tsx index 98f0ec84b7..da5fd872c1 100644 --- a/app/components/DocumentCard.tsx +++ b/app/components/DocumentCard.tsx @@ -236,6 +236,7 @@ const DocumentLink = styled(Link)<{ width: 100%; height: 100%; border-radius: 8px; + cursor: var(--pointer); background: ${(props) => props.theme.background}; transition: transform 50ms ease-in-out; border: 1px solid ${(props) => props.theme.inputBorder}; diff --git a/app/components/DocumentListItem.tsx b/app/components/DocumentListItem.tsx index b043e12137..03cf235692 100644 --- a/app/components/DocumentListItem.tsx +++ b/app/components/DocumentListItem.tsx @@ -201,6 +201,7 @@ const DocumentLink = styled(Link)<{ border-radius: 8px; max-height: 50vh; width: calc(100vw - 8px); + cursor: var(--pointer); &:focus-visible { outline: none; diff --git a/app/components/Facepile.tsx b/app/components/Facepile.tsx index 808bb9e397..a3e117edec 100644 --- a/app/components/Facepile.tsx +++ b/app/components/Facepile.tsx @@ -69,7 +69,7 @@ const More = styled.div<{ size: number }>` const Avatars = styled(Flex)` align-items: center; flex-direction: row-reverse; - cursor: pointer; + cursor: var(--pointer); `; export default observer(Facepile); diff --git a/app/components/GroupListItem.tsx b/app/components/GroupListItem.tsx index 350b054d49..a4ceaf9bd7 100644 --- a/app/components/GroupListItem.tsx +++ b/app/components/GroupListItem.tsx @@ -102,7 +102,7 @@ const Image = styled(Flex)` const Title = styled.span` &:hover { text-decoration: underline; - cursor: pointer; + cursor: var(--pointer); } `; diff --git a/app/components/Header.tsx b/app/components/Header.tsx index f5ea22d280..3822c7739e 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -143,7 +143,7 @@ const Title = styled("div")` text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - cursor: pointer; + cursor: var(--pointer); min-width: 0; ${breakpoint("tablet")` diff --git a/app/components/HoverPreviewDocument.tsx b/app/components/HoverPreviewDocument.tsx index 453bb750f8..dfc2bb0323 100644 --- a/app/components/HoverPreviewDocument.tsx +++ b/app/components/HoverPreviewDocument.tsx @@ -50,7 +50,7 @@ function HoverPreviewDocument({ url, children }: Props) { } const Content = styled(Link)` - cursor: pointer; + cursor: var(--pointer); `; const Heading = styled.h2` diff --git a/app/components/InputSelect.tsx b/app/components/InputSelect.tsx index 7890c94e08..7bbf307020 100644 --- a/app/components/InputSelect.tsx +++ b/app/components/InputSelect.tsx @@ -288,7 +288,7 @@ const Positioner = styled(Position)` color: ${(props) => props.theme.white}; background: ${(props) => props.theme.primary}; box-shadow: none; - cursor: pointer; + cursor: var(--pointer); svg { fill: ${(props) => props.theme.white}; diff --git a/app/components/List/Item.tsx b/app/components/List/Item.tsx index 25c86e34f2..f34853e2a7 100644 --- a/app/components/List/Item.tsx +++ b/app/components/List/Item.tsx @@ -87,7 +87,7 @@ const Wrapper = styled.a<{ border-bottom: 0; } - cursor: ${({ to }) => (to ? "pointer" : "default")}; + cursor: ${({ to }) => (to ? "var(--pointer)" : "default")}; `; const Image = styled(Flex)` diff --git a/app/components/NudeButton.tsx b/app/components/NudeButton.tsx index c9917adc7b..ea87887dc2 100644 --- a/app/components/NudeButton.tsx +++ b/app/components/NudeButton.tsx @@ -27,7 +27,7 @@ const NudeButton = styled(ActionButton).attrs((props: Props) => ({ line-height: 0; border: 0; padding: 0; - cursor: pointer; + cursor: var(--pointer); user-select: none; color: inherit; `; diff --git a/app/components/SearchListItem.tsx b/app/components/SearchListItem.tsx index 14f4c045e6..1f47144b96 100644 --- a/app/components/SearchListItem.tsx +++ b/app/components/SearchListItem.tsx @@ -83,6 +83,7 @@ const DocumentLink = styled(Link)<{ align-items: center; padding: 6px 12px; max-height: 50vh; + cursor: var(--pointer); &:not(:last-child) { margin-bottom: 4px; diff --git a/app/components/Sidebar/components/Header.tsx b/app/components/Sidebar/components/Header.tsx index c5f84a4999..e24a2b2c0f 100644 --- a/app/components/Sidebar/components/Header.tsx +++ b/app/components/Sidebar/components/Header.tsx @@ -80,7 +80,7 @@ const Button = styled.button` &:not(:disabled):hover, &:not(:disabled):active { color: ${(props) => props.theme.textSecondary}; - cursor: pointer; + cursor: var(--pointer); } `; diff --git a/app/components/Sidebar/components/SidebarButton.tsx b/app/components/Sidebar/components/SidebarButton.tsx index a23e0e87c3..17abf91ec7 100644 --- a/app/components/Sidebar/components/SidebarButton.tsx +++ b/app/components/Sidebar/components/SidebarButton.tsx @@ -68,7 +68,7 @@ const Wrapper = styled(Flex)<{ minHeight: number }>` text-align: left; overflow: hidden; user-select: none; - cursor: pointer; + cursor: var(--pointer); &:active, &:hover, diff --git a/app/components/Sidebar/components/SidebarLink.tsx b/app/components/Sidebar/components/SidebarLink.tsx index 1b4fc722f6..01fea9e02d 100644 --- a/app/components/Sidebar/components/SidebarLink.tsx +++ b/app/components/Sidebar/components/SidebarLink.tsx @@ -179,7 +179,7 @@ const Link = styled(NavLink)<{ color: ${(props) => props.$isActiveDrop ? props.theme.white : props.theme.sidebarText}; font-size: 16px; - cursor: pointer; + cursor: var(--pointer); overflow: hidden; ${(props) => diff --git a/app/components/Sidebar/components/Toggle.tsx b/app/components/Sidebar/components/Toggle.tsx index 965ac12734..a57abd538d 100644 --- a/app/components/Sidebar/components/Toggle.tsx +++ b/app/components/Sidebar/components/Toggle.tsx @@ -47,7 +47,7 @@ export const ToggleButton = styled.button<{ $direction?: "left" | "right" }>` ${breakpoint("tablet")` pointer-events: all; - cursor: pointer; + cursor: var(--pointer); `} @media (hover: none) { diff --git a/app/components/Switch.tsx b/app/components/Switch.tsx index 7e45b268ad..1b34bcd36b 100644 --- a/app/components/Switch.tsx +++ b/app/components/Switch.tsx @@ -86,7 +86,7 @@ const Input = styled.label<{ width: number; height: number }>` const Slider = styled.span<{ width: number; height: number }>` position: absolute; - cursor: pointer; + cursor: var(--pointer); top: 0; left: 0; right: 0; diff --git a/app/components/Theme.tsx b/app/components/Theme.tsx index 19bb85a85e..07d95af94a 100644 --- a/app/components/Theme.tsx +++ b/app/components/Theme.tsx @@ -8,7 +8,7 @@ import useMediaQuery from "~/hooks/useMediaQuery"; import useStores from "~/hooks/useStores"; const Theme: React.FC = ({ children }) => { - const { ui } = useStores(); + const { auth, ui } = useStores(); const resolvedTheme = ui.resolvedTheme === "dark" ? dark : light; const resolvedMobileTheme = ui.resolvedTheme === "dark" ? darkMobile : lightMobile; @@ -27,7 +27,9 @@ const Theme: React.FC = ({ children }) => { return ( <> - + {children} diff --git a/app/editor/components/BlockMenuItem.tsx b/app/editor/components/BlockMenuItem.tsx index 8d897687f0..ca886a34f4 100644 --- a/app/editor/components/BlockMenuItem.tsx +++ b/app/editor/components/BlockMenuItem.tsx @@ -76,7 +76,7 @@ const MenuItem = styled.button<{ line-height: 1; width: 100%; height: 36px; - cursor: pointer; + cursor: var(--pointer); border: none; opacity: ${(props) => (props.disabled ? ".5" : "1")}; color: ${(props) => diff --git a/app/editor/components/LinkSearchResult.tsx b/app/editor/components/LinkSearchResult.tsx index 04e10724f4..3253cc1df7 100644 --- a/app/editor/components/LinkSearchResult.tsx +++ b/app/editor/components/LinkSearchResult.tsx @@ -61,7 +61,7 @@ const ListItem = styled.li<{ text-decoration: none; overflow: hidden; white-space: nowrap; - cursor: pointer; + cursor: var(--pointer); user-select: none; line-height: ${(props) => (props.compact ? "inherit" : "1.2")}; height: ${(props) => (props.compact ? "28px" : "auto")}; diff --git a/app/editor/components/ToolbarButton.tsx b/app/editor/components/ToolbarButton.tsx index e1f084c4cc..7c059bbe82 100644 --- a/app/editor/components/ToolbarButton.tsx +++ b/app/editor/components/ToolbarButton.tsx @@ -7,7 +7,7 @@ export default styled.button` flex: 0; width: 24px; height: 24px; - cursor: pointer; + cursor: var(--pointer); border: none; background: none; transition: opacity 100ms ease-in-out; diff --git a/app/models/User.ts b/app/models/User.ts index 8a32069c6e..5fa7936e39 100644 --- a/app/models/User.ts +++ b/app/models/User.ts @@ -26,7 +26,9 @@ class User extends ParanoidModel { @observable language: string; - preferences: UserPreferences | null | undefined; + @Field + @observable + preferences: UserPreferences | null; email: string; diff --git a/app/scenes/Document/components/ReferenceListItem.tsx b/app/scenes/Document/components/ReferenceListItem.tsx index 808b78781a..6a69f54195 100644 --- a/app/scenes/Document/components/ReferenceListItem.tsx +++ b/app/scenes/Document/components/ReferenceListItem.tsx @@ -27,6 +27,7 @@ const DocumentLink = styled(Link)` min-width: 100%; overflow: hidden; position: relative; + cursor: var(--pointer); &:${hover}, &:active, diff --git a/app/scenes/Search/components/RecentSearches.tsx b/app/scenes/Search/components/RecentSearches.tsx index c33ff51c64..ccf175ccc2 100644 --- a/app/scenes/Search/components/RecentSearches.tsx +++ b/app/scenes/Search/components/RecentSearches.tsx @@ -89,6 +89,7 @@ const RecentSearch = styled(Link)` display: flex; justify-content: space-between; color: ${(props) => props.theme.textSecondary}; + cursor: var(--pointer); padding: 1px 4px; border-radius: 4px; diff --git a/app/scenes/Settings/Preferences.tsx b/app/scenes/Settings/Preferences.tsx index c30a0a5e4c..d43ff76d48 100644 --- a/app/scenes/Settings/Preferences.tsx +++ b/app/scenes/Settings/Preferences.tsx @@ -83,6 +83,20 @@ function Preferences() { ariaLabel={t("Language")} /> + + + (props.$disabled ? 0.5 : 1)}; &:hover { diff --git a/app/scenes/Settings/components/ImageInput.tsx b/app/scenes/Settings/components/ImageInput.tsx index d875eb3a3b..7b663b21b4 100644 --- a/app/scenes/Settings/components/ImageInput.tsx +++ b/app/scenes/Settings/components/ImageInput.tsx @@ -49,7 +49,7 @@ const ImageBox = styled(Flex)` bottom: 0; left: 0; opacity: 0; - cursor: pointer; + cursor: var(--pointer); transition: all 250ms; } diff --git a/app/scenes/Settings/components/ImageUpload.tsx b/app/scenes/Settings/components/ImageUpload.tsx index 696149b327..90b4806c70 100644 --- a/app/scenes/Settings/components/ImageUpload.tsx +++ b/app/scenes/Settings/components/ImageUpload.tsx @@ -158,7 +158,7 @@ const RangeInput = styled.input` width: 300px; margin-bottom: 30px; height: 4px; - cursor: pointer; + cursor: var(--pointer); color: inherit; border-radius: 99999px; background-color: #dee1e3; @@ -170,7 +170,7 @@ const RangeInput = styled.input` width: 16px; border-radius: 50%; background: ${(props) => props.theme.text}; - cursor: pointer; + cursor: var(--pointer); } &:focus { diff --git a/app/scenes/Settings/components/UserListItem.tsx b/app/scenes/Settings/components/UserListItem.tsx index a686b0f5b4..45d707cef6 100644 --- a/app/scenes/Settings/components/UserListItem.tsx +++ b/app/scenes/Settings/components/UserListItem.tsx @@ -43,7 +43,7 @@ const UserListItem = ({ user, showMenu }: Props) => { const Title = styled.span` &:hover { text-decoration: underline; - cursor: pointer; + cursor: var(--pointer); } `; diff --git a/server/routes/api/users.ts b/server/routes/api/users.ts index cc39744394..2f863a20a4 100644 --- a/server/routes/api/users.ts +++ b/server/routes/api/users.ts @@ -190,12 +190,12 @@ router.post("users.update", auth(), async (ctx) => { } if (preferences) { assertKeysIn(preferences, UserPreference); - if (has(preferences, UserPreference.RememberLastPath)) { - assertBoolean(preferences.rememberLastPath); - user.setPreference( - UserPreference.RememberLastPath, - preferences.rememberLastPath - ); + + for (const value of Object.values(UserPreference)) { + if (has(preferences, value)) { + assertBoolean(preferences[value]); + user.setPreference(value, preferences[value]); + } } } await user.save(); diff --git a/shared/editor/components/Styles.ts b/shared/editor/components/Styles.ts index 27c693bc10..2876d2abb3 100644 --- a/shared/editor/components/Styles.ts +++ b/shared/editor/components/Styles.ts @@ -347,7 +347,7 @@ h6:not(.placeholder):before { display: inline-block; color: ${props.theme.text}; opacity: .75; - cursor: pointer; + cursor: var(--pointer); background: none; outline: none; border: 0; @@ -673,7 +673,7 @@ ul.checkbox_list li::before { ul.checkbox_list li .checkbox { display: inline-block; - cursor: pointer; + cursor: var(--pointer); pointer-events: ${ props.readOnly && !props.readOnlyWriteCheckboxes ? "none" : "initial" }; @@ -797,7 +797,7 @@ mark { font-weight: 500; text-decoration: none; flex-shrink: 0; - cursor: pointer; + cursor: var(--pointer); user-select: none; appearance: none !important; padding: 6px 8px; @@ -1230,7 +1230,7 @@ table { &:hover, &:focus { - cursor: pointer; + cursor: var(--pointer); color: ${props.theme.text}; background: ${props.theme.secondaryBackground}; } diff --git a/shared/editor/nodes/Image.tsx b/shared/editor/nodes/Image.tsx index b350e86370..7b4bdd37ea 100644 --- a/shared/editor/nodes/Image.tsx +++ b/shared/editor/nodes/Image.tsx @@ -551,7 +551,7 @@ const Button = styled.button` width: 24px; height: 24px; display: inline-block; - cursor: pointer; + cursor: var(--pointer); opacity: 0; transition: opacity 100ms ease-in-out; diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index ff36085283..989b7700a3 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -696,6 +696,8 @@ "Delete account": "Delete account", "Language": "Language", "Choose the interface language. Community translations are accepted though our <2>translation portal.": "Choose the interface language. Community translations are accepted though our <2>translation portal.", + "Use pointer cursor": "Use pointer cursor", + "Show a hand cursor when hovering over interactive elements.": "Show a hand cursor when hovering over interactive elements.", "Remember previous location": "Remember previous location", "Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.", "You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable", diff --git a/shared/styles/globals.ts b/shared/styles/globals.ts index 6761d8c43c..b2a2f8d1dc 100644 --- a/shared/styles/globals.ts +++ b/shared/styles/globals.ts @@ -2,7 +2,9 @@ import { createGlobalStyle } from "styled-components"; import styledNormalize from "styled-normalize"; import { breakpoints, depths } from "."; -export default createGlobalStyle` +type Props = { useCursorPointer?: boolean }; + +export default createGlobalStyle` ${styledNormalize} * { @@ -17,6 +19,7 @@ export default createGlobalStyle` padding: 0; print-color-adjust: exact; -webkit-print-color-adjust: exact; + --pointer: ${(props) => (props.useCursorPointer ? "pointer" : "default")}; } body, diff --git a/shared/types.ts b/shared/types.ts index ac8c700220..f2cfae6f1b 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -46,6 +46,7 @@ export type IntegrationSettings = T extends IntegrationType.Embed export enum UserPreference { RememberLastPath = "rememberLastPath", + UseCursorPointer = "useCursorPointer", } export type UserPreferences = { [key in UserPreference]?: boolean };