feat: add account switcher menu (#941)
This commit is contained in:
@@ -10,10 +10,13 @@ import { AccountMenuPane } from '.';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { Menu } from '../Menu/Menu';
|
||||
import { MenuItem, MenuItemSeparator, MenuItemType } from '../Menu/MenuItem';
|
||||
import { WorkspaceSwitcherOption } from './WorkspaceSwitcher/WorkspaceSwitcherOption';
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
setMenuPane: (pane: AccountMenuPane) => void;
|
||||
closeMenu: () => void;
|
||||
};
|
||||
@@ -21,7 +24,7 @@ type Props = {
|
||||
const iconClassName = 'color-neutral mr-2';
|
||||
|
||||
export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
||||
({ application, appState, setMenuPane, closeMenu }) => {
|
||||
({ application, appState, setMenuPane, closeMenu, mainApplicationGroup }) => {
|
||||
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false);
|
||||
const [lastSyncDate, setLastSyncDate] = useState(
|
||||
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
|
||||
@@ -56,7 +59,7 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-3 mt-1 mb-3">
|
||||
<div className="flex items-center justify-between px-3 mt-1 mb-1">
|
||||
<div className="sn-account-menu-headline">Account</div>
|
||||
<div className="flex cursor-pointer" onClick={closeMenu}>
|
||||
<Icon type="close" className="color-neutral" />
|
||||
@@ -69,7 +72,7 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
||||
<div className="my-0.5 font-bold wrap">{user.email}</div>
|
||||
<span className="color-neutral">{application.getHost()}</span>
|
||||
</div>
|
||||
<div className="flex items-start justify-between px-3 mb-2">
|
||||
<div className="flex items-start justify-between px-3 mb-3">
|
||||
{isSyncingInProgress ? (
|
||||
<div className="flex items-center color-info font-semibold">
|
||||
<div className="sk-spinner w-5 h-5 mr-2 spinner-info"></div>
|
||||
@@ -106,12 +109,17 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="h-1px my-2 bg-border"></div>
|
||||
<Menu
|
||||
isOpen={appState.accountMenu.show}
|
||||
a11yLabel="General account menu"
|
||||
closeMenu={closeMenu}
|
||||
>
|
||||
<MenuItemSeparator />
|
||||
<WorkspaceSwitcherOption
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
appState={appState}
|
||||
/>
|
||||
<MenuItemSeparator />
|
||||
{user ? (
|
||||
<MenuItem
|
||||
type={MenuItemType.IconButton}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Icon } from '@/components/Icon';
|
||||
import { MenuItem, MenuItemType } from '@/components/Menu/MenuItem';
|
||||
import { KeyboardKey } from '@/services/ioService';
|
||||
import { ApplicationDescriptor } from '@standardnotes/snjs/dist/@types';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
|
||||
type Props = {
|
||||
descriptor: ApplicationDescriptor;
|
||||
onClick: () => void;
|
||||
onDelete: () => void;
|
||||
renameDescriptor: (label: string) => void;
|
||||
};
|
||||
|
||||
export const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
descriptor,
|
||||
onClick,
|
||||
onDelete,
|
||||
renameDescriptor,
|
||||
}) => {
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRenaming) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [isRenaming]);
|
||||
|
||||
const handleInputKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === KeyboardKey.Enter) {
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = (event: FocusEvent) => {
|
||||
const name = (event.target as HTMLInputElement).value;
|
||||
renameDescriptor(name);
|
||||
setIsRenaming(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
type={MenuItemType.RadioButton}
|
||||
className="sn-dropdown-item py-2 focus:bg-info-backdrop focus:shadow-none"
|
||||
onClick={onClick}
|
||||
checked={descriptor.primary}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
{isRenaming ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={descriptor.label}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
) : (
|
||||
<div>{descriptor.label}</div>
|
||||
)}
|
||||
{descriptor.primary && (
|
||||
<div>
|
||||
<button
|
||||
className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsRenaming((isRenaming) => !isRenaming);
|
||||
}}
|
||||
>
|
||||
<Icon type="pencil" className="sn-icon--mid color-neutral" />
|
||||
</button>
|
||||
<button
|
||||
className="w-5 h-5 p-0 border-0 bg-transparent hover:bg-contrast cursor-pointer"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Icon type="trash" className="sn-icon--mid color-danger" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { ApplicationDescriptor } from '@standardnotes/snjs';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { Icon } from '../../Icon';
|
||||
import { Menu } from '../../Menu/Menu';
|
||||
import { MenuItem, MenuItemSeparator, MenuItemType } from '../../Menu/MenuItem';
|
||||
import { WorkspaceMenuItem } from './WorkspaceMenuItem';
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
appState: AppState;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
|
||||
({ mainApplicationGroup, appState, isOpen }) => {
|
||||
const [applicationDescriptors, setApplicationDescriptors] = useState<
|
||||
ApplicationDescriptor[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const removeAppGroupObserver =
|
||||
mainApplicationGroup.addApplicationChangeObserver(() => {
|
||||
const applicationDescriptors = mainApplicationGroup.getDescriptors();
|
||||
setApplicationDescriptors(applicationDescriptors);
|
||||
});
|
||||
|
||||
return () => {
|
||||
removeAppGroupObserver();
|
||||
};
|
||||
}, [mainApplicationGroup]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
a11yLabel="Workspace switcher menu"
|
||||
className="px-0 focus:shadow-none"
|
||||
isOpen={isOpen}
|
||||
>
|
||||
{applicationDescriptors.map((descriptor) => (
|
||||
<WorkspaceMenuItem
|
||||
descriptor={descriptor}
|
||||
onDelete={() => {
|
||||
appState.accountMenu.setSigningOut(true);
|
||||
}}
|
||||
onClick={() => {
|
||||
mainApplicationGroup.loadApplicationForDescriptor(descriptor);
|
||||
}}
|
||||
renameDescriptor={(label: string) =>
|
||||
mainApplicationGroup.renameDescriptor(descriptor, label)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<MenuItemSeparator />
|
||||
<MenuItem
|
||||
type={MenuItemType.IconButton}
|
||||
onClick={() => {
|
||||
mainApplicationGroup.addNewApplication();
|
||||
}}
|
||||
>
|
||||
<Icon type="user-add" className="color-neutral mr-2" />
|
||||
Add another workspace
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/constants';
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import {
|
||||
calculateSubmenuStyle,
|
||||
SubmenuStyle,
|
||||
} from '@/utils/calculateSubmenuStyle';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { Icon } from '../../Icon';
|
||||
import { WorkspaceSwitcherMenu } from './WorkspaceSwitcherMenu';
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
appState: AppState;
|
||||
};
|
||||
|
||||
export const WorkspaceSwitcherOption: FunctionComponent<Props> = observer(
|
||||
({ mainApplicationGroup, appState }) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>();
|
||||
|
||||
const toggleMenu = () => {
|
||||
if (!isOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(buttonRef.current);
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition);
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setTimeout(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(
|
||||
buttonRef.current,
|
||||
menuRef.current
|
||||
);
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||
role="menuitem"
|
||||
onClick={toggleMenu}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||
Switch workspace
|
||||
</div>
|
||||
<Icon type="chevron-right" className="color-neutral" />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
appState={appState}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -9,6 +9,7 @@ import { SignInPane } from './SignIn';
|
||||
import { CreateAccount } from './CreateAccount';
|
||||
import { ConfirmPassword } from './ConfirmPassword';
|
||||
import { JSXInternal } from 'preact/src/jsx';
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
|
||||
export enum AccountMenuPane {
|
||||
GeneralMenu,
|
||||
@@ -21,18 +22,27 @@ type Props = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
onClickOutside: () => void;
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
};
|
||||
|
||||
type PaneSelectorProps = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
menuPane: AccountMenuPane;
|
||||
setMenuPane: (pane: AccountMenuPane) => void;
|
||||
closeMenu: () => void;
|
||||
};
|
||||
|
||||
const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
|
||||
({ application, appState, menuPane, setMenuPane, closeMenu }) => {
|
||||
({
|
||||
application,
|
||||
appState,
|
||||
menuPane,
|
||||
setMenuPane,
|
||||
closeMenu,
|
||||
mainApplicationGroup,
|
||||
}) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
@@ -42,6 +52,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
|
||||
<GeneralAccountMenu
|
||||
appState={appState}
|
||||
application={application}
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
setMenuPane={setMenuPane}
|
||||
closeMenu={closeMenu}
|
||||
/>
|
||||
@@ -81,7 +92,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
|
||||
);
|
||||
|
||||
export const AccountMenu: FunctionComponent<Props> = observer(
|
||||
({ application, appState, onClickOutside }) => {
|
||||
({ application, appState, onClickOutside, mainApplicationGroup }) => {
|
||||
const {
|
||||
currentPane,
|
||||
setCurrentPane,
|
||||
@@ -123,6 +134,7 @@ export const AccountMenu: FunctionComponent<Props> = observer(
|
||||
<MenuPaneSelector
|
||||
appState={appState}
|
||||
application={application}
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
menuPane={currentPane}
|
||||
setMenuPane={setCurrentPane}
|
||||
closeMenu={closeAccountMenu}
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { ApplicationDescriptor } from '@standardnotes/snjs';
|
||||
import { PureComponent } from '@/components/Abstract/PureComponent';
|
||||
import { JSX } from 'preact';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
mainApplicationGroup: ApplicationGroup;
|
||||
};
|
||||
|
||||
type State = {
|
||||
descriptors: ApplicationDescriptor[];
|
||||
editingDescriptor?: ApplicationDescriptor;
|
||||
};
|
||||
|
||||
export class AccountSwitcher extends PureComponent<Props, State> {
|
||||
private removeAppGroupObserver: any;
|
||||
activeApplication!: WebApplication;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props, props.application);
|
||||
this.removeAppGroupObserver =
|
||||
props.mainApplicationGroup.addApplicationChangeObserver(() => {
|
||||
this.activeApplication = props.mainApplicationGroup
|
||||
.primaryApplication as WebApplication;
|
||||
this.reloadApplications();
|
||||
});
|
||||
}
|
||||
|
||||
reloadApplications() {
|
||||
this.setState({
|
||||
descriptors: this.props.mainApplicationGroup.getDescriptors(),
|
||||
});
|
||||
}
|
||||
|
||||
addNewApplication = () => {
|
||||
this.dismiss();
|
||||
this.props.mainApplicationGroup.addNewApplication();
|
||||
};
|
||||
|
||||
selectDescriptor = (descriptor: ApplicationDescriptor) => {
|
||||
this.dismiss();
|
||||
this.props.mainApplicationGroup.loadApplicationForDescriptor(descriptor);
|
||||
};
|
||||
|
||||
inputForDescriptor(descriptor: ApplicationDescriptor) {
|
||||
return document.getElementById(`input-${descriptor.identifier}`);
|
||||
}
|
||||
|
||||
renameDescriptor = (event: Event, descriptor: ApplicationDescriptor) => {
|
||||
event.stopPropagation();
|
||||
|
||||
this.setState({ editingDescriptor: descriptor });
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputForDescriptor(descriptor)?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
submitRename = () => {
|
||||
this.props.mainApplicationGroup.renameDescriptor(
|
||||
this.state.editingDescriptor!,
|
||||
this.state.editingDescriptor!.label
|
||||
);
|
||||
this.setState({ editingDescriptor: undefined });
|
||||
};
|
||||
|
||||
deinit() {
|
||||
super.deinit();
|
||||
this.removeAppGroupObserver();
|
||||
this.removeAppGroupObserver = undefined;
|
||||
}
|
||||
|
||||
onDescriptorInputChange = (
|
||||
descriptor: ApplicationDescriptor,
|
||||
{ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>
|
||||
) => {
|
||||
descriptor.label = currentTarget.value;
|
||||
};
|
||||
|
||||
dismiss = () => {
|
||||
this.dismissModal();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="sk-modal">
|
||||
<div onClick={this.dismiss} className="sk-modal-background" />
|
||||
<div id="account-switcher" className="sk-modal-content">
|
||||
<div className="sn-component">
|
||||
<div id="menu-panel" className="sk-menu-panel">
|
||||
<div className="sk-menu-panel-header">
|
||||
<div className="sk-menu-panel-column">
|
||||
<div className="sk-menu-panel-header-title">
|
||||
Account Switcher
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-menu-panel-column">
|
||||
<a onClick={this.addNewApplication} className="sk-label info">
|
||||
Add Account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.descriptors.map((descriptor) => {
|
||||
return (
|
||||
<div
|
||||
key={descriptor.identifier}
|
||||
onClick={() => this.selectDescriptor(descriptor)}
|
||||
className="sk-menu-panel-row"
|
||||
>
|
||||
<div className="sk-menu-panel-column stretch">
|
||||
<div className="left">
|
||||
{descriptor.identifier ==
|
||||
this.activeApplication.identifier && (
|
||||
<div className="sk-menu-panel-column">
|
||||
<div className="sk-circle small success" />
|
||||
</div>
|
||||
)}
|
||||
<div className="sk-menu-panel-column stretch">
|
||||
<input
|
||||
value={descriptor.label}
|
||||
disabled={
|
||||
descriptor !== this.state.editingDescriptor
|
||||
}
|
||||
onChange={(event) =>
|
||||
this.onDescriptorInputChange(descriptor, event)
|
||||
}
|
||||
onKeyUp={(event) =>
|
||||
event.keyCode == 13 && this.submitRename()
|
||||
}
|
||||
id={`input-${descriptor.identifier}`}
|
||||
spellcheck={false}
|
||||
className="sk-label clickable"
|
||||
/>
|
||||
|
||||
{descriptor.identifier ==
|
||||
this.activeApplication.identifier && (
|
||||
<div className="sk-sublabel">
|
||||
Current Application
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{descriptor.identifier ==
|
||||
this.activeApplication.identifier && (
|
||||
<div className="sk-menu-panel-column">
|
||||
<button
|
||||
onClick={(event) =>
|
||||
this.renameDescriptor(event, descriptor)
|
||||
}
|
||||
className="sn-button success"
|
||||
>
|
||||
Rename
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import { Icon } from './Icon';
|
||||
import { QuickSettingsMenu } from './QuickSettingsMenu/QuickSettingsMenu';
|
||||
import { SyncResolutionMenu } from './SyncResolutionMenu';
|
||||
import { Fragment, render } from 'preact';
|
||||
import { AccountSwitcher } from './AccountSwitcher';
|
||||
|
||||
/**
|
||||
* Disable before production release.
|
||||
@@ -43,7 +42,6 @@ type State = {
|
||||
dataUpgradeAvailable: boolean;
|
||||
hasPasscode: boolean;
|
||||
descriptors: ApplicationDescriptor[];
|
||||
hasAccountSwitcher: boolean;
|
||||
showBetaWarning: boolean;
|
||||
showSyncResolution: boolean;
|
||||
newUpdateAvailable: boolean;
|
||||
@@ -70,7 +68,6 @@ export class Footer extends PureComponent<Props, State> {
|
||||
dataUpgradeAvailable: false,
|
||||
hasPasscode: false,
|
||||
descriptors: props.applicationGroup.getDescriptors(),
|
||||
hasAccountSwitcher: false,
|
||||
showBetaWarning: false,
|
||||
showSyncResolution: false,
|
||||
newUpdateAvailable: false,
|
||||
@@ -100,7 +97,6 @@ export class Footer extends PureComponent<Props, State> {
|
||||
arbitraryStatusMessage: message,
|
||||
});
|
||||
});
|
||||
this.loadAccountSwitcherState();
|
||||
this.autorun(() => {
|
||||
const showBetaWarning = this.appState.showBetaWarning;
|
||||
this.setState({
|
||||
@@ -111,18 +107,6 @@ export class Footer extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
loadAccountSwitcherState() {
|
||||
const stringValue = localStorage.getItem(ACCOUNT_SWITCHER_FEATURE_KEY);
|
||||
if (!stringValue && ACCOUNT_SWITCHER_ENABLED) {
|
||||
/** Enable permanently for this user so they don't lose the feature after its disabled */
|
||||
localStorage.setItem(ACCOUNT_SWITCHER_FEATURE_KEY, JSON.stringify(true));
|
||||
}
|
||||
const hasAccountSwitcher = stringValue
|
||||
? JSON.parse(stringValue)
|
||||
: ACCOUNT_SWITCHER_ENABLED;
|
||||
this.setState({ hasAccountSwitcher });
|
||||
}
|
||||
|
||||
reloadUpgradeStatus() {
|
||||
this.application.checkForSecurityUpdate().then((available) => {
|
||||
this.setState({
|
||||
@@ -333,16 +317,6 @@ export class Footer extends PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
accountSwitcherClickHandler = () => {
|
||||
render(
|
||||
<AccountSwitcher
|
||||
application={this.application}
|
||||
mainApplicationGroup={this.props.applicationGroup}
|
||||
/>,
|
||||
document.body.appendChild(document.createElement('div'))
|
||||
);
|
||||
};
|
||||
|
||||
accountMenuClickHandler = () => {
|
||||
this.appState.quickSettingsMenu.closeQuickSettingsMenu();
|
||||
this.appState.accountMenu.toggleShow();
|
||||
@@ -429,6 +403,7 @@ export class Footer extends PureComponent<Props, State> {
|
||||
onClickOutside={this.clickOutsideAccountMenu}
|
||||
appState={this.appState}
|
||||
application={this.application}
|
||||
mainApplicationGroup={this.props.applicationGroup}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -522,24 +497,6 @@ export class Footer extends PureComponent<Props, State> {
|
||||
<div className="sk-label">Offline</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.hasAccountSwitcher && (
|
||||
<Fragment>
|
||||
<div className="sk-app-bar-item border" />
|
||||
<div
|
||||
onClick={this.accountSwitcherClickHandler}
|
||||
className="sk-app-bar-item"
|
||||
>
|
||||
<div
|
||||
className={
|
||||
(this.state.hasPasscode ? 'alone' : '') +
|
||||
' flex items-center'
|
||||
}
|
||||
>
|
||||
<Icon type="user-switch" />
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
{this.state.hasPasscode && (
|
||||
<Fragment>
|
||||
<div className="sk-app-bar-item border" />
|
||||
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ClearCircleFilledIcon,
|
||||
CloseIcon,
|
||||
CloudOffIcon,
|
||||
ClearCircleFilledIcon,
|
||||
CodeIcon,
|
||||
CopyIcon,
|
||||
DashboardIcon,
|
||||
@@ -83,6 +83,7 @@ import {
|
||||
TuneIcon,
|
||||
UnarchiveIcon,
|
||||
UnpinIcon,
|
||||
UserAddIcon,
|
||||
UserIcon,
|
||||
UserSwitch,
|
||||
WarningIcon,
|
||||
@@ -99,8 +100,8 @@ export const ICONS = {
|
||||
'check-circle': CheckCircleIcon,
|
||||
'chevron-down': ChevronDownIcon,
|
||||
'chevron-right': ChevronRightIcon,
|
||||
'cloud-off': CloudOffIcon,
|
||||
'clear-circle-filled': ClearCircleFilledIcon,
|
||||
'cloud-off': CloudOffIcon,
|
||||
'eye-off': EyeOffIcon,
|
||||
'file-doc': FileDocIcon,
|
||||
'file-image': FileImageIcon,
|
||||
@@ -127,6 +128,7 @@ export const ICONS = {
|
||||
'rich-text': RichTextIcon,
|
||||
'trash-filled': TrashFilledIcon,
|
||||
'trash-sweep': TrashSweepIcon,
|
||||
'user-add': UserAddIcon,
|
||||
'user-switch': UserSwitch,
|
||||
accessibility: AccessibilityIcon,
|
||||
add: AddIcon,
|
||||
|
||||
@@ -73,8 +73,12 @@ export const Menu: FunctionComponent<MenuProps> = ({
|
||||
child: ComponentChild,
|
||||
index: number,
|
||||
array: ComponentChild[]
|
||||
) => {
|
||||
if (!child) return;
|
||||
): ComponentChild => {
|
||||
if (!child || (Array.isArray(child) && child.length < 1)) return;
|
||||
|
||||
if (Array.isArray(child)) {
|
||||
return child.map(mapMenuItems);
|
||||
}
|
||||
|
||||
const _child = child as VNode<unknown>;
|
||||
const isFirstMenuItem =
|
||||
|
||||
@@ -79,7 +79,7 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
|
||||
<div
|
||||
className={`pseudo-radio-btn ${
|
||||
checked ? 'pseudo-radio-btn--checked' : ''
|
||||
} mr-2`}
|
||||
} mr-2 flex-shrink-0`}
|
||||
></div>
|
||||
) : null}
|
||||
{children}
|
||||
|
||||
@@ -14,7 +14,7 @@ export type SubmenuStyle = {
|
||||
|
||||
export const calculateSubmenuStyle = (
|
||||
button: HTMLButtonElement | null,
|
||||
menu?: HTMLDivElement | null
|
||||
menu?: HTMLDivElement | HTMLMenuElement | null
|
||||
): SubmenuStyle | undefined => {
|
||||
const defaultFontSize = window.getComputedStyle(
|
||||
document.documentElement
|
||||
|
||||
Reference in New Issue
Block a user