diff --git a/src/js/component/modal-manager.jsx b/src/js/component/modal-manager.jsx index 3bf2205b..c991a134 100644 --- a/src/js/component/modal-manager.jsx +++ b/src/js/component/modal-manager.jsx @@ -13,13 +13,14 @@ import NewCollectionModal from './modal/new-collection'; import NewFileModal from './modal/new-file'; import NewItemModal from './modal/new-item'; import RenameCollectionModal from './modal/rename-collection'; +import SettingsModal from './modal/settings'; import StyleInstallerModal from './modal/style-installer'; import IdentifierPicker from './modal/identifier-picker'; import ManageTagsModal from './modal/manage-tags'; import { ADD_LINKED_URL_TOUCH, BIBLIOGRAPHY, COLLECTION_ADD, COLLECTION_RENAME, COLLECTION_SELECT, -EXPORT, IDENTIFIER_PICKER, MANAGE_TAGS, MOVE_COLLECTION, NEW_ITEM, SORT_ITEMS, STYLE_INSTALLER, -ADD_BY_IDENTIFIER, NEW_FILE } from '../constants/modals'; +EXPORT, IDENTIFIER_PICKER, MANAGE_TAGS, MOVE_COLLECTION, NEW_ITEM, SETTINGS, SORT_ITEMS, +STYLE_INSTALLER, ADD_BY_IDENTIFIER, NEW_FILE } from '../constants/modals'; const lookup = { [ADD_BY_IDENTIFIER]: AddByIdentifierModal, @@ -36,6 +37,7 @@ const lookup = { [NEW_ITEM]: NewItemModal, [SORT_ITEMS]: ItemsSortModal, [STYLE_INSTALLER]: StyleInstallerModal, + [SETTINGS]: SettingsModal, }; const UNMOUNT_DELAY = 500; // to allow outro animatons (delay in ms) diff --git a/src/js/component/modal/settings.jsx b/src/js/component/modal/settings.jsx new file mode 100644 index 00000000..bd8a1c48 --- /dev/null +++ b/src/js/component/modal/settings.jsx @@ -0,0 +1,141 @@ +import cx from 'classnames'; +import { memo, useCallback, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Button, Icon } from 'web-common/components'; + +import Modal from '../ui/modal'; +import { preferenceChange, toggleModal } from '../../actions'; +import { SETTINGS } from '../../constants/modals'; +import Select from '../form/select'; +import { getUniqueId } from '../../utils'; + +const colorSchemeOptions = [ + { label: 'Automatic', value: '' }, + { label: 'Light', value: 'light' }, + { label: 'Dark', value: 'dark' }, +]; + +const densityOptions = [ + { label: 'Automatic', value: '' }, + { label: 'Mouse', value: 'mouse' }, + { label: 'Touch', value: 'touch' }, +]; + +const SettingsModal = () => { + const dispatch = useDispatch(); + const colorScheme = useSelector(state => state.preferences.colorScheme); + const density = useSelector(state => state.preferences.density); + const useDarkModeForContent = useSelector(state => state.preferences.useDarkModeForContent); + const isSmall = useSelector(state => state.device.xxs || state.device.xs || state.device.sm); + const isOpen = useSelector(state => state.modal.id === SETTINGS); + const colorSchemeInputId = useRef(getUniqueId()); + const densityInputId = useRef(getUniqueId()); + const useDarkModeForContentInputId = useRef(getUniqueId()); + + console.log({ isSmall }); + + const handleChange = useCallback(() => true, []); + + const handleSelectColorScheme = useCallback((newColorScheme) => { + dispatch(preferenceChange('colorScheme', newColorScheme)); + }, [dispatch]); + + const handleSelectDensity = useCallback((newDensity) => { + dispatch(preferenceChange('density', newDensity)); + }, [dispatch]); + + const handleUseDarkModeForContentChange = useCallback((ev) => { + dispatch(preferenceChange('useDarkModeForContent', ev.target.checked)); + }, [dispatch]); + + const handleClose = useCallback( + () => dispatch(toggleModal(SETTINGS, false)), + [dispatch]); + + return ( + + + + + + + Settings + + + + + + + + + + + + + UI Density + + + + + + + + Color Scheme + + + + + + + + + Use Dark Mode for Content + + + + + + ); +} + +export default memo(SettingsModal); diff --git a/src/js/component/ui/navbar.jsx b/src/js/component/ui/navbar.jsx index cf026869..74d15f6b 100644 --- a/src/js/component/ui/navbar.jsx +++ b/src/js/component/ui/navbar.jsx @@ -1,14 +1,15 @@ import PropTypes from 'prop-types'; -import cx from 'classnames'; import { Fragment, memo, useCallback, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { Button, DropdownToggle, DropdownMenu, DropdownItem, Icon, UncontrolledDropdown } from 'web-common/components'; +import { Button, Icon } from 'web-common/components'; import { useFocusManager } from 'web-common/hooks'; import { isTriggerEvent } from 'web-common/utils'; import MenuEntry from './menu-entry'; import Search from './../../component/search'; -import { currentTriggerSearchMode, preferenceChange, toggleNavbar, toggleTouchTagSelector } from '../../actions'; +import { SETTINGS } from '../../constants/modals'; +import { currentTriggerSearchMode, toggleModal, + toggleNavbar, toggleTouchTagSelector } from '../../actions'; const Navbar = memo(({ entries = [] }) => { const ref = useRef(null); @@ -18,8 +19,6 @@ const Navbar = memo(({ entries = [] }) => { const isLibrariesView = useSelector(state => state.current.view === 'libraries'); const isSingleColumn = useSelector(state => state.device.isSingleColumn); const colorScheme = useSelector(state => state.preferences.colorScheme); - // useDarkModeForContent === null means enabled - const useDarkModeForContent = useSelector(state => state.preferences.useDarkModeForContent) ?? true; const handleSearchButtonClick = useCallback(() => { dispatch(currentTriggerSearchMode()); @@ -29,16 +28,6 @@ const Navbar = memo(({ entries = [] }) => { dispatch(toggleTouchTagSelector()); }, [dispatch]); - const handleSelectColorScheme = useCallback((ev) => { - const colorScheme = ev.target.dataset.colorScheme === 'automatic' - ? null : ev.target.dataset.colorScheme; - dispatch(preferenceChange('colorScheme', colorScheme)); - }, [dispatch]); - - const handleToggleUseDarkModeForContent = useCallback(() => { - dispatch(preferenceChange('useDarkModeForContent', !useDarkModeForContent)); - }, [dispatch, useDarkModeForContent]); - const handleKeyDown = useCallback(ev => { if(ev.target !== ev.currentTarget) { return; @@ -55,6 +44,10 @@ const Navbar = memo(({ entries = [] }) => { } }, [focusNext, focusPrev]); + const handleSettingsButtonClick = useCallback(() => { + dispatch(toggleModal(SETTINGS, true)); + }, [dispatch]); + const handleNavbarToggle = useCallback(() => { dispatch(toggleNavbar(null)); }, [dispatch]); @@ -122,63 +115,16 @@ const Navbar = memo(({ entries = [] }) => { ) } - - - - - - - - { !colorScheme ? "✓" : ""} - Automatic - - - { colorScheme === 'light' ? "✓" : "" } - Light - - - { colorScheme === 'dark' ? "✓" : "" } - Dark - - - - {useDarkModeForContent ? "✓" : ""} - Use Dark Mode for content - - - + + { } -const current = (state = stateDefault, action, { config = {}, device = {} } = {}) => { +const current = (state = stateDefault, action, { config = {}, device = {}, preferences = {} } = {}) => { switch(action.type) { case CONFIGURE: return { @@ -210,7 +210,7 @@ const current = (state = stateDefault, action, { config = {}, device = {} } = {} case TRIGGER_USER_TYPE_CHANGE: return { ...state, - editingItemKey: action.userType === 'mouse' && !device.xxs && !device.xs && + editingItemKey: (preferences.density ? preferences.density : action.userType) === 'mouse' && !device.xxs && !device.xs && !device.sm && !device.md ? null : state.editingItemKey } case TRIGGER_FOCUS: diff --git a/src/js/reducers/device.js b/src/js/reducers/device.js index d33e763b..5968d523 100644 --- a/src/js/reducers/device.js +++ b/src/js/reducers/device.js @@ -1,4 +1,4 @@ -import { TRIGGER_USER_TYPE_CHANGE, TRIGGER_RESIZE_VIEWPORT } from '../constants/actions.js'; +import { TRIGGER_USER_TYPE_CHANGE, TRIGGER_RESIZE_VIEWPORT, PREFERENCE_CHANGE } from '../constants/actions.js'; import { getScrollbarWidth, pick } from 'web-common/utils'; const isInitiallyMouse = typeof(matchMedia) === 'function' ? matchMedia('(pointer:fine)').matches : null; @@ -41,19 +41,42 @@ const getDevice = (userType, viewport, { isEmbedded } = {}) => { shouldUseSidebar, shouldUseTabs }; }; -const device = (state = defaultState, action, { config } = {}) => { - var viewport; +const getUserType = (state, action, preferences) => { + if (action.type === PREFERENCE_CHANGE && action.name === 'density') { + return action.value ? action.value : state.lastDetectedUserType; + } + + if (preferences.density) { + return preferences.density; + } + + return state.userType; +} + +const getUserTypeBooleans = (state, action, userType) => { + return { + isKeyboardUser: action.type === TRIGGER_USER_TYPE_CHANGE ? action.isKeyboardUser : state.isKeyboardUser, + isMouseUser: userType === 'mouse', + isTouchUser: userType === 'touch' + }; +} + +const device = (state = defaultState, action, { config, preferences } = {}) => { switch(action.type) { + case PREFERENCE_CHANGE: case TRIGGER_RESIZE_VIEWPORT: - case TRIGGER_USER_TYPE_CHANGE: - viewport = action.type === TRIGGER_RESIZE_VIEWPORT ? getViewport(action) : pick(state, ['xxs', 'xs', 'sm', 'md', 'lg']); + case TRIGGER_USER_TYPE_CHANGE: { + const viewport = action.type === TRIGGER_RESIZE_VIEWPORT ? getViewport(action) : pick(state, ['xxs', 'xs', 'sm', 'md', 'lg']); + const userType = getUserType(state, action, preferences); return { ...state, - ...pick(action, ['isKeyboardUser', 'isMouseUser', 'isTouchUser', 'userType']), - ...getDevice('userType' in action ? action.userType : state.userType, viewport, config), + ...getUserTypeBooleans(state, action, userType), + ...getDevice(userType, viewport, config), ...viewport, - scrollbarWidth: state.userType === 'touch' && action.userType === 'mouse' ? getScrollbarWidth() : state.scrollbarWidth + lastDetectedUserType: action.type === TRIGGER_USER_TYPE_CHANGE ? action.userType : state.lastDetectedUserType, + scrollbarWidth: state.userType === 'touch' && userType === 'mouse' ? getScrollbarWidth() : state.scrollbarWidth } + } default: return state; } diff --git a/src/scss/components/_navbar.scss b/src/scss/components/_navbar.scss index 3d719a72..86661a70 100644 --- a/src/scss/components/_navbar.scss +++ b/src/scss/components/_navbar.scss @@ -121,7 +121,7 @@ .search-toggle, .touch-tag-selector-toggle { .icon { - color: $icon-bar-bg; + color: var(--color-shade-8); } } @@ -140,7 +140,7 @@ display: block; width: $icon-bar-width; height: $icon-bar-height; - background: $icon-bar-bg; + background: var(--color-shade-8); & + .icon-bar { margin-top: $icon-bar-spacing; @@ -194,33 +194,13 @@ } } - .color-scheme-dropdown { - .nav-link { - display: flex; - align-items: center; - color: $navbar-link-color; - - @include bp-up(sm) { - margin-left: $space-sm; - } - - @include state(".color-scheme-dropdown.show") { - @include scopedVariant("html:not(.specifity)") { - color: $navbar-dropdown-toggle-active-color; - - .icon { - color: inherit; - } - } - } - - .icon { - color: inherit; - } + .settings-toggle { + @include bp-up(sm) { + margin-left: $space-min; } - .tick { - margin-right: $space-min; + .icon { + color: var(--color-shade-8); } } } diff --git a/src/scss/components/modal/_settings.scss b/src/scss/components/modal/_settings.scss new file mode 100644 index 00000000..5f8772b5 --- /dev/null +++ b/src/scss/components/modal/_settings.scss @@ -0,0 +1,16 @@ +.modal-settings { + overflow: visible; + + .form-group:not(.checkbox) { + @include touch-or-bp-down(sm) { + .col-form-label { + flex: 0 0 33%; + } + + .col { + flex: 0 0 66%; + overflow: hidden; + } + } + } +} diff --git a/src/scss/partials/_forms.scss b/src/scss/partials/_forms.scss index 6034de76..03f0ddbd 100644 --- a/src/scss/partials/_forms.scss +++ b/src/scss/partials/_forms.scss @@ -42,6 +42,10 @@ fieldset { } .form-group { + &.checkbox { + display: flex; + } + @include touch-or-bp-down(sm) { display: flex; padding: $input-border-width $input-btn-padding-x-touch; @@ -346,6 +350,12 @@ textarea { .checkbox, .radio, .file { + html:not(.specifity) &.disabled { + label { + color: var(--color-text-disabled); + } + } + @include touch-or-bp-down(sm) { display: flex; align-items: center; @@ -354,7 +364,7 @@ textarea { @include hairline(bottom, "color-shade-5"); &:not(:first-child) { - @include hairline(top, "color-shade-5", $start: $default-padding-x-touch); + @include hairline(top, "color-shade-5"); } &:not(:last-child) { diff --git a/src/scss/themes/_common.scss b/src/scss/themes/_common.scss index 144ad5a2..80d5169c 100644 --- a/src/scss/themes/_common.scss +++ b/src/scss/themes/_common.scss @@ -65,7 +65,6 @@ $navbar-link-color: $shade-9; $navbar-link-active-color: var(--accent-blue); $navbar-link-mobile-active-color: var(--color-shade-0-50); $navbar-dropdown-toggle-active-color: $shade-6; -$icon-bar-bg: $shade-8; // Collection tree diff --git a/src/scss/zotero-web-library.scss b/src/scss/zotero-web-library.scss index a5c0d568..ba00dda5 100644 --- a/src/scss/zotero-web-library.scss +++ b/src/scss/zotero-web-library.scss @@ -93,6 +93,7 @@ $scope: ".zotero-wl"; @import "components/modal/react-modal"; @import "components/modal/collection-select"; @import "components/modal/bibliography"; + @import "components/modal/settings"; @import "components/modal/style-installer"; @import "components/modal/note"; @import "components/modal/identifier-picker"; diff --git a/test/snapshots/desktop-items-list-macos-safari.png b/test/snapshots/desktop-items-list-macos-safari.png index a106a775..68eb2fc8 100644 Binary files a/test/snapshots/desktop-items-list-macos-safari.png and b/test/snapshots/desktop-items-list-macos-safari.png differ diff --git a/test/snapshots/desktop-items-list-windows-chrome-small-desktop.png b/test/snapshots/desktop-items-list-windows-chrome-small-desktop.png index a557ba33..c86b29f4 100644 Binary files a/test/snapshots/desktop-items-list-windows-chrome-small-desktop.png and b/test/snapshots/desktop-items-list-windows-chrome-small-desktop.png differ diff --git a/test/snapshots/desktop-items-list-windows-chrome.png b/test/snapshots/desktop-items-list-windows-chrome.png index f965ba53..a20468bb 100644 Binary files a/test/snapshots/desktop-items-list-windows-chrome.png and b/test/snapshots/desktop-items-list-windows-chrome.png differ diff --git a/test/snapshots/mobile-item-details-ipad-emulator.png b/test/snapshots/mobile-item-details-ipad-emulator.png index 3219c379..f4d8ce17 100644 Binary files a/test/snapshots/mobile-item-details-ipad-emulator.png and b/test/snapshots/mobile-item-details-ipad-emulator.png differ diff --git a/test/snapshots/mobile-item-details-ipad-pro-landscape-emulator.png b/test/snapshots/mobile-item-details-ipad-pro-landscape-emulator.png index c16d0fe2..4fdb8f9e 100644 Binary files a/test/snapshots/mobile-item-details-ipad-pro-landscape-emulator.png and b/test/snapshots/mobile-item-details-ipad-pro-landscape-emulator.png differ diff --git a/test/snapshots/mobile-item-details-iphone-emulator.png b/test/snapshots/mobile-item-details-iphone-emulator.png index 9a087e8b..91a1d410 100644 Binary files a/test/snapshots/mobile-item-details-iphone-emulator.png and b/test/snapshots/mobile-item-details-iphone-emulator.png differ diff --git a/test/snapshots/mobile-item-details-pixel-7-chrome.png b/test/snapshots/mobile-item-details-pixel-7-chrome.png index 9c187daf..3ab69326 100644 Binary files a/test/snapshots/mobile-item-details-pixel-7-chrome.png and b/test/snapshots/mobile-item-details-pixel-7-chrome.png differ diff --git a/test/snapshots/mobile-items-list-ipad-emulator.png b/test/snapshots/mobile-items-list-ipad-emulator.png index 839bda24..3b5ad9f6 100644 Binary files a/test/snapshots/mobile-items-list-ipad-emulator.png and b/test/snapshots/mobile-items-list-ipad-emulator.png differ diff --git a/test/snapshots/mobile-items-list-ipad-pro-landscape-emulator.png b/test/snapshots/mobile-items-list-ipad-pro-landscape-emulator.png index 4376c4bd..3bb44320 100644 Binary files a/test/snapshots/mobile-items-list-ipad-pro-landscape-emulator.png and b/test/snapshots/mobile-items-list-ipad-pro-landscape-emulator.png differ diff --git a/test/snapshots/mobile-items-list-iphone-emulator.png b/test/snapshots/mobile-items-list-iphone-emulator.png index 0e2b717f..ef11988c 100644 Binary files a/test/snapshots/mobile-items-list-iphone-emulator.png and b/test/snapshots/mobile-items-list-iphone-emulator.png differ diff --git a/test/snapshots/mobile-items-list-pixel-7-chrome.png b/test/snapshots/mobile-items-list-pixel-7-chrome.png index 89eb058b..c2c62e56 100644 Binary files a/test/snapshots/mobile-items-list-pixel-7-chrome.png and b/test/snapshots/mobile-items-list-pixel-7-chrome.png differ diff --git a/test/snapshots/mobile-items-list-search-enabled-iphone-emulator.png b/test/snapshots/mobile-items-list-search-enabled-iphone-emulator.png index 578ac483..08f87fb1 100644 Binary files a/test/snapshots/mobile-items-list-search-enabled-iphone-emulator.png and b/test/snapshots/mobile-items-list-search-enabled-iphone-emulator.png differ diff --git a/test/snapshots/mobile-items-list-search-enabled-pixel-7-chrome.png b/test/snapshots/mobile-items-list-search-enabled-pixel-7-chrome.png index 037867cb..bb4db25c 100644 Binary files a/test/snapshots/mobile-items-list-search-enabled-pixel-7-chrome.png and b/test/snapshots/mobile-items-list-search-enabled-pixel-7-chrome.png differ