feat(web): mobile-friendly (responsive) preferences UI (#1234)
This commit is contained in:
BIN
.yarn/cache/react-refresh-npm-0.14.0-78ef5eeb73-dc69fa8c99.zip
vendored
Normal file
BIN
.yarn/cache/react-refresh-npm-0.14.0-78ef5eeb73-dc69fa8c99.zip
vendored
Normal file
Binary file not shown.
@@ -25,6 +25,7 @@
|
|||||||
"@babel/plugin-transform-react-jsx": "^7.17.3",
|
"@babel/plugin-transform-react-jsx": "^7.17.3",
|
||||||
"@babel/preset-env": "*",
|
"@babel/preset-env": "*",
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||||
"@types/jest": "^27.5.1",
|
"@types/jest": "^27.5.1",
|
||||||
"@types/react": "^17.0.42",
|
"@types/react": "^17.0.42",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
"postcss-loader": "^7.0.0",
|
"postcss-loader": "^7.0.0",
|
||||||
"prettier": "*",
|
"prettier": "*",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||||
|
"react-refresh": "^0.14.0",
|
||||||
"sass-loader": "*",
|
"sass-loader": "*",
|
||||||
"svg-jest": "^1.0.1",
|
"svg-jest": "^1.0.1",
|
||||||
"tailwindcss": "^3.1.4",
|
"tailwindcss": "^3.1.4",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { ListboxButton } from '@reach/listbox'
|
import { ListboxButton } from '@reach/listbox'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
const StyledListboxButton = styled(ListboxButton)`
|
const StyledListboxButton = styled(ListboxButton).attrs(() => ({
|
||||||
|
className: 'w-full md:w-fit',
|
||||||
|
}))`
|
||||||
&[data-reach-listbox-button] {
|
&[data-reach-listbox-button] {
|
||||||
background-color: var(--sn-stylekit-background-color);
|
background-color: var(--sn-stylekit-background-color);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@@ -14,7 +16,6 @@ const StyledListboxButton = styled(ListboxButton)`
|
|||||||
padding-left: 0.875rem;
|
padding-left: 0.875rem;
|
||||||
padding-right: 0.875rem;
|
padding-right: 0.875rem;
|
||||||
padding-top: 0.375rem;
|
padding-top: 0.375rem;
|
||||||
width: fit-content;
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const Authentication: FunctionComponent<Props> = ({ viewControllerManager }) =>
|
|||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<div className="flex flex-col items-center px-12">
|
<div className="flex flex-col items-center px-4 md:px-12">
|
||||||
<AccountIllustration className="mb-3" />
|
<AccountIllustration className="mb-3" />
|
||||||
<Title>You're not signed in</Title>
|
<Title>You're not signed in</Title>
|
||||||
<Text className="mb-3 text-center">
|
<Text className="mb-3 text-center">
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ const SignOutView: FunctionComponent<Props> = observer(({ application, viewContr
|
|||||||
<Title>Sign out</Title>
|
<Title>Sign out</Title>
|
||||||
<Subtitle>Other devices</Subtitle>
|
<Subtitle>Other devices</Subtitle>
|
||||||
<Text>Want to sign out on all devices except this one?</Text>
|
<Text>Want to sign out on all devices except this one?</Text>
|
||||||
<div className="mt-3 flex flex-row">
|
<div className="mt-3 flex flex-row flex-wrap gap-3">
|
||||||
<Button
|
<Button
|
||||||
className="mr-3"
|
|
||||||
label="Sign out other sessions"
|
label="Sign out other sessions"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
viewControllerManager.accountMenuController.setOtherSessionsSignOut(true)
|
viewControllerManager.accountMenuController.setOtherSessionsSignOut(true)
|
||||||
|
|||||||
@@ -20,14 +20,15 @@ const EncryptionEnabled: FunctionComponent<Props> = ({ viewControllerManager })
|
|||||||
const tagIcon = <Icon type="hashtag" className="min-h-5 min-w-5" />
|
const tagIcon = <Icon type="hashtag" className="min-h-5 min-w-5" />
|
||||||
const archiveIcon = <Icon type="archive" className="min-h-5 min-w-5" />
|
const archiveIcon = <Icon type="archive" className="min-h-5 min-w-5" />
|
||||||
const trashIcon = <Icon type="trash" className="min-h-5 min-w-5" />
|
const trashIcon = <Icon type="trash" className="min-h-5 min-w-5" />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row items-start pb-1 pt-1.5">
|
<div className="flex flex-row flex-wrap items-start pt-1.5 md:pb-1">
|
||||||
<EncryptionStatusItem status={notes} icon={noteIcon} />
|
<EncryptionStatusItem status={notes} icon={noteIcon} />
|
||||||
<div className="min-w-3" />
|
<div className="min-w-3" />
|
||||||
<EncryptionStatusItem status={tags} icon={tagIcon} />
|
<EncryptionStatusItem status={tags} icon={tagIcon} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-start">
|
<div className="flex flex-row flex-wrap items-start">
|
||||||
<EncryptionStatusItem status={archived} icon={archiveIcon} />
|
<EncryptionStatusItem status={archived} icon={archiveIcon} />
|
||||||
<div className="min-w-3" />
|
<div className="min-w-3" />
|
||||||
<EncryptionStatusItem status={deleted} icon={trashIcon} />
|
<EncryptionStatusItem status={deleted} icon={trashIcon} />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import PaneSelector from './PaneSelector'
|
|||||||
import { PreferencesProps } from './PreferencesProps'
|
import { PreferencesProps } from './PreferencesProps'
|
||||||
|
|
||||||
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = (props) => (
|
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = (props) => (
|
||||||
<div className="flex min-h-0 flex-grow flex-row justify-between">
|
<div className="flex min-h-0 flex-grow flex-col-reverse md:flex-row md:justify-between">
|
||||||
<PreferencesMenuView menu={props.menu} />
|
<PreferencesMenuView menu={props.menu} />
|
||||||
<PaneSelector {...props} />
|
<PaneSelector {...props} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { FunctionComponent } from 'react'
|
|||||||
|
|
||||||
export const Title: FunctionComponent = ({ children }) => (
|
export const Title: FunctionComponent = ({ children }) => (
|
||||||
<>
|
<>
|
||||||
<h2 className="m-0 mb-1 text-base font-bold text-info">{children}</h2>
|
<h2 className="m-0 mb-1 text-lg font-bold text-info md:text-base">{children}</h2>
|
||||||
<div className="min-h-2" />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ export const SubtitleLight: FunctionComponent<{ className?: string }> = ({ child
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const Text: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
export const Text: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
||||||
<p className={`${className} text-xs`}>{children}</p>
|
<p className={`${className} text-sm md:text-xs`}>{children}</p>
|
||||||
)
|
)
|
||||||
|
|
||||||
const buttonClasses =
|
const buttonClasses =
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent } from 'react'
|
||||||
|
|
||||||
const PreferencesPane: FunctionComponent = ({ children }) => (
|
const PreferencesPane: FunctionComponent = ({ children }) => (
|
||||||
<div className="flex min-h-0 flex-grow flex-row overflow-y-auto text-foreground">
|
<div className="flex min-h-0 flex-grow flex-col overflow-y-auto text-foreground md:flex-row">
|
||||||
<div className="flex flex-grow flex-col items-center py-6">
|
<div className="flex flex-grow flex-col items-center px-3 py-6 md:px-0">
|
||||||
<div className="flex w-125 max-w-125 flex-col">
|
<div className="flex flex-col md:w-125 md:max-w-125">
|
||||||
{children != undefined && Array.isArray(children) ? children.filter((child) => child != undefined) : children}
|
{children != undefined && Array.isArray(children) ? children.filter((child) => child != undefined) : children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export class PreferencesMenu {
|
|||||||
return 'account'
|
return 'account'
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPane(key: PreferenceId): void {
|
selectPane = (key: PreferenceId) => {
|
||||||
this._selectedPane = key
|
this._selectedPane = key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,54 @@
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent, useMemo } from 'react'
|
||||||
|
import Dropdown from '../Dropdown/Dropdown'
|
||||||
|
import { DropdownItem } from '../Dropdown/DropdownItem'
|
||||||
import PreferencesMenuItem from './PreferencesComponents/MenuItem'
|
import PreferencesMenuItem from './PreferencesComponents/MenuItem'
|
||||||
import { PreferencesMenu } from './PreferencesMenu'
|
import { PreferenceId, PreferencesMenu } from './PreferencesMenu'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
menu: PreferencesMenu
|
menu: PreferencesMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => (
|
const PreferencesMenuView: FunctionComponent<Props> = ({ menu }) => {
|
||||||
<div className="flex min-w-55 flex-col overflow-y-auto px-3 py-6">
|
const { selectedPaneId, selectPane, menuItems } = menu
|
||||||
{menu.menuItems.map((pref) => (
|
|
||||||
<PreferencesMenuItem
|
const dropdownMenuItems: DropdownItem[] = useMemo(
|
||||||
key={pref.id}
|
() =>
|
||||||
iconType={pref.icon}
|
menuItems.map((menuItem) => ({
|
||||||
label={pref.label}
|
icon: menuItem.icon,
|
||||||
selected={pref.selected}
|
label: menuItem.label,
|
||||||
hasBubble={pref.hasBubble}
|
value: menuItem.id,
|
||||||
onClick={() => menu.selectPane(pref.id)}
|
})),
|
||||||
/>
|
[menuItems],
|
||||||
))}
|
)
|
||||||
</div>
|
|
||||||
)
|
return (
|
||||||
|
<div className="px-3 py-2 md:px-0">
|
||||||
|
<div className="hidden min-w-55 flex-col overflow-y-auto px-3 py-6 md:flex">
|
||||||
|
{menuItems.map((pref) => (
|
||||||
|
<PreferencesMenuItem
|
||||||
|
key={pref.id}
|
||||||
|
iconType={pref.icon}
|
||||||
|
label={pref.label}
|
||||||
|
selected={pref.selected}
|
||||||
|
hasBubble={pref.hasBubble}
|
||||||
|
onClick={() => selectPane(pref.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="md:hidden">
|
||||||
|
<Dropdown
|
||||||
|
id="preferences-menu"
|
||||||
|
items={dropdownMenuItems}
|
||||||
|
label="Preferences Menu"
|
||||||
|
value={selectedPaneId}
|
||||||
|
onChange={(paneId) => {
|
||||||
|
selectPane(paneId as PreferenceId)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default observer(PreferencesMenuView)
|
export default observer(PreferencesMenuView)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import RoundIconButton from '@/Components/Button/RoundIconButton'
|
import RoundIconButton from '@/Components/Button/RoundIconButton'
|
||||||
import TitleBar from '@/Components/TitleBar/TitleBar'
|
|
||||||
import Title from '@/Components/TitleBar/Title'
|
|
||||||
import { FunctionComponent, useEffect, useMemo } from 'react'
|
import { FunctionComponent, useEffect, useMemo } from 'react'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { PreferencesMenu } from './PreferencesMenu'
|
import { PreferencesMenu } from './PreferencesMenu'
|
||||||
@@ -29,9 +27,9 @@ const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-0 left-0 z-preferences flex h-full w-full flex-col bg-contrast">
|
<div className="absolute top-0 left-0 z-preferences flex h-full w-full flex-col bg-contrast">
|
||||||
<TitleBar className="items-center justify-between">
|
<div className="flex w-full flex-row items-center justify-between border-b border-solid border-border bg-default px-3 py-2 md:p-3">
|
||||||
<div className="h-8 w-8" />
|
<div className="hidden h-8 w-8 md:block" />
|
||||||
<Title className="text-lg">Your preferences for Standard Notes</Title>
|
<h1 className="text-base font-bold md:text-lg">Your preferences for Standard Notes</h1>
|
||||||
<RoundIconButton
|
<RoundIconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.closePreferences()
|
props.closePreferences()
|
||||||
@@ -39,7 +37,7 @@ const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
|
|||||||
type="normal"
|
type="normal"
|
||||||
icon="close"
|
icon="close"
|
||||||
/>
|
/>
|
||||||
</TitleBar>
|
</div>
|
||||||
<PreferencesCanvas {...props} menu={menu} />
|
<PreferencesCanvas {...props} menu={menu} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { FunctionComponent } from 'react'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Title: FunctionComponent<Props> = ({ children, className }) => {
|
|
||||||
return <div className={`font-bold ${className ?? ''}`}>{children}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Title
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { FunctionComponent } from 'react'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const TitleBar: FunctionComponent<Props> = ({ children, className }) => (
|
|
||||||
<div className={`flex h-14 w-full flex-row border-b border-solid border-border bg-default p-3 ${className ?? ''}`}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default TitleBar
|
|
||||||
@@ -5,6 +5,8 @@ const CircularDependencyPlugin = require('circular-dependency-plugin')
|
|||||||
const mergeWithEnvDefaults = require('./web.webpack-defaults')
|
const mergeWithEnvDefaults = require('./web.webpack-defaults')
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
module.exports = (env) => {
|
module.exports = (env) => {
|
||||||
mergeWithEnvDefaults(env)
|
mergeWithEnvDefaults(env)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge')
|
||||||
const config = require('./web.webpack.config.js');
|
const config = require('./web.webpack.config.js')
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
const mergeWithEnvDefaults = require('./web.webpack-defaults.js');
|
const mergeWithEnvDefaults = require('./web.webpack-defaults.js')
|
||||||
|
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
|
||||||
|
|
||||||
module.exports = (env, argv) => {
|
module.exports = (env, argv) => {
|
||||||
const port = argv.port || 3001;
|
const port = argv.port || 3001
|
||||||
mergeWithEnvDefaults(env);
|
mergeWithEnvDefaults(env)
|
||||||
return merge(config(env, argv), {
|
return merge(config(env, argv), {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
optimization: {
|
optimization: {
|
||||||
@@ -19,14 +20,15 @@ module.exports = (env, argv) => {
|
|||||||
env: process.env,
|
env: process.env,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
new ReactRefreshWebpackPlugin(),
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
hot: 'only',
|
hot: true,
|
||||||
static: '../web-server/public',
|
static: '../web-server/public',
|
||||||
port,
|
port,
|
||||||
devMiddleware: {
|
devMiddleware: {
|
||||||
writeToDisk: argv.writeToDisk,
|
writeToDisk: argv.writeToDisk,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|||||||
11
yarn.lock
11
yarn.lock
@@ -5117,7 +5117,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3":
|
"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3, @pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7":
|
||||||
version: 0.5.7
|
version: 0.5.7
|
||||||
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7"
|
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7466,6 +7466,7 @@ __metadata:
|
|||||||
"@babel/plugin-transform-react-jsx": ^7.17.3
|
"@babel/plugin-transform-react-jsx": ^7.17.3
|
||||||
"@babel/preset-env": "*"
|
"@babel/preset-env": "*"
|
||||||
"@babel/preset-typescript": ^7.16.7
|
"@babel/preset-typescript": ^7.16.7
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.7
|
||||||
"@reach/alert": ^0.16.0
|
"@reach/alert": ^0.16.0
|
||||||
"@reach/alert-dialog": ^0.16.2
|
"@reach/alert-dialog": ^0.16.2
|
||||||
"@reach/checkbox": ^0.16.0
|
"@reach/checkbox": ^0.16.0
|
||||||
@@ -7515,6 +7516,7 @@ __metadata:
|
|||||||
react-dnd-html5-backend: ^16.0.1
|
react-dnd-html5-backend: ^16.0.1
|
||||||
react-dnd-touch-backend: ^16.0.1
|
react-dnd-touch-backend: ^16.0.1
|
||||||
react-dom: ^18.1.0
|
react-dom: ^18.1.0
|
||||||
|
react-refresh: ^0.14.0
|
||||||
sass-loader: "*"
|
sass-loader: "*"
|
||||||
styled-components: ^5.3.5
|
styled-components: ^5.3.5
|
||||||
svg-jest: ^1.0.1
|
svg-jest: ^1.0.1
|
||||||
@@ -32588,6 +32590,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-refresh@npm:^0.14.0":
|
||||||
|
version: 0.14.0
|
||||||
|
resolution: "react-refresh@npm:0.14.0"
|
||||||
|
checksum: dc69fa8c993df512f42dd0f1b604978ae89bd747c0ed5ec595c0cc50d535fb2696619ccd98ae28775cc01d0a7c146a532f0f7fb81dc22e1977c242a4912312f4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-refresh@npm:^0.4.0":
|
"react-refresh@npm:^0.4.0":
|
||||||
version: 0.4.3
|
version: 0.4.3
|
||||||
resolution: "react-refresh@npm:0.4.3"
|
resolution: "react-refresh@npm:0.4.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user