feat(web): mobile-friendly (responsive) preferences UI (#1234)

This commit is contained in:
Aman Harwara
2022-07-09 00:38:44 +05:30
committed by GitHub
parent 961d1fb8d4
commit 66f97f0612
17 changed files with 89 additions and 71 deletions

Binary file not shown.

View File

@@ -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",

View File

@@ -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;
} }
` `

View File

@@ -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">

View File

@@ -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)

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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 =

View File

@@ -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>

View File

@@ -117,7 +117,7 @@ export class PreferencesMenu {
return 'account' return 'account'
} }
selectPane(key: PreferenceId): void { selectPane = (key: PreferenceId) => {
this._selectedPane = key this._selectedPane = key
} }

View File

@@ -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)

View File

@@ -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>
) )

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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,
}, },
}, },
}); })
}; }

View File

@@ -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"