Chore: Remove Sidebar from Blaze (#27571)

Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/27600/head
Douglas Fabris 3 years ago committed by GitHub
parent acb4cd3046
commit 86ea45e992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 71
      apps/meteor/app/theme/client/imports/components/sidebar/rooms-list.css
  2. 51
      apps/meteor/app/theme/client/imports/components/sidebar/sidebar-flex.css
  3. 130
      apps/meteor/app/theme/client/imports/components/sidebar/sidebar.css
  4. 4
      apps/meteor/app/theme/client/main.css
  5. 0
      apps/meteor/app/ui-sidenav/README.md
  6. 3
      apps/meteor/app/ui-sidenav/client/index.js
  7. 23
      apps/meteor/app/ui-sidenav/client/sideNav.html
  8. 126
      apps/meteor/app/ui-sidenav/client/sideNav.js
  9. 1
      apps/meteor/app/ui-sidenav/client/userPresence.html
  10. 59
      apps/meteor/app/ui-sidenav/client/userPresence.js
  11. 1
      apps/meteor/app/ui-sidenav/index.js
  12. 2
      apps/meteor/app/ui-utils/client/index.ts
  13. 26
      apps/meteor/app/ui-utils/client/lib/AccountBox.ts
  14. 114
      apps/meteor/app/ui-utils/client/lib/SideNav.ts
  15. 121
      apps/meteor/app/ui-utils/client/lib/menu.js
  16. 1
      apps/meteor/app/ui/client/index.ts
  17. 21
      apps/meteor/app/ui/client/lib/menu.js
  18. 5
      apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx
  19. 35
      apps/meteor/client/components/Sidebar/Header.tsx
  20. 1
      apps/meteor/client/importPackages.ts
  21. 25
      apps/meteor/client/lib/createRouteGroup.tsx
  22. 31
      apps/meteor/client/providers/LayoutProvider.tsx
  23. 89
      apps/meteor/client/sidebar/RoomList/RoomList.tsx
  24. 4
      apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx
  25. 14
      apps/meteor/client/sidebar/Sidebar.stories.tsx
  26. 127
      apps/meteor/client/sidebar/Sidebar.tsx
  27. 16
      apps/meteor/client/sidebar/SidebarPortal.tsx
  28. 1
      apps/meteor/client/sidebar/index.ts
  29. 11
      apps/meteor/client/templates.ts
  30. 19
      apps/meteor/client/views/account/AccountRouter.tsx
  31. 29
      apps/meteor/client/views/account/AccountSidebar.tsx
  32. 19
      apps/meteor/client/views/admin/AdministrationLayout.tsx
  33. 24
      apps/meteor/client/views/admin/sidebar/AdminSidebar.tsx
  34. 20
      apps/meteor/client/views/omnichannel/OmnichannelRouter.tsx
  35. 25
      apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx
  36. 37
      apps/meteor/client/views/root/MainLayout/LayoutWithSidebar.tsx
  37. 2
      apps/meteor/definition/externals/meteor/kadira-flow-router.d.ts
  38. 3
      apps/meteor/tests/unit/client/components/AdministrationList/AdministrationList.spec.tsx
  39. 12
      apps/meteor/tests/unit/client/components/AdministrationList/AdministrationModelList.spec.tsx
  40. 2
      ee/packages/ui-theming/src/SidebarPaletteStyleTag.tsx
  41. 16
      packages/ui-contexts/src/LayoutContext.ts

@ -1,71 +0,0 @@
.rooms-list {
position: relative;
display: flex;
overflow-x: hidden;
overflow-y: hidden;
flex: 1 1 auto;
height: 100%;
&--embedded {
margin-top: 2rem;
}
&__list:not(:last-child) {
margin-bottom: 22px;
}
&__type {
display: flex;
flex-direction: row;
padding: 0 var(--sidebar-default-padding) 1rem var(--sidebar-default-padding);
color: var(--rooms-list-title-color);
font-size: var(--rooms-list-title-text-size);
align-items: center;
justify-content: space-between;
&-text--livechat {
flex: 1;
}
}
&__empty-room {
padding: 0 var(--sidebar-default-padding);
color: var(--rooms-list-empty-text-color);
font-size: var(--rooms-list-empty-text-size);
}
&__toolbar-search {
position: absolute;
z-index: 10;
left: 0;
overflow-y: scroll;
height: 100%;
background-color: var(--sidebar-background);
padding-block-start: 12px;
}
}
@media (width <= 400px) {
padding: 0 calc(var(--sidebar-small-default-padding) - 4px);
.rooms-list {
&__type,
&__empty-room {
padding: 0 calc(var(--sidebar-small-default-padding) - 4px) 0.5rem calc(var(--sidebar-small-default-padding) - 4px);
}
}
}

@ -1,51 +0,0 @@
.sidebar-flex {
&__header {
display: flex;
padding: var(--sidebar-default-padding);
}
&__title {
flex: 1;
font-size: 1rem;
font-weight: 400;
}
&__close-button {
font-size: 18px;
}
&__search {
padding: 0 1rem;
& .rc-input {
&__wrapper {
color: var(--sidebar-flex-search-placeholder-color);
}
&__icon {
left: 0.5rem;
}
&__element {
padding-left: 2.25rem;
border: 0;
background-color: var(--sidebar-flex-search-background);
&::placeholder {
color: var(--sidebar-flex-search-placeholder-color);
}
}
}
}
}
@media (width <= 400px) {
.sidebar-flex {
&__back-button {
padding: 1rem calc(var(--sidebar-small-default-padding) - 8px) 1.5rem;
}
}
}

@ -1,130 +0,0 @@
.sidebar {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
flex: 0 0 var(--sidebar-width);
width: var(--sidebar-width);
max-width: var(--sidebar-width);
height: 100%;
user-select: none;
transition: transform 0.3s;
background-color: var(--sidebar-background);
&-wrap {
position: absolute;
z-index: 1;
top: 0;
left: 0;
height: 100%;
user-select: none;
transition: opacity 0.3s;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
touch-action: pan-y;
-webkit-user-drag: none;
}
& .wrapper-unread {
position: relative;
z-index: 2;
& .unread-rooms {
position: absolute;
left: 50%;
overflow: hidden;
min-width: 120px;
max-width: 100%;
padding: 8px var(--sidebar-small-default-padding);
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
animation: fade 0.3s;
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
border-radius: 25px;
&.bottom-unread-rooms {
bottom: 0;
}
&.top-unread-rooms {
top: 0;
}
}
}
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (width < 768px) {
.sidebar {
position: absolute;
user-select: none;
transform: translate3d(-100%, 0, 0);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
touch-action: pan-y;
-webkit-user-drag: none;
will-change: transform;
}
.rtl .sidebar {
transform: translate3d(200%, 0, 0);
}
}
@media (width <= 400px) {
.sidebar {
flex: 0 0 var(--sidebar-small-width);
width: var(--sidebar-small-width);
max-width: var(--sidebar-small-width);
&__footer {
display: none;
}
&:not(&--light) {
transform: translate3d(-100%, 0, 0);
}
}
.rtl .sidebar {
transform: translate3d(200%, 0, 0);
}
}
@media (min-width: 1372px) { /* 1440px -68px (eletron menu) */
.sidebar {
flex: 0 0 20%;
width: 20%;
max-width: 20%;
}
}

@ -19,10 +19,6 @@
/* Typography */
@import 'imports/general/typography.css';
/* Sidebar */
@import 'imports/components/sidebar/sidebar.css';
@import 'imports/components/sidebar/rooms-list.css';
/* Main */
@import 'imports/components/flex-nav.css';
@import 'imports/components/header.css';

@ -1,3 +0,0 @@
import './sideNav.html';
import './sideNav';
import './userPresence';

@ -1,23 +0,0 @@
<template name="sideNav">
<aside class="rcx-sidebar sidebar sidebar--main sidebar--{{sidebarViewMode}} {{#if sidebarHideAvatar}}sidebar--hide-avatar{{/if}}" role="navigation" data-qa-opened="{{dataQa}}">
{{> sidebarHeader }}
<div class="wrapper-unread">
<div class="unread-rooms background-primary-action-color color-primary-action-contrast top-unread-rooms hidden">
<i class="icon-up-big"></i> {{_ "More_unreads"}}
</div>
</div>
<div class="rooms-list sidebar--custom-colors" aria-label="{{_ "Channels"}}" role="region">
{{> sidebarChats }}
</div>
<div class="wrapper-unread">
<div class="unread-rooms background-primary-action-color color-primary-action-contrast bottom-unread-rooms hidden">
<i class="icon-down-big"></i> {{_ "More_unreads"}}
</div>
</div>
<div class="flex-nav rcx-sidebar animated-hidden">
{{> Template.dynamic template=flexTemplate data=flexData }}
</div>
{{> sidebarFooter}}
</aside>
<div class="sidebar-wrap"></div>
</template>

@ -1,126 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { SideNav, menu } from '../../ui-utils';
import { settings } from '../../settings';
import { getUserPreference } from '../../utils';
import { Users } from '../../models/client';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Template.sideNav.helpers({
dataQa() {
return Template.instance().menuState.get() === 'opened';
},
flexTemplate() {
return SideNav.getFlex().template;
},
flexData() {
return SideNav.getFlex().data;
},
roomType() {
return roomCoordinator.getSortedTypes().map(({ config }) => ({
template: config.customTemplate || 'roomList',
data: {
header: config.header,
identifier: config.identifier,
label: config.label,
},
}));
},
loggedInUser() {
return !!Meteor.userId();
},
sidebarViewMode() {
const viewMode = getUserPreference(Meteor.userId(), 'sidebarViewMode');
return viewMode || 'condensed';
},
sidebarHideAvatar() {
return !getUserPreference(Meteor.userId(), 'sidebarDisplayAvatar');
},
});
Template.sideNav.events({
'click .close-flex'() {
return SideNav.closeFlex();
},
'dropped .sidebar'(e) {
return e.preventDefault();
},
'mouseenter .sidebar-item__link'(e) {
const element = e.currentTarget;
setTimeout(() => {
const ellipsedElement = element.querySelector('.sidebar-item__ellipsis');
const isTextEllipsed = ellipsedElement.offsetWidth < ellipsedElement.scrollWidth;
if (isTextEllipsed) {
element.setAttribute('title', element.getAttribute('aria-label'));
} else {
element.removeAttribute('title');
}
}, 0);
},
});
const redirectToDefaultChannelIfNeeded = () => {
const needToBeRedirect = () => ['/', '/home'].includes(FlowRouter.current().path);
Tracker.autorun((c) => {
const firstChannelAfterLogin = settings.get('First_Channel_After_Login');
if (!needToBeRedirect()) {
return c.stop();
}
if (!firstChannelAfterLogin) {
return c.stop();
}
const room = roomCoordinator.getRoomDirectives('c')?.findRoom(firstChannelAfterLogin);
if (!room) {
return;
}
c.stop();
FlowRouter.go(`/channel/${firstChannelAfterLogin}`);
});
};
Template.sideNav.onRendered(function () {
SideNav.init();
menu.init();
this.stopMenuListener = menu.on('change', () => {
this.menuState.set(menu.isOpen() ? 'opened' : 'closed');
});
redirectToDefaultChannelIfNeeded();
});
Template.sideNav.onDestroyed(function () {
this.stopMenuListener();
});
Template.sideNav.onCreated(function () {
this.groupedByType = new ReactiveVar(false);
this.menuState = new ReactiveVar(menu.isOpen() ? 'opened' : 'closed');
this.autorun(() => {
const user = Users.findOne(Meteor.userId(), {
fields: {
'settings.preferences.sidebarGroupByType': 1,
},
});
const userPref = getUserPreference(user, 'sidebarGroupByType');
this.groupedByType.set(userPref || settings.get('UI_Group_Channels_By_Type'));
});
});

@ -1 +0,0 @@
<template name="userPresence">{{> Template.contentBlock}}</template>

@ -1,59 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { Presence } from '../../../client/lib/presence';
import './userPresence.html';
const data = new Map();
const options = {
threshold: 0.1,
};
let lastEntries = [];
const handleEntries = function (entries) {
lastEntries = entries.filter(({ isIntersecting }) => isIntersecting);
lastEntries.forEach(async (entry) => {
const { uid } = data.get(entry.target);
Presence.get(uid);
});
};
const featureExists = !!window.IntersectionObserver;
const observer = featureExists && new IntersectionObserver(handleEntries, options);
Tracker.autorun(() => {
// Only clear statuses on disconnect, prevent process it on reconnect again
const isConnected = Meteor.status().connected;
if (!Meteor.userId() || !isConnected) {
Presence.reset();
return Meteor.users.update({ status: { $exists: true } }, { $unset: { status: true } }, { multi: true });
}
Presence.restart();
if (featureExists) {
for (const node of data.keys()) {
observer.unobserve(node);
observer.observe(node);
}
return;
}
Accounts.onLogout(() => {
Presence.reset();
});
});
Template.userPresence.onRendered(function () {
if (!this.data || !this.data.uid) {
return;
}
data.set(this.firstNode, this.data);
if (featureExists) {
return observer.observe(this.firstNode);
}
});

@ -1 +0,0 @@
export * from './client/index';

@ -1,9 +1,7 @@
import './lib/messageActionDefault';
export { modal } from './lib/modal';
export { SideNav } from './lib/SideNav';
export { AccountBox } from './lib/AccountBox';
export { menu } from './lib/menu';
export { MessageAction } from './lib/MessageAction';
export { messageBox } from './lib/messageBox';
export { popover } from './lib/popover';

@ -6,7 +6,6 @@ import type { TranslationKey } from '@rocket.chat/ui-contexts';
import type { Icon } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { SideNav } from './SideNav';
import { applyDropdownActionButtonFilters } from '../../../ui-message/client/actionButtons/lib/applyButtonFilters';
import { APIClient } from '../../../utils/client';
@ -33,35 +32,10 @@ export const isAppAccountBoxItem = (item: IAppAccountBoxItem | AccountBoxItem):
export class AccountBoxBase {
private items = new ReactiveVar<IAppAccountBoxItem[]>([]);
private status = 0;
public setStatus(status: UserStatus, statusText: string): any {
return APIClient.post('/v1/users.setStatus', { status, message: statusText });
}
public open(): void {
if (SideNav.flexStatus()) {
SideNav.closeFlex();
return;
}
this.status = 1;
}
public close(): void {
this.status = 0;
}
public toggle(): Window | null | void {
if (this.status) {
return this.close();
}
return this.open();
}
public openFlex(): void {
this.status = 0;
}
public async addItem(newItem: IAppAccountBoxItem): Promise<void> {
Tracker.nonreactive(() => {
const actual = this.items.get();

@ -1,114 +0,0 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Emitter } from '@rocket.chat/emitter';
import { Subscriptions } from '../../../models/client';
import { RoomManager } from '../../../../client/lib/RoomManager';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
export const SideNav = new (class extends Emitter<{
changed: undefined;
}> {
private opened = false;
private initiated = false;
private openQueue: {
config: {
template?: string;
data: Record<string, unknown>;
};
callback: () => void;
}[] = [];
private animating = false;
private sideNav: JQuery<HTMLElement>;
private flexNav: JQuery<HTMLElement>;
toggleFlex(status: 1 | -1, callback?: () => void): void {
if (this.animating === true) {
return;
}
this.animating = true;
if (status === -1 || (status !== 1 && this.opened)) {
this.opened = false;
this.flexNav.addClass('animated-hidden');
} else {
this.opened = true;
this.flexNav.removeClass('animated-hidden');
}
this.emit('changed');
!this.opened && this.setFlex();
this.animating = false;
typeof callback === 'function' && callback();
}
closeFlex(callback: () => void = (): void => undefined): void {
const routeName = FlowRouter.current().route?.name;
if (!routeName || !roomCoordinator.isRouteNameKnown(routeName)) {
const subscription = Subscriptions.findOne({ rid: RoomManager.lastRid });
if (subscription) {
roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
} else {
FlowRouter.go('home');
}
}
if (this.animating === true) {
return;
}
this.toggleFlex(-1, callback);
}
flexStatus(): boolean {
return this.opened;
}
setFlex(template?: string, data = {}): void {
Session.set('flex-nav-template', template);
return Session.set('flex-nav-data', data);
}
getFlex(): {
template?: string;
data: Record<string, unknown>;
} {
return {
template: Session.get('flex-nav-template'),
data: Session.get('flex-nav-data'),
};
}
openFlex(callback = (): void => undefined): void {
if (!this.initiated) {
this.openQueue.push({
config: this.getFlex(),
callback,
});
return;
}
if (this.animating === true) {
return;
}
this.toggleFlex(1, callback);
}
init(): void {
this.sideNav = $('.sidebar');
this.flexNav = this.sideNav.find('.flex-nav');
this.setFlex('');
this.initiated = true;
if (this.openQueue.length > 0) {
this.openQueue.forEach((item) => {
this.setFlex(item.config.template, item.config.data);
return this.openFlex(item.callback);
});
this.openQueue = [];
}
}
})();

@ -1,121 +0,0 @@
import { Session } from 'meteor/session';
import { Meteor } from 'meteor/meteor';
import { Emitter } from '@rocket.chat/emitter';
import { isRTLScriptLanguage } from '../../../../client/lib/utils/isRTLScriptLanguage';
const sideNavW = 280;
const map = (x, in_min, in_max, out_min, out_max) => ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min;
export const menu = new (class extends Emitter {
constructor() {
super();
this._open = false;
this.sideNavW = sideNavW;
}
get isRtl() {
return isRTLScriptLanguage(Meteor._localStorage.getItem('userLanguage'));
}
translate(diff, width = sideNavW) {
if (diff === undefined) {
diff = this.isRtl ? -1 * sideNavW : sideNavW;
}
this.sidebarWrap.css('width', '100%');
this.wrapper.css('overflow', 'hidden');
this.sidebarWrap.css('background-color', '#000');
this.sidebarWrap.css('opacity', map(Math.abs(diff) / width, 0, 1, -0.1, 0.8).toFixed(2));
this.isRtl
? this.sidebar.css('transform', `translate3d(${(sideNavW + diff).toFixed(3)}px, 0 , 0)`)
: this.sidebar.css('transform', `translate3d(${(diff - sideNavW).toFixed(3)}px, 0 , 0)`);
}
init() {
this.menu = $('.sidebar');
this.sidebar = this.menu;
this.sidebarWrap = $('.sidebar-wrap');
this.wrapper = $('.messages-box > .wrapper');
const ignore = (fn) => (event) => document.body.clientWidth <= 780 && fn(event);
this.sidebarWrap.on(
'click',
ignore((e) => {
e.target === this.sidebarWrap[0] && this.isOpen() && this.emit('clickOut', e);
}),
);
this.on('close', () => {
this.sidebarWrap.css('width', '');
// this.sidebarWrap.css('z-index', '');
this.sidebarWrap.css('background-color', '');
this.sidebar.css('transform', '');
this.sidebar.css('box-shadow', '');
this.sidebar.css('transition', '');
this.sidebarWrap.css('transition', '');
this.wrapper && this.wrapper.css('overflow', '');
});
this.on(
'open',
ignore(() => {
this.sidebar.css('box-shadow', '0 0 15px 1px rgba(0,0,0,.3)');
// this.sidebarWrap.css('z-index', '9998');
this.translate();
}),
);
this.mainContent = $('.main-content');
this.list = $('.rooms-list');
this._open = false;
Session.set('isMenuOpen', this._open);
}
closePopover() {
return this.menu.find('[data-popover="anchor"]:checked').prop('checked', false).length > 0;
}
isOpen() {
return Session.get('isMenuOpen');
}
open() {
this._open = true;
Session.set('isMenuOpen', this._open);
this.emit('change');
this.emit('open');
}
close() {
this._open = false;
Session.set('isMenuOpen', this._open);
this.emit('change');
this.emit('close');
}
toggle() {
return this.isOpen() ? this.close() : this.open();
}
})();
let passClosePopover = false;
menu.on('clickOut', function () {
if (!menu.closePopover()) {
passClosePopover = true;
menu.close();
}
});
menu.on('close', function () {
if (!menu.sidebar) {
return;
}
menu.sidebar.css('transition', '');
menu.sidebarWrap.css('transition', '');
if (passClosePopover) {
passClosePopover = false;
return;
}
menu.closePopover();
});

@ -1,7 +1,6 @@
import './lib/accounts';
import './lib/collections';
import './lib/iframeCommands';
import './lib/menu';
import './lib/parentTemplate';
import './lib/codeMirror';
import './views/app/roomSearch.html';

@ -1,21 +0,0 @@
import _ from 'underscore';
import { menu } from '../../../ui-utils';
window.addEventListener(
'resize',
_.debounce(
(() => {
let lastState = window.matchMedia('(min-width: 780px)').matches ? 'mini' : 'large';
menu.close();
return () => {
const futureState = window.matchMedia('(min-width: 780px)').matches ? 'mini' : 'large';
if (lastState !== futureState) {
lastState = futureState;
menu.close();
}
};
})(),
100,
),
);

@ -5,7 +5,6 @@ import type { FC } from 'react';
import React from 'react';
import { userHasAllPermission } from '../../../app/authorization/client';
import { SideNav } from '../../../app/ui-utils/client';
import type { AccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox';
import { getUpgradeTabLabel, isFullyFeature } from '../../../lib/upgradeTab';
import { useUpgradeTabParams } from '../../views/hooks/useUpgradeTabParams';
@ -75,10 +74,6 @@ const AdministrationModelList: FC<AdministrationModelListProps> = ({ accountBoxI
if (item.href) {
FlowRouter.go(item.href);
}
if (item.sideNav) {
SideNav.setFlex(item.sideNav);
SideNav.openFlex();
}
closeList();
};

@ -1,4 +1,5 @@
import { Box, IconButton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, ReactNode } from 'react';
import React from 'react';
@ -7,20 +8,24 @@ type HeaderProps = {
onClose?: () => void;
};
const Header: FC<HeaderProps> = ({ title, onClose, children, ...props }) => (
<Box is='header' display='flex' flexDirection='column' pb='x16' {...props}>
{(title || onClose) && (
<Box display='flex' flexDirection='row' alignItems='center' pi='x24' justifyContent='space-between' flexGrow={1}>
{title && (
<Box fontSize='p2' fontWeight='p2' flexShrink={1} withTruncatedText>
{title}
</Box>
)}
{onClose && <IconButton small icon='cross' onClick={onClose} />}
</Box>
)}
{children}
</Box>
);
const Header: FC<HeaderProps> = ({ title, onClose, children, ...props }) => {
const t = useTranslation();
return (
<Box is='header' display='flex' flexDirection='column' pb='x16' {...props}>
{(title || onClose) && (
<Box display='flex' flexDirection='row' alignItems='center' pi='x24' justifyContent='space-between' flexGrow={1}>
{title && (
<Box fontSize='p2' fontWeight='p2' flexShrink={1} withTruncatedText>
{title}
</Box>
)}
{onClose && <IconButton title={t('Close')} small icon='cross' onClick={onClose} />}
</Box>
)}
{children}
</Box>
);
};
export default Header;

@ -58,7 +58,6 @@ import '../app/ui-clean-history/client';
import '../app/ui-login';
import '../app/ui-master/client';
import '../app/ui-message/client';
import '../app/ui-sidenav/client';
import '../app/ui-vrecord/client';
import '../app/videobridge/client';
import '../app/webdav/client';

@ -1,4 +1,4 @@
import type { Group, RouteOptions } from 'meteor/kadira:flow-router';
import type { Context, Current, Group, RouteOptions } from 'meteor/kadira:flow-router';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
@ -8,6 +8,8 @@ import React from 'react';
import MainLayout from '../views/root/MainLayout';
import { appLayout } from './appLayout';
let oldRoute: Current;
const registerLazyComponentRoute = (
routeGroup: Group,
RouterComponent: ElementType<{
@ -28,7 +30,7 @@ const registerLazyComponentRoute = (
const enabled = new ReactiveVar(ready ? true : undefined);
let computation: Tracker.Computation | undefined;
const handleEnter = (_context: unknown, _redirect: (pathDef: string) => void, stop: () => void): void => {
const handleEnter = (_context: Context, _redirect: (pathDef: string) => void, stop: () => void): void => {
const _enabled = Tracker.nonreactive(() => enabled.get());
if (_enabled === false) {
stop();
@ -44,8 +46,13 @@ const registerLazyComponentRoute = (
});
};
const handleExit = (): void => {
const handleExit = (context: Context): void => {
computation?.stop();
if (context.route.name === context.oldRoute?.name) {
return;
}
oldRoute?.route?.name && FlowRouter.go(oldRoute.route.name, oldRoute.params, oldRoute.queryParams);
};
routeGroup.route(path, {
@ -127,3 +134,15 @@ export const createRouteGroup = (
return registerRoute;
};
Tracker.autorun(
(() => {
let oldName: string;
return () => {
const name = FlowRouter.getRouteName();
if (oldName !== name) {
oldRoute = FlowRouter.current();
}
};
})(),
);

@ -1,25 +1,38 @@
import { useBreakpoints } from '@rocket.chat/fuselage-hooks';
import { LayoutContext, useQueryStringParameter, useSetting } from '@rocket.chat/ui-contexts';
import { LayoutContext, useQueryStringParameter, useRoute, useSetting } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useMemo } from 'react';
import { menu } from '../../app/ui-utils/client';
import React, { useMemo, useState, useEffect } from 'react';
const LayoutProvider: FC = ({ children }) => {
const showTopNavbarEmbeddedLayout = Boolean(useSetting('UI_Show_top_navbar_embedded_layout'));
const [isCollapsed, setIsCollapsed] = useState(false);
const layout = useQueryStringParameter('layout');
const isEmbedded = layout === 'embedded';
const breakpoints = useBreakpoints();
// ["xs", "sm", "md", "lg", "xl", xxl"]
const breakpoints = useBreakpoints(); // ["xs", "sm", "md", "lg", "xl", xxl"]
const isMobile = !breakpoints.includes('md');
useEffect(() => {
setIsCollapsed(isMobile);
}, [isMobile]);
const routeHome = useRoute('home');
return (
<LayoutContext.Provider
children={children}
value={useMemo(
() => ({
isMobile: !breakpoints.includes('md'),
isMobile,
isEmbedded,
showTopNavbarEmbeddedLayout,
sidebar: menu,
sidebar: {
isCollapsed,
toggle: () => setIsCollapsed((isCollapsed) => !isCollapsed),
collapse: () => setIsCollapsed(true),
expand: () => setIsCollapsed(false),
close: () => (isEmbedded ? setIsCollapsed(true) : routeHome.push()),
},
size: {
sidebar: '240px',
// eslint-disable-next-line no-nested-ternary
@ -29,7 +42,7 @@ const LayoutProvider: FC = ({ children }) => {
// eslint-disable-next-line no-nested-ternary
contextualBarPosition: breakpoints.includes('sm') ? (breakpoints.includes('lg') ? 'relative' : 'absolute') : 'fixed',
}),
[isEmbedded, showTopNavbarEmbeddedLayout, breakpoints],
[isMobile, isEmbedded, showTopNavbarEmbeddedLayout, isCollapsed, breakpoints, routeHome],
)}
/>
);

@ -1,4 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
import { useResizeObserver } from '@rocket.chat/fuselage-hooks';
import { useSession, useUserPreference, useUserId, useTranslation } from '@rocket.chat/ui-contexts';
@ -43,15 +44,87 @@ const RoomList = (): ReactElement => {
usePreventDefault(ref);
useShortcutOpenMenu(ref);
const roomsListStyle = css`
position: relative;
display: flex;
overflow-x: hidden;
overflow-y: hidden;
flex: 1 1 auto;
height: 100%;
&--embedded {
margin-top: 2rem;
}
&__list:not(:last-child) {
margin-bottom: 22px;
}
&__type {
display: flex;
flex-direction: row;
padding: 0 var(--sidebar-default-padding) 1rem var(--sidebar-default-padding);
color: var(--rooms-list-title-color);
font-size: var(--rooms-list-title-text-size);
align-items: center;
justify-content: space-between;
&-text--livechat {
flex: 1;
}
}
&__empty-room {
padding: 0 var(--sidebar-default-padding);
color: var(--rooms-list-empty-text-color);
font-size: var(--rooms-list-empty-text-size);
}
&__toolbar-search {
position: absolute;
z-index: 10;
left: 0;
overflow-y: scroll;
height: 100%;
background-color: var(--sidebar-background);
padding-block-start: 12px;
}
@media (width <= 400px) {
padding: 0 calc(var(--sidebar-small-default-padding) - 4px);
&__type,
&__empty-room {
padding: 0 calc(var(--sidebar-small-default-padding) - 4px) 0.5rem calc(var(--sidebar-small-default-padding) - 4px);
}
}
`;
return (
<Box h='full' w='full' ref={ref}>
<Virtuoso
totalCount={roomsList.length}
data={roomsList}
components={{ Scroller: ScrollerWithCustomProps }}
computeItemKey={computeItemKey}
itemContent={(_, data): ReactElement => <RoomListRow data={itemData} item={data} />}
/>
<Box className={[roomsListStyle, 'sidebar--custom-colors'].filter(Boolean)} aria-label={t('Channels')} role='region'>
<Box h='full' w='full' ref={ref}>
<Virtuoso
totalCount={roomsList.length}
data={roomsList}
components={{ Scroller: ScrollerWithCustomProps }}
computeItemKey={computeItemKey}
itemContent={(_, data): ReactElement => <RoomListRow data={itemData} item={data} />}
/>
</Box>
</Box>
);
};

@ -148,7 +148,9 @@ function SideBarItemTemplateWithData({
unread={highlighted}
selected={selected}
href={href}
onClick={(): void => !selected && sidebar.toggle()}
onClick={(): void => {
!selected && sidebar.toggle();
}}
aria-label={title}
title={title}
time={lastMessage?.ts}

@ -6,8 +6,7 @@ import type { ObjectId } from 'mongodb';
import type { ContextType } from 'react';
import React from 'react';
import RoomList from './RoomList/index';
import Header from './header';
import Sidebar from './Sidebar';
export default {
title: 'Sidebar',
@ -97,15 +96,8 @@ const userContextValue: ContextType<typeof UserContext> = {
logout: () => Promise.resolve(),
};
export const Sidebar: Story = () => (
<aside className='sidebar sidebar--main' role='navigation'>
<Header />
<div className='rooms-list sidebar--custom-colors' aria-label='Channels' role='region'>
<RoomList />
</div>
</aside>
);
Sidebar.decorators = [
export const SidebarStory: Story = () => <Sidebar />;
SidebarStory.decorators = [
(fn) => (
<SettingsContext.Provider value={settingContextValue}>
<UserContext.Provider value={userContextValue}>{fn()}</UserContext.Provider>

@ -0,0 +1,127 @@
import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
import { useLayout, useUserPreference } from '@rocket.chat/ui-contexts';
import React from 'react';
import SidebarRoomList from './RoomList';
import SidebarFooter from './footer';
import SidebarHeader from './header';
const Sidebar = () => {
const sidebarViewMode = useUserPreference('sidebarViewMode');
const sidebarHideAvatar = !useUserPreference('sidebarDisplayAvatar');
const { isMobile, sidebar } = useLayout();
const sideBarStyle = css`
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
flex: 0 0 var(--sidebar-width);
width: var(--sidebar-width);
max-width: var(--sidebar-width);
height: 100%;
user-select: none;
transition: transform 0.3s;
background-color: var(--sidebar-background);
&.opened {
box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 15px 1px;
transform: translate3d(0px, 0px, 0px);
}
@media (width < 768px) {
position: absolute;
user-select: none;
transform: translate3d(-100%, 0, 0);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
touch-action: pan-y;
-webkit-user-drag: none;
will-change: transform;
.rtl & {
transform: translate3d(200%, 0, 0);
}
}
@media (width <= 400px) {
flex: 0 0 var(--sidebar-small-width);
width: var(--sidebar-small-width);
max-width: var(--sidebar-small-width);
&__footer {
display: none;
}
&:not(&--light) {
transform: translate3d(-100%, 0, 0);
}
.rtl & {
transform: translate3d(200%, 0, 0);
}
}
@media (min-width: 1372px) {
/* 1440px -68px (eletron menu) */
flex: 0 0 20%;
width: 20%;
max-width: 20%;
}
`;
const sidebarWrapStyle = css`
position: absolute;
z-index: 1;
top: 0;
left: 0;
height: 100%;
user-select: none;
transition: opacity 0.3s;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
touch-action: pan-y;
-webkit-user-drag: none;
&.opened {
width: 100%;
background-color: rgb(0, 0, 0);
opacity: 0.8;
}
`;
return (
<>
<Box
id='sidebar-region'
className={[
'rcx-sidebar',
'sidebar--main',
`sidebar--${sidebarViewMode}`,
sidebarHideAvatar && 'sidebar--hide-avatar',
!sidebar.isCollapsed && isMobile && 'opened',
sideBarStyle,
].filter(Boolean)}
>
<Box
display='flex'
flexDirection='column'
height='100%'
is='nav'
className='rcx-sidebar--template'
role='navigation'
data-qa-opened={sidebar.isCollapsed ? 'false' : 'true'}
>
<SidebarHeader />
<SidebarRoomList />
<SidebarFooter />
</Box>
</Box>
{isMobile && (
<Box className={[sidebarWrapStyle, !sidebar.isCollapsed && 'opened'].filter(Boolean)} onClick={() => sidebar.toggle()}></Box>
)}
</>
);
};
export default Sidebar;

@ -0,0 +1,16 @@
import { Box } from '@rocket.chat/fuselage';
import type { FC } from 'react';
import React, { memo } from 'react';
import { createPortal } from 'react-dom';
const SidebarPortal: FC = ({ children }) => {
const sidebarRoot = document.getElementById('sidebar-region');
if (!sidebarRoot) {
return null;
}
return createPortal(<Box className='rcx-sidebar flex-nav'>{children}</Box>, sidebarRoot);
};
export default memo<typeof SidebarPortal>(SidebarPortal);

@ -0,0 +1 @@
export { default } from './Sidebar';

@ -32,15 +32,6 @@ createTemplateForComponent('UploadProgressIndicator', () => import('./views/room
createTemplateForComponent('messageLocation', () => import('./views/location/MessageLocation'));
createTemplateForComponent('sidebarHeader', () => import('./sidebar/header'));
createTemplateForComponent('sidebarChats', () => import('./sidebar/RoomList/index'), {
renderContainerView: () =>
HTML.DIV({
style: 'display: flex; flex: 1 1 100%;',
}),
});
createTemplateForComponent('omnichannelFlex', () => import('./views/omnichannel/sidebar/OmnichannelSidebar'), {
renderContainerView: () => HTML.DIV({ style: 'height: 100%; position: relative;' }),
});
@ -59,8 +50,6 @@ createTemplateForComponent('accountFlex', () => import('./views/account/AccountS
renderContainerView: () => HTML.DIV({ style: 'height: 100%; position: relative;' }),
});
createTemplateForComponent('sidebarFooter', () => import('./sidebar/footer'));
createTemplateForComponent('loggedOutBanner', () => import('../ee/client/components/deviceManagement/LoggedOutBanner'), {
renderContainerView: () => HTML.DIV({ style: 'max-width: 520px; margin: 0 auto;' }),
});

@ -2,8 +2,9 @@ import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts';
import type { ReactElement, ReactNode } from 'react';
import React, { Suspense, useEffect } from 'react';
import { SideNav } from '../../../app/ui-utils/client';
import PageSkeleton from '../../components/PageSkeleton';
import SidebarPortal from '../../sidebar/SidebarPortal';
import AccountSidebar from './AccountSidebar';
type AccountRouterProps = {
children?: ReactNode;
@ -21,12 +22,16 @@ const AccountRouter = ({ children }: AccountRouterProps): ReactElement => {
defaultRoute.replace();
}, [routeName, defaultRoute]);
useEffect(() => {
SideNav.setFlex('accountFlex');
SideNav.openFlex(() => undefined);
}, []);
return children ? <Suspense fallback={<PageSkeleton />}>{children}</Suspense> : <PageSkeleton />;
return children ? (
<>
<Suspense fallback={<PageSkeleton />}>{children}</Suspense>
<SidebarPortal>
<AccountSidebar />
</SidebarPortal>
</>
) : (
<PageSkeleton />
);
};
export default AccountRouter;

@ -1,43 +1,28 @@
import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { memo, useCallback, useEffect } from 'react';
import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { memo } from 'react';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { menu, SideNav } from '../../../app/ui-utils/client';
import Sidebar from '../../components/Sidebar';
import { isLayoutEmbedded } from '../../lib/utils/isLayoutEmbedded';
import SettingsProvider from '../../providers/SettingsProvider';
import { getAccountSidebarItems, subscribeToAccountSidebarItems } from './sidebarItems';
const AccountSidebar = (): ReactElement => {
const AccountSidebar: FC = () => {
const t = useTranslation();
const items = useSyncExternalStore(subscribeToAccountSidebarItems, getAccountSidebarItems);
const closeFlex = useCallback(() => {
if (isLayoutEmbedded()) {
menu.close();
return;
}
SideNav.closeFlex();
}, []);
const { sidebar } = useLayout();
const currentRoute = useCurrentRoute();
const [currentRouteName, currentRouteParams, currentQueryStringParams, currentRouteGroupName] = currentRoute;
const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute;
const currentPath = useRoutePath(currentRouteName || '', currentRouteParams, currentQueryStringParams);
useEffect(() => {
if (currentRouteGroupName !== 'account') {
SideNav.closeFlex();
}
}, [currentRouteGroupName]);
// TODO: uplift this provider
return (
<SettingsProvider privileged>
<Sidebar>
<Sidebar.Header onClose={closeFlex} title={t('Account')} />
<Sidebar.Header onClose={sidebar.close} title={t('Account')} />
<Sidebar.Content>
<Sidebar.ItemsAssembler items={items} currentPath={currentPath} />
</Sidebar.Content>

@ -1,15 +1,18 @@
import type { FC } from 'react';
import React, { useEffect } from 'react';
import React from 'react';
import { SideNav } from '../../../app/ui-utils/client';
import SidebarPortal from '../../sidebar/SidebarPortal';
import AdminSidebar from './sidebar/AdminSidebar';
const AdministrationLayout: FC = ({ children }) => {
useEffect(() => {
SideNav.setFlex('adminFlex');
SideNav.openFlex();
}, []);
return <>{children}</>;
return (
<>
<SidebarPortal>
<AdminSidebar />
</SidebarPortal>
{children}
</>
);
};
export default AdministrationLayout;

@ -1,43 +1,27 @@
import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts';
import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useCallback, useEffect, memo } from 'react';
import React, { memo } from 'react';
import { menu, SideNav } from '../../../../app/ui-utils/client';
import PlanTag from '../../../components/PlanTag';
import Sidebar from '../../../components/Sidebar';
import { isLayoutEmbedded } from '../../../lib/utils/isLayoutEmbedded';
import SettingsProvider from '../../../providers/SettingsProvider';
import AdminSidebarPages from './AdminSidebarPages';
const AdminSidebar: FC = () => {
const t = useTranslation();
const closeAdminFlex = useCallback(() => {
if (isLayoutEmbedded()) {
menu.close();
return;
}
SideNav.closeFlex();
}, []);
const { sidebar } = useLayout();
const currentRoute = useCurrentRoute();
const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute;
const currentPath = useRoutePath(currentRouteName || '', currentRouteParams, currentQueryStringParams);
const [, , , currentRouteGroupName] = currentRoute;
useEffect(() => {
if (currentRouteGroupName !== 'admin') {
SideNav.toggleFlex(-1);
}
}, [currentRouteGroupName]);
// TODO: uplift this provider
return (
<SettingsProvider privileged>
<Sidebar>
<Sidebar.Header
onClose={closeAdminFlex}
onClose={sidebar.close}
title={
<>
{t('Administration')} <PlanTag />

@ -2,8 +2,9 @@ import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts';
import type { ReactNode, ReactElement } from 'react';
import React, { Suspense, useEffect } from 'react';
import { SideNav } from '../../../app/ui-utils/client';
import PageSkeleton from '../../components/PageSkeleton';
import SidebarPortal from '../../sidebar/SidebarPortal';
import OmnichannelSidebar from './sidebar/OmnichannelSidebar';
type OmnichannelRouterProps = {
children?: ReactNode;
@ -12,18 +13,23 @@ type OmnichannelRouterProps = {
const OmnichannelRouter = ({ children }: OmnichannelRouterProps): ReactElement => {
const [routeName] = useCurrentRoute();
const defaultRoute = useRoute('omnichannel-current-chats');
useEffect(() => {
if (routeName === 'omnichannel-index') {
defaultRoute.push();
}
}, [defaultRoute, routeName]);
useEffect(() => {
SideNav.setFlex('omnichannelFlex');
SideNav.openFlex(() => undefined);
}, []);
return children ? <Suspense fallback={<PageSkeleton />}>{children}</Suspense> : <PageSkeleton />;
return children ? (
<>
<Suspense fallback={<PageSkeleton />}>{children}</Suspense>
<SidebarPortal>
<OmnichannelSidebar />
</SidebarPortal>
</>
) : (
<PageSkeleton />
);
};
export default OmnichannelRouter;

@ -1,12 +1,10 @@
import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts';
import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useCallback, useEffect, memo } from 'react';
import React, { memo } from 'react';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { menu, SideNav } from '../../../../app/ui-utils/client';
import Sidebar from '../../../components/Sidebar';
import SidebarItemsAssemblerProps from '../../../components/Sidebar/SidebarItemsAssembler';
import { isLayoutEmbedded } from '../../../lib/utils/isLayoutEmbedded';
import SettingsProvider from '../../../providers/SettingsProvider';
import { getOmnichannelSidebarItems, subscribeToOmnichannelSidebarItems } from '../sidebarItems';
@ -14,29 +12,16 @@ const OmnichannelSidebar: FC = () => {
const items = useSyncExternalStore(subscribeToOmnichannelSidebarItems, getOmnichannelSidebarItems);
const t = useTranslation();
const closeOmnichannelFlex = useCallback(() => {
if (isLayoutEmbedded()) {
menu.close();
return;
}
SideNav.closeFlex();
}, []);
const { sidebar } = useLayout();
const currentRoute = useCurrentRoute();
const [currentRouteName, currentRouteParams, currentQueryStringParams, currentRouteGroupName] = currentRoute;
const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute;
const currentPath = useRoutePath(currentRouteName ?? '', currentRouteParams, currentQueryStringParams);
useEffect(() => {
if (currentRouteGroupName !== 'omnichannel') {
SideNav.closeFlex();
}
}, [currentRouteGroupName]);
return (
<SettingsProvider privileged>
<Sidebar>
<Sidebar.Header onClose={closeOmnichannelFlex} title={<>{t('Omnichannel')}</>} />
<Sidebar.Header onClose={sidebar.close} title={<>{t('Omnichannel')}</>} />
<Sidebar.Content>
<SidebarItemsAssemblerProps items={items} currentPath={currentPath} />
</Sidebar.Content>

@ -1,10 +1,9 @@
import { Box } from '@rocket.chat/fuselage';
import { useLayout, useCurrentRoute, useRoutePath, useSetting, useCurrentModal } from '@rocket.chat/ui-contexts';
import { useLayout, useCurrentRoute, useRoutePath, useSetting, useCurrentModal, useRoute } from '@rocket.chat/ui-contexts';
import type { ReactElement, ReactNode } from 'react';
import React, { useCallback } from 'react';
import React, { useEffect, useRef } from 'react';
import { useReactiveValue } from '../../../hooks/useReactiveValue';
import BlazeTemplate from '../BlazeTemplate';
import Sidebar from '../../../sidebar';
const LayoutWithSidebar = ({ children }: { children: ReactNode }): ReactElement => {
const { isEmbedded: embeddedLayout } = useLayout();
@ -12,11 +11,33 @@ const LayoutWithSidebar = ({ children }: { children: ReactNode }): ReactElement
const modal = useCurrentModal();
const currentRoutePath = useRoutePath(currentRouteName, currentParameters);
const removeSidenav = useReactiveValue(
useCallback(() => embeddedLayout && !currentRoutePath?.startsWith('/admin'), [currentRoutePath, embeddedLayout]),
);
const channelRoute = useRoute('channel');
const removeSidenav = embeddedLayout && !currentRoutePath?.startsWith('/admin');
const readReceiptsEnabled = useSetting('Message_Read_Receipt_Store_Users');
const firstChannelAfterLogin = useSetting('First_Channel_After_Login');
const redirected = useRef(false);
useEffect(() => {
const needToBeRedirect = currentRoutePath && ['/', '/home'].includes(currentRoutePath);
if (!needToBeRedirect) {
return;
}
if (!firstChannelAfterLogin || typeof firstChannelAfterLogin !== 'string') {
return;
}
if (redirected.current) {
return;
}
redirected.current = true;
channelRoute.push({ name: firstChannelAfterLogin });
}, [channelRoute, currentRoutePath, firstChannelAfterLogin]);
return (
<Box
bg='surface-light'
@ -24,7 +45,7 @@ const LayoutWithSidebar = ({ children }: { children: ReactNode }): ReactElement
className={[embeddedLayout ? 'embedded-view' : undefined, 'menu-nav'].filter(Boolean).join(' ')}
aria-hidden={Boolean(modal)}
>
{!removeSidenav ? <BlazeTemplate template='sideNav' /> : null}
{!removeSidenav ? <Sidebar /> : null}
<div className={['rc-old', 'main-content', readReceiptsEnabled ? 'read-receipts-enabled' : undefined].filter(Boolean).join(' ')}>
{children}
</div>

@ -5,6 +5,8 @@ declare module 'meteor/kadira:flow-router' {
params: Record<string, string>;
queryParams: Record<string, string>;
pathname: string;
oldRoute?: Route;
route: Route;
};
export type RouteOptions = {

@ -8,9 +8,6 @@ const defaultConfig = {
'@rocket.chat/ui-contexts': {
useAtLeastOnePermission: () => true,
},
'../../../app/ui-utils/client': {
SideNav: {},
},
'../../../app/ui-utils/client/lib/AccountBox': {
AccountBoxItem: {},
isAppAccountBoxItem: () => false,

@ -9,7 +9,6 @@ import RouterContextMock from '../../../../mocks/client/RouterContextMock';
const COMPONENT_PATH = '../../../../../client/components/AdministrationList/AdministrationModelList';
const defaultConfig = {
'../../../app/ui-utils/client': {
'SideNav': {},
'@noCallThru': true,
},
'meteor/kadira:flow-router': {
@ -135,17 +134,8 @@ describe('components/AdministrationList/AdministrationModelList', () => {
it('should render admin box and call sidenav', async () => {
const closeList = spy();
const setFlex = spy();
const openFlex = spy();
const AdministrationModelList = proxyquire.load(COMPONENT_PATH, {
...defaultConfig,
'../../../app/ui-utils/client': {
'SideNav': {
setFlex,
openFlex,
},
'@noCallThru': true,
},
}).default;
render(
<AdministrationModelList closeList={closeList} accountBoxItems={[{ name: 'Admin Item', sideNav: 'admin' }]} showAdmin={true} />,
@ -153,8 +143,6 @@ describe('components/AdministrationList/AdministrationModelList', () => {
const button = screen.getByText('Admin Item');
userEvent.click(button);
await waitFor(() => expect(setFlex).to.have.been.called.with('admin'));
await waitFor(() => expect(openFlex).to.have.been.called());
await waitFor(() => expect(closeList).to.have.been.called());
});
});

@ -16,7 +16,7 @@ export const SidebarPaletteStyleTag = memo((): ReactElement | null => {
<style id='sidebar-palette' data-style={theme}>
{convertToCss(
theme === 'dark' ? filterOnlyChangedColors(darkPalette, sidebarPaletteDark) : { ...darkPalette, ...defaultSidebarPalette },
'.sidebar--main.sidebar',
'.rcx-sidebar--template',
)}
</style>,
document.head,

@ -9,7 +9,13 @@ export type LayoutContextValue = {
isEmbedded: boolean;
showTopNavbarEmbeddedLayout: boolean;
isMobile: boolean;
sidebar: any;
sidebar: {
isCollapsed: boolean;
toggle: () => void;
collapse: () => void;
expand: () => void;
close: () => void;
};
size: SizeLayout;
contextualBarExpanded: boolean;
contextualBarPosition: 'absolute' | 'relative' | 'fixed';
@ -19,7 +25,13 @@ export const LayoutContext = createContext<LayoutContextValue>({
isEmbedded: false,
showTopNavbarEmbeddedLayout: false,
isMobile: false,
sidebar: {},
sidebar: {
isCollapsed: false,
toggle: () => undefined,
collapse: () => undefined,
expand: () => undefined,
close: () => undefined,
},
size: {
sidebar: '380px',
contextualBar: '380px',

Loading…
Cancel
Save