feat: implement preferences pane

This commit is contained in:
Gorjan Petrovski
2021-07-05 16:57:37 +02:00
committed by GitHub
parent b3347b75ba
commit a9870214ea
27 changed files with 572 additions and 206 deletions

View File

@@ -13,40 +13,59 @@ import PasswordIcon from '../../icons/ic-textbox-password.svg';
import TrashSweepIcon from '../../icons/ic-trash-sweep.svg';
import MoreIcon from '../../icons/ic-more.svg';
import TuneIcon from '../../icons/ic-tune.svg';
import AccessibilityIcon from '../../icons/ic-accessibility.svg';
import HelpIcon from '../../icons/ic-help.svg';
import KeyboardIcon from '../../icons/ic-keyboard.svg';
import ListedIcon from '../../icons/ic-listed.svg';
import SecurityIcon from '../../icons/ic-security.svg';
import SettingsFilledIcon from '../../icons/ic-settings-filled.svg';
import StarIcon from '../../icons/ic-star.svg';
import ThemesIcon from '../../icons/ic-themes.svg';
import UserIcon from '../../icons/ic-user.svg';
import { toDirective } from './utils';
const ICONS = {
'pencil-off': PencilOffIcon,
'rich-text': RichTextIcon,
'trash': TrashIcon,
'pin': PinIcon,
'unpin': UnpinIcon,
'archive': ArchiveIcon,
'unarchive': UnarchiveIcon,
'hashtag': HashtagIcon,
trash: TrashIcon,
pin: PinIcon,
unpin: UnpinIcon,
archive: ArchiveIcon,
unarchive: UnarchiveIcon,
hashtag: HashtagIcon,
'chevron-right': ChevronRightIcon,
'restore': RestoreIcon,
'close': CloseIcon,
'password': PasswordIcon,
restore: RestoreIcon,
close: CloseIcon,
password: PasswordIcon,
'trash-sweep': TrashSweepIcon,
'more': MoreIcon,
'tune': TuneIcon,
more: MoreIcon,
tune: TuneIcon,
accessibility: AccessibilityIcon,
help: HelpIcon,
keyboard: KeyboardIcon,
listed: ListedIcon,
security: SecurityIcon,
'settings-filled': SettingsFilledIcon,
star: StarIcon,
themes: ThemesIcon,
user: UserIcon,
};
export type IconType = keyof typeof ICONS;
type Props = {
type: keyof (typeof ICONS);
className: string;
}
type: IconType;
className?: string;
};
export const Icon: React.FC<Props> = ({ type, className }) => {
const IconComponent = ICONS[type];
return <IconComponent className={`sn-icon ${className}`} />;
};
export const IconDirective = toDirective<Props>(
Icon,
{
type: '@',
className: '@',
}
);
export const IconDirective = toDirective<Props>(Icon, {
type: '@',
className: '@',
});

View File

@@ -0,0 +1,53 @@
import { FunctionComponent } from 'preact';
import { Icon, IconType } from './Icon';
const ICON_BUTTON_TYPES: {
[type: string]: { className: string };
} = {
normal: {
className: '',
},
primary: {
className: 'info',
},
};
export type IconButtonType = keyof typeof ICON_BUTTON_TYPES;
interface IconButtonProps {
/**
* onClick - preventDefault is handled within the component
*/
onClick: () => void;
type: IconButtonType;
className?: string;
iconType: IconType;
}
/**
* CircleButton component with an icon for SPA
* preventDefault is already handled within the component
*/
export const IconButton: FunctionComponent<IconButtonProps> = ({
onClick,
type,
className,
iconType,
}) => {
const click = (e: MouseEvent) => {
e.preventDefault();
onClick();
};
const typeProps = ICON_BUTTON_TYPES[type];
return (
<button
className={`sn-icon-button ${typeProps.className} ${className ?? ''}`}
onClick={click}
>
<Icon type={iconType} />
</button>
);
};

View File

@@ -0,0 +1,23 @@
import { Icon, IconType } from '@/components/Icon';
import { FunctionComponent } from 'preact';
interface PreferencesMenuItemProps {
iconType: IconType;
label: string;
selected: boolean;
onClick: () => void;
}
export const PreferencesMenuItem: FunctionComponent<PreferencesMenuItemProps> =
({ iconType, label, selected, onClick }) => (
<div
className={`preferences-menu-item ${selected ? 'selected' : ''}`}
onClick={(e) => {
e.preventDefault();
onClick();
}}
>
<Icon className="icon" type={iconType} />
{label}
</div>
);

View File

@@ -0,0 +1,13 @@
import { FunctionComponent } from 'preact';
export const TitleBar: FunctionComponent<{ className?: string }> = ({
children,
className,
}) => <div className={`sn-titlebar ${className ?? ''}`}>{children}</div>;
export const Title: FunctionComponent<{ className?: string }> = ({
children,
className,
}) => {
return <div className={`sn-title ${className ?? ''}`}>{children}</div>;
};

View File

@@ -0,0 +1,22 @@
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { toDirective } from '../utils';
import { PreferencesView } from './view';
interface WrapperProps {
appState: { preferences: { isOpen: boolean; closePreferences: () => void } };
}
const PreferencesViewWrapper: FunctionComponent<WrapperProps> = observer(
({ appState }) => {
if (!appState.preferences.isOpen) return null;
return (
<PreferencesView close={() => appState.preferences.closePreferences()} />
);
}
);
export const PreferencesDirective = toDirective<WrapperProps>(
PreferencesViewWrapper
);

View File

@@ -0,0 +1,50 @@
import { IconType } from '@/components/Icon';
import { action, computed, makeObservable, observable } from 'mobx';
interface PreferenceItem {
icon: IconType;
label: string;
}
interface PreferenceListItem extends PreferenceItem {
id: number;
}
const predefinedItems: PreferenceItem[] = [
{ label: 'General', icon: 'settings-filled' },
{ label: 'Account', icon: 'user' },
{ label: 'Appearance', icon: 'themes' },
{ label: 'Security', icon: 'security' },
{ label: 'Listed', icon: 'listed' },
{ label: 'Shortcuts', icon: 'keyboard' },
{ label: 'Accessibility', icon: 'accessibility' },
{ label: 'Get a free month', icon: 'star' },
{ label: 'Help & feedback', icon: 'help' },
];
export class MockState {
private readonly _items: PreferenceListItem[];
private _selectedId = 0;
constructor(items: PreferenceItem[] = predefinedItems) {
makeObservable<MockState, '_selectedId'>(this, {
_selectedId: observable,
items: computed,
select: action,
});
this._items = items.map((p, idx) => ({ ...p, id: idx }));
this._selectedId = this._items[0].id;
}
select(id: number) {
this._selectedId = id;
}
get items(): (PreferenceListItem & { selected: boolean })[] {
return this._items.map((p) => ({
...p,
selected: p.id === this._selectedId,
}));
}
}

View File

@@ -0,0 +1,33 @@
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { PreferencesMenuItem } from '../PreferencesMenuItem';
import { MockState } from './mock-state';
interface PreferencesMenuProps {
store: MockState;
}
const PreferencesMenu: FunctionComponent<PreferencesMenuProps> = observer(
({ store }) => (
<div className="h-full w-auto flex flex-col px-3 py-6">
{store.items.map((pref) => (
<PreferencesMenuItem
key={pref.id}
iconType={pref.icon}
label={pref.label}
selected={pref.selected}
onClick={() => store.select(pref.id)}
/>
))}
</div>
)
);
export const PreferencesPane: FunctionComponent = () => {
const store = new MockState();
return (
<div className="h-full w-full flex flex-row">
<PreferencesMenu store={store}></PreferencesMenu>
</div>
);
};

View File

@@ -0,0 +1,28 @@
import { IconButton } from '@/components/IconButton';
import { TitleBar, Title } from '@/components/TitleBar';
import { FunctionComponent } from 'preact';
import { PreferencesPane } from './pane';
interface PreferencesViewProps {
close: () => void;
}
export const PreferencesView: FunctionComponent<PreferencesViewProps> = ({
close,
}) => (
<div className="sn-full-screen flex flex-col bg-contrast z-index-preferences">
<TitleBar className="items-center justify-between">
{/* div is added so flex justify-between can center the title */}
<div className="h-8 w-8" />
<Title className="text-lg">Your preferences for Standard Notes</Title>
<IconButton
onClick={() => {
close();
}}
type="normal"
iconType="close"
/>
</TitleBar>
<PreferencesPane />
</div>
);