[NEW] New set of rules for client code (#21318)

Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pull/21493/head
Tasso Evangelista 5 years ago committed by GitHub
parent 45b05602c4
commit d8a24b044e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .eslintignore
  2. 69
      .storybook/main.js
  3. 65
      .storybook/webpack.config.js
  4. 1
      app/authorization/client/index.js
  5. 18
      app/authorization/client/usersNameChanged.js
  6. 4
      app/channel-settings/client/index.js
  7. 67
      app/channel-settings/client/startup/messageTypes.js
  8. 4
      app/channel-settings/client/tabBar.ts
  9. 12
      app/channel-settings/client/views/Multiselect.js
  10. 3
      app/chatpal-search/client/route.js
  11. 2
      app/livechat/client/tabBar.ts
  12. 6
      app/livestream/client/tabBar.tsx
  13. 3
      app/mail-messages/client/router.js
  14. 3
      app/message-snippet/client/router.js
  15. 2
      app/oauth2-server-config/client/oauth/oauth2-client.js
  16. 4
      app/otr/client/rocketchat.otr.room.js
  17. 3
      app/search/client/provider/result.js
  18. 11
      app/theme/client/imports/components/read-receipts.css
  19. 12
      app/theme/client/imports/general/base_old.css
  20. 1
      app/theme/client/main.css
  21. 15
      app/threads/client/flextab/thread.js
  22. 3
      app/token-login/client/login_token_client.js
  23. 3
      app/ui-login/client/routes.js
  24. 7
      app/ui-master/client/main.html
  25. 8
      app/ui-master/client/main.js
  26. 1
      app/ui-message/client/index.js
  27. 14
      app/ui-message/client/message.js
  28. 5
      app/ui-sidenav/client/sideNav.js
  29. 2
      app/ui-sidenav/client/userPresence.js
  30. 2
      app/ui-utils/client/lib/AccountBox.js
  31. 6
      app/ui-utils/client/lib/MessageAction.js
  32. 3
      app/ui-utils/client/lib/messageContext.js
  33. 2
      app/ui-utils/client/lib/openRoom.js
  34. 15
      app/ui/client/components/status.html
  35. 66
      app/ui/client/components/status.js
  36. 5
      app/ui/client/index.js
  37. 6
      app/ui/client/lib/Tooltip.js
  38. 6
      app/ui/client/lib/UserCard.js
  39. 3
      app/ui/client/views/app/lib/getCommonRoomEvents.js
  40. 10
      app/videobridge/client/tabBar.tsx
  41. 6
      app/webrtc/client/WebRTCClass.js
  42. 129
      client/.eslintrc.js
  43. 12
      client/.prettierrc
  44. 25
      client/UIKit/hooks/useUIKitHandleAction.tsx
  45. 39
      client/UIKit/hooks/useUIKitHandleClose.tsx
  46. 5
      client/UIKit/hooks/useUIKitStateManager.tsx
  47. 7
      client/adapters.js
  48. 18
      client/components/AppRoot.js
  49. 25
      client/components/AutoComplete.js
  50. 10
      client/components/AutoComplete.stories.js
  51. 45
      client/components/AutoCompleteAgent.js
  52. 43
      client/components/AutoCompleteDepartment.js
  53. 2
      client/components/Backdrop.js
  54. 10
      client/components/Card/Body.js
  55. 56
      client/components/Card/Card.js
  56. 160
      client/components/Card/Card.stories.js
  57. 6
      client/components/Card/CardDivider.js
  58. 16
      client/components/Card/CardIcon.js
  59. 10
      client/components/Card/Col.js
  60. 10
      client/components/Card/ColSection.js
  61. 10
      client/components/Card/ColTitle.js
  62. 6
      client/components/Card/Footer.js
  63. 10
      client/components/Card/Title.js
  64. 23
      client/components/Card/index.ts
  65. 90
      client/components/ConfirmOwnerChangeWarningModal.js
  66. 163
      client/components/CustomFieldsForm.js
  67. 36
      client/components/DeleteChannelWarning.js
  68. 10
      client/components/DeleteFileWarning.js
  69. 40
      client/components/DeleteSuccessModal.tsx
  70. 40
      client/components/DeleteWarningModal.tsx
  71. 14
      client/components/DotLeader.stories.js
  72. 25
      client/components/DotLeader.tsx
  73. 4
      client/components/Emoji.js
  74. 5
      client/components/ExternalLink.tsx
  75. 25
      client/components/FilterByText.tsx
  76. 5
      client/components/GenericModal.stories.js
  77. 46
      client/components/GenericModal.tsx
  78. 22
      client/components/GenericTable.stories.js
  79. 14
      client/components/GenericTable/HeaderCell.tsx
  80. 13
      client/components/GenericTable/LoadingRow.tsx
  81. 30
      client/components/GenericTable/SortIcon.tsx
  82. 121
      client/components/GenericTable/index.js
  83. 7
      client/components/Header/Avatar.tsx
  84. 6
      client/components/Header/Button.tsx
  85. 17
      client/components/Header/Content.tsx
  86. 134
      client/components/Header/Header.stories.js
  87. 147
      client/components/Header/Header.tsx
  88. 6
      client/components/Header/HeaderDivider.tsx
  89. 24
      client/components/Header/HeaderIcon.tsx
  90. 24
      client/components/Header/HeaderLink.tsx
  91. 14
      client/components/Header/HeaderTag.tsx
  92. 13
      client/components/Header/HeaderTagIcon.tsx
  93. 5
      client/components/Header/HeaderTagSkeleton.tsx
  94. 8
      client/components/Header/Row.tsx
  95. 11
      client/components/Header/State.tsx
  96. 8
      client/components/Header/Subtitle.tsx
  97. 8
      client/components/Header/Title.tsx
  98. 6
      client/components/Header/ToolBox.tsx
  99. 32
      client/components/Header/ToolBoxAction.tsx
  100. 18
      client/components/Header/ToolBoxActionBadge.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -18,3 +18,5 @@ imports/client/
!/.storybook/
ee/server/services/dist/**
!/.mocharc.js
!/client/.eslintrc.js
!/ee/client/.eslintrc.js

@ -1,3 +1,7 @@
const path = require('path');
const webpack = require('webpack');
module.exports = {
stories: [
'../app/**/*.stories.js',
@ -6,5 +10,70 @@ module.exports = {
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-postcss',
],
webpackFinal: async (config) => {
const cssRule = config.module.rules.find(({ test }) => test.test('index.css'));
cssRule.use[2].options = {
...cssRule.use[2].options,
postcssOptions: {
plugins: [
require('postcss-custom-properties')({ preserve: true }),
require('postcss-media-minmax')(),
require('postcss-selector-not')(),
require('postcss-nested')(),
require('autoprefixer')(),
require('postcss-url')({ url: ({ absolutePath, relativePath, url }) => {
const absoluteDir = absolutePath.slice(0, -relativePath.length);
const relativeDir = path.relative(absoluteDir, path.resolve(__dirname, '../public'));
const newPath = path.join(relativeDir, url);
return newPath;
} }),
],
},
};
config.module.rules.push({
test: /\.info$/,
type: 'json',
});
config.module.rules.push({
test: /\.html$/,
use: '@settlin/spacebars-loader',
});
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: 'ts-loader',
options: {
compilerOptions: {
noEmit: false,
},
},
},
],
});
config.resolve.extensions.push('.ts', '.tsx');
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(
/^meteor/,
require.resolve('./mocks/meteor.js'),
),
new webpack.NormalModuleReplacementPlugin(
/(app)\/*.*\/(server)\/*/,
require.resolve('./mocks/empty.js'),
),
);
config.mode = 'development';
config.optimization.usedExports = true;
return config;
},
};

@ -1,65 +0,0 @@
'use strict';
const path = require('path');
const webpack = require('webpack');
module.exports = async ({ config }) => {
const cssRule = config.module.rules.find(({ test }) => test.test('index.css'));
cssRule.use[2].options.plugins = [
require('postcss-custom-properties')({ preserve: true }),
require('postcss-media-minmax')(),
require('postcss-selector-not')(),
require('postcss-nested')(),
require('autoprefixer')(),
require('postcss-url')({ url: ({ absolutePath, relativePath, url }) => {
const absoluteDir = absolutePath.slice(0, -relativePath.length);
const relativeDir = path.relative(absoluteDir, path.resolve(__dirname, '../public'));
const newPath = path.join(relativeDir, url);
return newPath;
} }),
];
config.module.rules.push({
test: /\.info$/,
type: 'json',
});
config.module.rules.push({
test: /\.html$/,
use: '@settlin/spacebars-loader',
});
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: 'ts-loader',
options: {
compilerOptions: {
noEmit: false,
},
},
},
],
});
config.resolve.extensions.push('.ts', '.tsx');
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(
/^meteor/,
require.resolve('./mocks/meteor.js'),
),
new webpack.NormalModuleReplacementPlugin(
/(app)\/*.*\/(server)\/*/,
require.resolve('./mocks/empty.js'),
),
);
config.mode = 'development';
config.optimization.usedExports = true;
return config;
};

@ -1,7 +1,6 @@
import { hasAllPermission, hasAtLeastOnePermission, hasPermission, userHasAllPermission } from './hasPermission';
import { hasRole } from './hasRole';
import { AuthorizationUtils } from '../lib/AuthorizationUtils';
import './usersNameChanged';
import './requiresPermission.html';
import './startup';

@ -1,18 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Notifications } from '../../notifications';
import { RoomRoles } from '../../models';
Meteor.startup(function() {
Notifications.onLogged('Users:NameChanged', function({ _id, name }) {
RoomRoles.update({
'u._id': _id,
}, {
$set: {
'u.name': name,
},
}, {
multi: true,
});
});
});

@ -1,5 +1,3 @@
import './startup/messageTypes';
import './startup/tabBar';
import './views/Multiselect';
import './tabBar';
export { ChannelSettings } from './lib/ChannelSettings';

@ -1,67 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { escapeHTML } from '../../../../lib/escapeHTML';
import { MessageTypes } from '../../../ui-utils';
import { t } from '../../../utils';
Meteor.startup(function() {
MessageTypes.registerType({
id: 'room_changed_privacy',
system: true,
message: 'room_changed_privacy',
data(message) {
return {
user_by: message.u && message.u.username,
room_type: t(message.msg),
};
},
});
MessageTypes.registerType({
id: 'room_changed_topic',
system: true,
message: 'room_changed_topic',
data(message) {
return {
user_by: message.u && message.u.username,
room_topic: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});
MessageTypes.registerType({
id: 'room_changed_avatar',
system: true,
message: 'room_changed_avatar',
data(message) {
return {
user_by: message.u && message.u.username,
};
},
});
MessageTypes.registerType({
id: 'room_changed_announcement',
system: true,
message: 'room_changed_announcement',
data(message) {
return {
user_by: message.u && message.u.username,
room_announcement: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});
MessageTypes.registerType({
id: 'room_changed_description',
system: true,
message: 'room_changed_description',
data(message) {
return {
user_by: message.u && message.u.username,
room_description: escapeHTML(message.msg || `(${ t('None').toLowerCase() })`),
};
},
});
});

@ -1,6 +1,6 @@
import { FC, lazy, LazyExoticComponent } from 'react';
import { addAction } from '../../../../client/views/room/lib/Toolbox';
import { addAction } from '../../../client/views/room/lib/Toolbox';
addAction('channel-settings', {
groups: ['channel', 'group'],
@ -9,6 +9,6 @@ addAction('channel-settings', {
full: true,
title: 'Room_Info',
icon: 'info-circled',
template: lazy(() => import('../../../../client/views/room/contextualBar/Info')) as LazyExoticComponent<FC>,
template: lazy(() => import('../../../client/views/room/contextualBar/Info')) as LazyExoticComponent<FC>,
order: 7,
});

@ -1,12 +0,0 @@
import { HTML } from 'meteor/htmljs';
import { createTemplateForComponent } from '../../../../client/reactAdapters';
createTemplateForComponent(
'Multiselect',
() => import('../../../../client/admin/settings/inputs/MultiSelectSettingInput'),
{
// eslint-disable-next-line new-cap
renderContainerView: () => HTML.DIV({ class: 'rc-multiselect', style: 'display: flex;' }),
},
);

@ -1,5 +1,4 @@
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import * as BlazeLayout from '../../../client/lib/portals/blazeLayout';
import { registerAdminRoute } from '../../../client/views/admin';
import { t } from '../../utils';

@ -7,7 +7,7 @@ addAction('room-info', {
id: 'room-info',
title: 'Room_Info',
icon: 'info-circled',
template: lazy(() => import('../../../client/omnichannel/chats/contextualBar')),
template: lazy(() => import('../../../client/views/omnichannel/directory/chats/contextualBar/ChatsContextualBar')),
order: 0,
});

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { ReactNode, useMemo } from 'react';
import { Option, Badge } from '@rocket.chat/fuselage';
import { useSetting } from '../../../client/contexts/SettingsContext';
@ -19,10 +19,10 @@ addAction('livestream', ({ room }) => {
icon: 'podcast',
template: 'liveStreamTab',
order: isLive ? -1 : 15,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
renderAction: (props): ReactNode => <Header.ToolBoxAction {...props}>
{isLive ? <Header.Badge title={t('Livestream_live_now')} variant='danger'>!</Header.Badge> : null}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}>
renderOption: ({ label: { title, icon }, ...props }: any): ReactNode => <Option label={title} title={title} icon={icon} {...props}>
{isLive ? <Badge title={t('Livestream_live_now')} variant='danger'>!</Badge> : null }
</Option>,
} : null), [enabled, isLive, t]);

@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import * as BlazeLayout from '../../../client/lib/portals/blazeLayout';
FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', {
name: 'mailer-unsubscribe',

@ -1,5 +1,6 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import * as BlazeLayout from '../../../client/lib/portals/blazeLayout';
FlowRouter.route('/snippet/:snippetId/:snippetName', {
name: 'snippetView',

@ -1,10 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { Template } from 'meteor/templating';
import { Accounts } from 'meteor/accounts-base';
import { ReactiveVar } from 'meteor/reactive-var';
import * as BlazeLayout from '../../../../client/lib/portals/blazeLayout';
import { APIClient } from '../../../utils/client';
FlowRouter.route('/oauth/authorize', {

@ -3,7 +3,6 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Random } from 'meteor/random';
import { EJSON } from 'meteor/ejson';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { TimeSync } from 'meteor/mizzao:timesync';
import _ from 'underscore';
@ -14,6 +13,7 @@ import { Notifications } from '../../notifications';
import { modal } from '../../ui-utils';
import { getUidDirectMessage } from '../../ui-utils/client/lib/getUidDirectMessage';
import { Presence } from '../../../client/lib/presence';
import { goToRoomById } from '../../../client/lib/goToRoomById';
OTR.Room = class {
constructor(userId, roomId) {
@ -189,7 +189,7 @@ OTR.Room = class {
this.generateKeyPair().then(() => {
this.importPublicKey(data.publicKey).then(() => {
this.firstPeer = false;
FlowRouter.goToRoomById(data.roomId);
goToRoomById(data.roomId);
Meteor.defer(() => {
this.established.set(true);
this.acknowledge();

@ -11,6 +11,7 @@ import { MessageAction, RoomHistoryManager } from '../../../ui-utils';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { Rooms } from '../../../models/client';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
import { goToRoomById } from '../../../../client/lib/goToRoomById';
Meteor.startup(function() {
MessageAction.addButton({
@ -35,7 +36,7 @@ Meteor.startup(function() {
return RoomHistoryManager.getSurroundingMessages(message, 50);
}
FlowRouter.goToRoomById(message.rid);
goToRoomById(message.rid);
// RocketChat.MessageAction.hideDropDown();
if (window.matchMedia('(max-width: 500px)').matches) {

@ -5,11 +5,12 @@
}
.read-receipt .rc-icon {
height: 0.8em;
width: 0.8em;
height: 0.8em;
}
.message:hover .read-receipt, .message.active .read-receipt {
.message:hover .read-receipt,
.message.active .read-receipt {
display: none;
}
@ -20,6 +21,7 @@
.read-receipt.read {
color: #1d74f5;
color: var(--rc-color-button-primary);
font-style: normal;
}
@ -29,13 +31,16 @@
.read-receipts__user {
display: flex;
padding: 8px 8px;
padding: 8px;
align-items: center;
}
.read-receipts__name {
flex: 1 1 auto;
margin: 0 10px;
font-size: 16px;
}

@ -145,18 +145,6 @@
justify-content: center;
}
.connection-status > .alert {
position: absolute;
z-index: 1000000;
top: 0;
width: 100%;
margin-top: 0 !important;
padding: 2px;
border-width: 0 0 1px;
}
.rc-old .alert {
margin: 1rem 0;
padding: 0.5rem;

@ -36,6 +36,7 @@
@import 'imports/components/modal.css';
@import 'imports/components/chip.css';
@import 'imports/components/messages.css';
@import 'imports/components/read-receipts.css';
@import 'imports/components/contextual-bar.css';
@import 'imports/components/emojiPicker.css';
@import 'imports/components/table.css';

@ -3,7 +3,6 @@ import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { HTML } from 'meteor/htmljs';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
@ -14,7 +13,6 @@ import { messageContext } from '../../../ui-utils/client/lib/messageContext';
import { upsertMessageBulk } from '../../../ui-utils/client/lib/RoomHistoryManager';
import { Messages } from '../../../models';
import { fileUpload } from '../../../ui/client/lib/fileUpload';
import { createTemplateForComponent } from '../../../../client/reactAdapters';
import { dropzoneEvents, dropzoneHelpers } from '../../../ui/client/views/app/room';
import './thread.html';
import { getUserPreference } from '../../../utils';
@ -23,21 +21,8 @@ import { callbacks } from '../../../callbacks/client';
import './messageBoxFollow';
import { getCommonRoomEvents } from '../../../ui/client/views/app/lib/getCommonRoomEvents';
createTemplateForComponent('Checkbox', async () => {
const { CheckBox } = await import('@rocket.chat/fuselage');
return { default: CheckBox };
}, {
// eslint-disable-next-line new-cap
renderContainerView: () => HTML.DIV({ class: 'rcx-checkbox', style: 'display: flex;' }),
});
const sort = { ts: 1 };
createTemplateForComponent('ThreadComponent', () => import('../components/ThreadComponent'), {
// eslint-disable-next-line new-cap
renderContainerView: () => HTML.DIV({ class: 'contextual-bar', style: 'display: flex; height: 100%;' }),
});
Template.thread.events({
...dropzoneEvents,
...getCommonRoomEvents(),

@ -1,7 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import * as BlazeLayout from '../../../client/lib/portals/blazeLayout';
Meteor.loginWithLoginToken = function(token) {
Accounts.callLoginMethod({

@ -1,5 +1,6 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import * as BlazeLayout from '../../../client/lib/portals/blazeLayout';
FlowRouter.route('/reset-password/:token', {
name: 'resetPassword',

@ -5,9 +5,6 @@
{{#if subsReady}}
{{#unless showSetupWizard}}
{{#unless logged}}
<div class="rc-old connection-status">
{{> status}}
</div>
{{#if useIframe}}
{{#if iframeUrl}}
<iframe src="{{iframeUrl}}" style="height: 100%; width: 100%;"></iframe>
@ -40,10 +37,6 @@
{{> Template.dynamic template=center}}
</div>
</div>
<div class="rc-old connection-status">
{{> status}}
</div>
{{/if}}
{{/if}}
{{/unless}}

@ -17,7 +17,6 @@ import { hasRole } from '../../authorization';
import { tooltip } from '../../ui/client/components/tooltip';
import { callbacks } from '../../callbacks/client';
import { isSyncReady } from '../../../client/lib/userData';
import { createTemplateForComponent } from '../../../client/reactAdapters';
function executeCustomScript(script) {
eval(script);//eslint-disable-line
@ -30,8 +29,6 @@ function customScriptsOnLogout() {
}
}
createTemplateForComponent('accountSecurity', () => import('../../../client/views/account/security/AccountSecurityPage'));
callbacks.add('afterLogoutCleanUp', () => customScriptsOnLogout(), callbacks.priority.LOW, 'custom-script-on-logout');
Template.body.onRendered(function() {
@ -222,6 +219,11 @@ Template.main.helpers({
return (!userId && Show_Setup_Wizard === 'pending') || (userId && hasRole(userId, 'admin') && Show_Setup_Wizard === 'in_progress');
},
readReceiptsEnabled() {
if (settings.get('Message_Read_Receipt_Store_Users')) {
return 'read-receipts-enabled';
}
},
});
Template.main.events({

@ -6,4 +6,3 @@ import './popup/messagePopupChannel';
import './popup/messagePopupConfig';
import './popup/messagePopupEmoji';
import './popup/messagePopupSlashCommandPreview';
import '../../../client/views/blocks';

@ -17,12 +17,9 @@ import { AutoTranslate } from '../../autotranslate/client';
import { escapeHTML } from '../../../lib/escapeHTML';
import { renderMentions } from '../../mentions/client/client';
import { renderMessageBody } from '../../../client/lib/renderMessageBody';
import { createTemplateForComponent } from '../../../client/reactAdapters';
import { settings } from '../../settings/client';
import './message.html';
createTemplateForComponent('messageLocation', () => import('../../../client/views/location/MessageLocation'));
const renderBody = (msg, settings) => {
const searchedText = msg.searchedText ? msg.searchedText : '';
const isSystemMessage = MessageTypes.isSystemMessage(msg);
@ -454,6 +451,15 @@ Template.message.helpers({
const { msg } = this;
return msg.starred && msg.starred.length > 0 && msg.starred.find((star) => star._id === Meteor.userId()) && !(msg.actionContext === 'starred' || this.context === 'starred');
},
readReceipt() {
if (!settings.get('Message_Read_Receipt_Enabled')) {
return;
}
return {
readByEveryone: (!this.msg.unread && 'read') || 'color-component-color',
};
},
});
const hasTempClass = (node) => node.classList.contains('temp');

@ -1,5 +1,4 @@
import { Meteor } from 'meteor/meteor';
import { HTML } from 'meteor/htmljs';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
@ -9,10 +8,6 @@ import { SideNav, menu } from '../../ui-utils';
import { settings } from '../../settings';
import { roomTypes, getUserPreference } from '../../utils';
import { Users } from '../../models';
import { createTemplateForComponent } from '../../../client/reactAdapters';
createTemplateForComponent('sidebarHeader', () => import('../../../client/sidebar/header'));
createTemplateForComponent('sidebarChats', () => import('../../../client/sidebar/RoomList'), { renderContainerView: () => HTML.DIV({ style: 'display: flex; flex: 1 1 auto;' }) });// eslint-disable-line new-cap
Template.sideNav.helpers({
flexTemplate() {

@ -6,7 +6,7 @@ import _ from 'underscore';
import mem from 'mem';
import { APIClient } from '../../utils/client';
import { saveUser, interestedUserIds } from '../../../imports/startup/client/listenActiveUsers';
import { saveUser, interestedUserIds } from '../../../client/startup/listenActiveUsers';
import { Presence } from '../../../client/lib/presence';
import './userPresence.html';

@ -2,10 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { Session } from 'meteor/session';
import _ from 'underscore';
import * as BlazeLayout from '../../../../client/lib/portals/blazeLayout';
import { SideNav } from './SideNav';
export const AccountBox = (function() {

@ -8,9 +8,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { HTML } from 'meteor/htmljs';
import { createTemplateForComponent } from '../../../../client/reactAdapters';
import { messageArgs } from './messageArgs';
import { roomTypes, canDeleteMessage } from '../../../utils/client';
import { Messages, Rooms, Subscriptions } from '../../../models/client';
@ -387,7 +385,3 @@ Meteor.startup(async function() {
group: 'menu',
});
});
createTemplateForComponent('reactionList', () => import('./ReactionListContent'), {
renderContainerView: () => HTML.DIV({ style: 'margin: -16px; height: 100%; display: flex; flex-direction: column; overflow: hidden;' }), // eslint-disable-line new-cap
});

@ -11,6 +11,7 @@ import { AutoTranslate } from '../../../autotranslate/client';
import { Layout } from './Layout';
import { fireGlobalEvent } from './fireGlobalEvent';
import { actionLinks } from '../../../action-links/client';
import { goToRoomById } from '../../../../client/lib/goToRoomById';
const fields = { name: 1, username: 1, 'settings.preferences.showMessageInMainThread': 1, 'settings.preferences.autoImageLoad': 1, 'settings.preferences.saveMobileBandwidth': 1, 'settings.preferences.collapseMediaByDefault': 1, 'settings.preferences.hideRoles': 1 };
@ -51,7 +52,7 @@ export function messageContext({ rid } = Template.instance()) {
const openDiscussion = (e) => {
e.preventDefault();
const { drid } = e.currentTarget.dataset;
FlowRouter.goToRoomById(drid);
goToRoomById(drid);
};
const replyBroadcast = (e) => {

@ -3,11 +3,11 @@ import { Tracker } from 'meteor/tracker';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { Session } from 'meteor/session';
import mem from 'mem';
import _ from 'underscore';
import * as BlazeLayout from '../../../../client/lib/portals/blazeLayout';
import { Messages, ChatSubscription, Rooms } from '../../../models';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';

@ -1,15 +0,0 @@
<template name="status">
{{#unless connected}}
<div class="alert alert-warning text-center" role="alert">
<strong>
<span class="glyphicon glyphicon-warning-sign"></span>
{{message}}
</strong>
{{extraMessage}}
{{#if showReconnect}}
<a href="#" class="alert-link">{{reconnectLabel}}</a>
{{/if}}
</div>
{{/unless}}
</template>

@ -1,66 +0,0 @@
import _ from 'underscore';
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import './status.html';
const retryTime = new ReactiveVar(0);
let retryHandle = null;
const clearRetryInterval = function() {
clearInterval(retryHandle);
retryHandle = null;
};
const trackStatus = function() {
if (Meteor.status().status === 'waiting') {
retryHandle = retryHandle || setInterval(function() {
const timeDiff = Meteor.status().retryTime - new Date().getTime();
const _retryTime = (timeDiff > 0 && Math.round(timeDiff / 1000)) || 0;
retryTime.set(_retryTime);
}, 500);
} else {
clearRetryInterval();
}
};
Template.status.onDestroyed(clearRetryInterval);
Template.status.onCreated(function() {
this.autorun(trackStatus);
});
Template.status.helpers({
connected() {
return Meteor.status().connected;
},
message() {
return TAPi18n.__('meteor_status', { context: Meteor.status().status });
},
extraMessage() {
if (Meteor.status().status === 'waiting') {
return TAPi18n.__('meteor_status_reconnect_in', { count: retryTime.get() });
}
},
showReconnect() {
return _.contains(['waiting', 'offline'], Meteor.status().status);
},
reconnectLabel() {
return TAPi18n.__('meteor_status_try_now', { context: Meteor.status().status });
},
});
Template.status.events({
'click a.alert-link'(e) {
e.preventDefault();
Meteor.reconnect();
},
});

@ -1,5 +1,3 @@
import { createTemplateForComponent } from '../../../client/reactAdapters';
import './lib/accounts';
import './lib/collections';
import './lib/iframeCommands';
@ -37,7 +35,6 @@ import './views/app/videoCall/videoButtons';
import './views/app/videoCall/videoCall';
import './views/app/photoswipe';
import './components/icon';
import './components/status';
import './components/table.html';
import './components/table';
import './components/tabs';
@ -58,5 +55,3 @@ export { AudioRecorder } from './lib/recorderjs/audioRecorder';
export { VideoRecorder } from './lib/recorderjs/videoRecorder';
export { chatMessages } from './views/app/room';
export * from './lib/userPopoverStatus';
createTemplateForComponent('RoomForeword', () => import('../../../client/components/RoomForeword'));

@ -1,6 +1,6 @@
import { Tracker } from 'meteor/tracker';
import { createEphemeralPortal } from '../../../../client/reactAdapters';
import { createEphemeralPortal } from '../../../../client/lib/portals/createEphemeralPortal';
const Dep = new Tracker.Dependency();
@ -28,14 +28,14 @@ export const closeTooltip = () => {
unregister = unregister && unregister();
};
export const openToolTip = async (title, anchor) => {
export const openToolTip = (title, anchor) => {
dom = dom || createAnchor();
state = {
title,
anchor,
};
Dep.changed();
unregister = unregister || await createEphemeralPortal(() => import('./TooltipComponent'), props, dom);
unregister = unregister || createEphemeralPortal(() => import('./TooltipComponent'), props, dom);
};
document.body.addEventListener('mouseover', (() => {

@ -1,7 +1,7 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Tracker } from 'meteor/tracker';
import { createEphemeralPortal } from '../../../../client/reactAdapters';
import { createEphemeralPortal } from '../../../../client/lib/portals/createEphemeralPortal';
const Dep = new Tracker.Dependency();
@ -35,7 +35,7 @@ export const closeUserCard = () => {
});
};
export const openUserCard = async (args) => {
export const openUserCard = (args) => {
props = {
...args,
onClose: closeUserCard,
@ -45,7 +45,7 @@ export const openUserCard = async (args) => {
container = container || createContainer();
unregister = unregister || await createEphemeralPortal(
unregister = unregister || createEphemeralPortal(
() => import('../../../../client/views/room/UserCard'), () => {
Dep.depend();
return props;

@ -21,6 +21,7 @@ import { ChatMessage, Rooms, Messages } from '../../../../../models';
import { t } from '../../../../../utils/client';
import { chatMessages } from '../room';
import { EmojiEvents } from '../../../../../reactions/client/init';
import { goToRoomById } from '../../../../../../client/lib/goToRoomById';
const mountPopover = (e, i, outerContext) => {
let context = $(e.target).parents('.message').data('context');
@ -313,7 +314,7 @@ export const getCommonRoomEvents = () => ({
if (Layout.isEmbedded()) {
fireGlobalEvent('click-mention-link', { path: FlowRouter.path('channel', { name: channel }), channel });
}
FlowRouter.goToRoomById(channel);
goToRoomById(channel);
return;
}

@ -1,4 +1,4 @@
import React, { useMemo, lazy } from 'react';
import React, { useMemo, lazy, ReactNode } from 'react';
import { useStableArray } from '@rocket.chat/fuselage-hooks';
import { Option, Badge } from '@rocket.chat/fuselage';
@ -32,10 +32,10 @@ addAction('bbb_video', ({ room }) => {
icon: 'phone',
template: templateBBB,
order: live ? -1 : 0,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
renderAction: (props): ReactNode => <Header.ToolBoxAction {...props}>
{live ? <Header.Badge title={t('Started_a_video_call')} variant='primary'>!</Header.Badge> : null}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}><Badge title={t('Started_a_video_call')} variant='primary'>!</Badge></Option>,
renderOption: ({ label: { title, icon }, ...props }: any): ReactNode => <Option label={title} title={title} icon={icon} {...props}><Badge title={t('Started_a_video_call')} variant='primary'>!</Badge></Option>,
} : null), [enabled, groups, live, t]);
});
@ -66,10 +66,10 @@ addAction('video', ({ room }) => {
template: templateJitsi,
full: true,
order: live ? -1 : 0,
renderAction: (props): React.ReactNode => <Header.ToolBoxAction {...props}>
renderAction: (props): ReactNode => <Header.ToolBoxAction {...props}>
{live && <Header.Badge title={t('Started_a_video_call')} variant='primary'>!</Header.Badge>}
</Header.ToolBoxAction>,
renderOption: ({ label: { title, icon }, ...props }: any): React.ReactNode => <Option label={title} title={title} icon={icon} {...props}>
renderOption: ({ label: { title, icon }, ...props }: any): ReactNode => <Option label={title} title={title} icon={icon} {...props}>
{ live && <Badge title={t('Started_a_video_call')} variant='primary'>!</Badge> }
</Option>,
} : null), [enabled, groups, live, t]);

@ -2,7 +2,6 @@ import { Emitter } from '@rocket.chat/emitter';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { ChromeScreenShare } from './screenShare';
@ -12,6 +11,7 @@ import { settings } from '../../settings';
import { modal } from '../../ui-utils';
import { ChatSubscription } from '../../models';
import { WEB_RTC_EVENTS } from '..';
import { goToRoomById } from '../../../client/lib/goToRoomById';
class WebRTCTransportClass extends Emitter {
constructor(webrtcInstance) {
@ -663,7 +663,7 @@ class WebRTCClass {
onRemoteCall(data) {
if (this.autoAccept === true) {
FlowRouter.goToRoomById(data.room);
goToRoomById(data.room);
Meteor.defer(() => {
this.joinCall({
to: data.from,
@ -712,7 +712,7 @@ class WebRTCClass {
cancelButtonText: t('No'),
}, (isConfirm) => {
if (isConfirm) {
FlowRouter.goToRoomById(data.room);
goToRoomById(data.room);
return this.joinCall({
to: data.from,
monitor: data.monitor,

@ -0,0 +1,129 @@
module.exports = {
root: true,
extends: ['@rocket.chat/eslint-config', 'prettier'],
parser: 'babel-eslint',
plugins: ['react', 'react-hooks', 'prettier'],
rules: {
'import/named': 'error',
'import/order': [
'error',
{
'newlines-between': 'always',
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
'alphabetize': {
order: 'asc',
},
},
],
'jsx-quotes': ['error', 'prefer-single'],
'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
'prettier/prettier': 2,
'react/display-name': 'error',
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-fragments': ['error', 'syntax'],
'react/no-multi-comp': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': [
'warn',
{
additionalHooks: '(useComponentDidUpdate)',
},
],
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.ts', '.tsx'],
},
},
'react': {
version: 'detect',
},
},
env: {
browser: true,
es6: true,
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'@rocket.chat/eslint-config',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react', 'react-hooks', 'prettier'],
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/interface-name-prefix': ['error', 'always'],
'@typescript-eslint/no-extra-parens': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
},
],
'func-call-spacing': 'off',
'indent': 'off',
'import/order': [
'error',
{
'newlines-between': 'always',
'groups': ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
'alphabetize': {
order: 'asc',
},
},
],
'jsx-quotes': ['error', 'prefer-single'],
'no-extra-parens': 'off',
'no-spaced-func': 'off',
'no-unused-vars': 'off',
'no-useless-constructor': 'off',
'no-use-before-define': 'off',
'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
'prettier/prettier': 2,
'react/display-name': 'error',
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-fragments': ['error', 'syntax'],
'react/no-multi-comp': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': [
'warn',
{
additionalHooks: '(useComponentDidUpdate)',
},
],
},
env: {
browser: true,
es6: true,
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.ts', '.tsx'],
},
},
'react': {
version: 'detect',
},
},
},
{
files: ['**/*.stories.js', '**/*.stories.jsx', '**/*.stories.ts', '**/*.stories.tsx'],
rules: {
'react/display-name': 'off',
'react/no-multi-comp': 'off',
},
},
],
};

@ -0,0 +1,12 @@
{
"semi": true,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"jsxSingleQuote": true,
"printWidth": 100,
"quoteProps": "consistent",
"singleQuote": true,
"trailingComma": "all",
"useTabs": true
}

@ -10,15 +10,20 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import * as ActionManager from '../../../app/ui-message/client/ActionManager';
import { UiKitPayload, UIKitActionEvent } from '../../../definition/UIKit';
const useUIKitHandleAction = <S extends UiKitPayload>(state: S): (event: UIKitActionEvent) => Promise<void> => useMutableCallback(async ({ blockId, value, appId, actionId }) => ActionManager.triggerBlockAction({
container: {
type: UIKitIncomingInteractionContainerType.VIEW,
id: state.viewId || state.appId,
},
actionId,
appId,
value,
blockId,
}));
const useUIKitHandleAction = <S extends UiKitPayload>(
state: S,
): ((event: UIKitActionEvent) => Promise<void>) =>
useMutableCallback(async ({ blockId, value, appId, actionId }) =>
ActionManager.triggerBlockAction({
container: {
type: UIKitIncomingInteractionContainerType.VIEW,
id: state.viewId || state.appId,
},
actionId,
appId,
value,
blockId,
}),
);
export { useUIKitHandleAction };

@ -7,29 +7,36 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
// import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer';
// import { useEndpoint } from '../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
import * as ActionManager from '../../../app/ui-message/client/ActionManager';
import { UiKitPayload } from '../../../definition/UIKit';
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emptyFn = (_error: any, _result: UIKitInteractionType | void): void => undefined;
const useUIKitHandleClose = <S extends UiKitPayload>(state: S, fn = emptyFn): () => Promise<void | UIKitInteractionType> => {
const useUIKitHandleClose = <S extends UiKitPayload>(
state: S,
fn = emptyFn,
): (() => Promise<void | UIKitInteractionType>) => {
const dispatchToastMessage = useToastMessageDispatch();
return useMutableCallback(() => ActionManager.triggerCancel({
appId: state.appId,
viewId: state.viewId,
view: {
...state,
id: state.viewId,
// state: groupStateByBlockId(values),
},
isCleared: true,
}).then((result) => fn(undefined, result)).catch((error) => {
dispatchToastMessage({ type: 'error', message: error });
fn(error, undefined);
return Promise.reject(error);
}));
return useMutableCallback(() =>
ActionManager.triggerCancel({
appId: state.appId,
viewId: state.viewId,
view: {
...state,
id: state.viewId,
// state: groupStateByBlockId(values),
},
isCleared: true,
})
.then((result) => fn(undefined, result))
.catch((error) => {
dispatchToastMessage({ type: 'error', message: error });
fn(error, undefined);
return Promise.reject(error);
}),
);
};
export { useUIKitHandleClose };

@ -1,9 +1,8 @@
import { useEffect, useState } from 'react';
import { useSafely } from '@rocket.chat/fuselage-hooks';
import { useEffect, useState } from 'react';
import { isErrorType, UIKitUserInteractionResult, UiKitPayload } from '../../../definition/UIKit';
import * as ActionManager from '../../../app/ui-message/client/ActionManager';
import { isErrorType, UIKitUserInteractionResult, UiKitPayload } from '../../../definition/UIKit';
const useUIKitStateManager = <S extends UiKitPayload>(initialState: S): S => {
const [state, setState] = useSafely(useState<S>(initialState));

@ -1,7 +0,0 @@
import { createTemplateForComponent } from './reactAdapters';
createTemplateForComponent('MessageActions', () => import('./components/Message/Actions'));
createTemplateForComponent('reactAttachments', () => import('./components/Message/Attachments'));
createTemplateForComponent('ThreadMetric', () => import('./components/Message/Metrics/Thread'));
createTemplateForComponent('DiscussionMetric', () => import('./components/Message/Metrics/Discussion'));
createTemplateForComponent('BroadCastMetric', () => import('./components/Message/Metrics/Broadcast'));

@ -1,18 +0,0 @@
import React from 'react';
import { useSubscription } from 'use-subscription';
import MeteorProvider from '../providers/MeteorProvider';
import { portalsSubscription } from '../reactAdapters';
import BannerRegion from '../views/banners/BannerRegion';
import PortalWrapper from './PortalWrapper';
const AppRoot = () => {
const portals = useSubscription(portalsSubscription);
return <MeteorProvider>
<BannerRegion />
{portals.map(({ key, portal }) => <PortalWrapper key={key} portal={portal} />)}
</MeteorProvider>;
};
export default AppRoot;

@ -1,25 +0,0 @@
import React, { useMemo, useState } from 'react';
import { AutoComplete, Option, Options } from '@rocket.chat/fuselage';
import UserAvatar from './avatar/UserAvatar';
import { useEndpointData } from '../hooks/useEndpointData';
const query = (term = '', conditions = {}) => ({ selector: JSON.stringify({ term, conditions }) });
const Avatar = ({ value, ...props }) => <UserAvatar size={Options.AvatarSize} username={value} {...props} />;
export const UserAutoComplete = React.memo((props) => {
const { conditions = {} } = props;
const [filter, setFilter] = useState('');
// eslint-disable-next-line react-hooks/exhaustive-deps
const { value: data } = useEndpointData('users.autocomplete', useMemo(() => query(filter, conditions), [filter]));
const options = useMemo(() => (data && data.items.map((user) => ({ value: user.username, label: user.name }))) || [], [data]);
return <AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ value, label }) => <><UserAvatar size='x20' username={value} /> {label}</>}
renderItem={({ value, ...props }) => <Option key={value} {...props} avatar={<Avatar value={value} />} />}
options={ options }
/>;
});

@ -1,10 +0,0 @@
import React from 'react';
import { UserAutoComplete } from './AutoComplete';
export default {
title: 'components/AutoComplete',
component: UserAutoComplete,
};
export const Example = () => <UserAutoComplete/>;

@ -1,23 +1,40 @@
import React, { useMemo, useState } from 'react';
import { AutoComplete, Option } from '@rocket.chat/fuselage';
import React, { memo, useMemo, useState } from 'react';
import { useTranslation } from '../contexts/TranslationContext';
import { useEndpointData } from '../hooks/useEndpointData';
export const AutoCompleteAgent = React.memo((props) => {
const AutoCompleteAgent = (props) => {
const t = useTranslation();
const [filter, setFilter] = useState('');
const { value: data } = useEndpointData('livechat/users/agent', useMemo(() => ({ text: filter }), [filter]));
const { value: data } = useEndpointData(
'livechat/users/agent',
useMemo(() => ({ text: filter }), [filter]),
);
const options = useMemo(() => (data && [...data.users.map((user) => ({ value: user._id, label: user.name }))]) || [], [data]);
const optionsWithAll = useMemo(() => (data && [{ value: 'all', label: t('All') }, ...data.users.map((user) => ({ value: user._id, label: user.name }))]) || [{ value: 'all', label: t('All') }], [data, t]);
const options = useMemo(
() => (data && [...data.users.map((user) => ({ value: user._id, label: user.name }))]) || [],
[data],
);
const optionsWithAll = useMemo(
() =>
(data && [
{ value: 'all', label: t('All') },
...data.users.map((user) => ({ value: user._id, label: user.name })),
]) || [{ value: 'all', label: t('All') }],
[data, t],
);
return <AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ label }) => <>{label}</>}
renderItem={({ value, ...props }) => <Option key={value} {...props} />}
options={ props.empty ? options : optionsWithAll }
/>;
});
return (
<AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ label }) => <>{label}</>}
renderItem={({ value, ...props }) => <Option key={value} {...props} />}
options={props.empty ? options : optionsWithAll}
/>
);
};
export default memo(AutoCompleteAgent);

@ -1,24 +1,41 @@
import React, { useMemo, useState } from 'react';
import { AutoComplete, Option } from '@rocket.chat/fuselage';
import React, { memo, useMemo, useState } from 'react';
import { useTranslation } from '../contexts/TranslationContext';
import { useEndpointData } from '../hooks/useEndpointData';
export const AutoCompleteDepartment = React.memo((props) => {
const AutoCompleteDepartment = (props) => {
const t = useTranslation();
const [filter, setFilter] = useState('');
const { value: data } = useEndpointData('livechat/department', useMemo(() => ({ text: filter }), [filter]));
const { value: data } = useEndpointData(
'livechat/department',
useMemo(() => ({ text: filter }), [filter]),
);
const { label } = props;
const options = useMemo(() => (data && [{ value: 'all', label: label && t('All') }, ...data.departments.map((department) => ({ value: department._id, label: department.name }))]) || [{ value: 'all', label: label || t('All') }], [data, label, t]);
const options = useMemo(
() =>
(data && [
{ value: 'all', label: label && t('All') },
...data.departments.map((department) => ({
value: department._id,
label: department.name,
})),
]) || [{ value: 'all', label: label || t('All') }],
[data, label, t],
);
return <AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ label }) => <>{label}</>}
renderItem={({ value, ...props }) => <Option key={value} {...props} />}
options={ options }
/>;
});
return (
<AutoComplete
{...props}
filter={filter}
setFilter={setFilter}
renderSelected={({ label }) => <>{label}</>}
renderItem={({ value, ...props }) => <Option key={value} {...props} />}
options={options}
/>
);
};
export default memo(AutoCompleteDepartment);

@ -1,4 +1,4 @@
import React from 'react';
import { ModalBackdrop } from '@rocket.chat/fuselage';
import React from 'react';
export const Backdrop = (props) => <ModalBackdrop bg='transparent' {...props} />;

@ -0,0 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const Body = ({ children, flexDirection = 'row' }) => (
<Box mb='x8' display='flex' flexDirection={flexDirection} flexGrow={1}>
{children}
</Box>
);
export default Body;

@ -1,46 +1,18 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import { Box, Divider, Icon } from '@rocket.chat/fuselage';
export const DOUBLE_COLUMN_CARD_WIDTH = 552;
const Title = ({ children }) => <Box mb='x8' fontScale='p2'>{children}</Box>;
const Footer = ({ children }) => <Box mb='x8'>{children}</Box>;
const Body = ({ children, flexDirection = 'row' }) => <Box mb='x8' display='flex' flexDirection={flexDirection} flexGrow={1}>{children}</Box>;
const Col = ({ children }) => <Box display='flex' alignSelf='stretch' w='x228' flexDirection='column' fontScale='c1'>{children}</Box>;
const ColSection = ({ children }) => <Box mb='x8' color='info'>{children}</Box>;
const ColTitle = ({ children }) => <Box fontScale='c2' m='none'>{children}</Box>;
const CardDivider = () => <Divider width='x1' mi='x24' mb='none' alignSelf='stretch'/>;
const Card = ({ children, ...props }) => <Box display='flex' flexDirection='column' pi='x16' pb='x8' width='fit-content' bg='neutral-100' {...props}>{children}</Box>;
const CardIcon = ({ name, children, ...props }) => <Box
minWidth='x16'
display='inline-flex'
flexDirection='row'
alignItems='flex-end'
justifyContent='center'
>
{children || <Icon size='x16' name={name} {...props}/>}
</Box>;
Object.assign(Col, {
Title: ColTitle,
Section: ColSection,
});
Object.assign(Card, {
Title,
Body,
Col,
Footer,
Divider: CardDivider,
Icon: CardIcon,
});
const Card = ({ children, ...props }) => (
<Box
display='flex'
flexDirection='column'
pi='x16'
pb='x8'
width='fit-content'
bg='neutral-100'
{...props}
>
{children}
</Box>
);
export default Card;

@ -1,5 +1,5 @@
import React from 'react';
import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
import React from 'react';
import Card from './Card';
@ -8,77 +8,89 @@ export default {
component: Card,
};
export const Single = () => <Box p='x40'>
<Card>
<Card.Title>A card</Card.Title>
<Card.Body>
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
</Card.Body>
<Card.Footer>
<ButtonGroup align='end'>
<Button small>I'm a button in a footer</Button>
</ButtonGroup>
</Card.Footer>
</Card>
</Box>;
export const Single = () => (
<Box p='x40'>
<Card>
<Card.Title>A card</Card.Title>
<Card.Body>
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
</Card.Body>
<Card.Footer>
<ButtonGroup align='end'>
<Button small>I'm a button in a footer</Button>
</ButtonGroup>
</Card.Footer>
</Card>
</Box>
);
export const Double = () => <Box p='x40'>
<Card>
<Card.Title>A card</Card.Title>
<Card.Body>
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
<Card.Divider />
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<Box display='flex' flexDirection='row' alignItems='center'><Card.Icon name='document-eye'/>A bunch of stuff</Box>
<Box display='flex' flexDirection='row' alignItems='center'><Card.Icon name='pencil'/>A bunch of stuff</Box>
<Box display='flex' flexDirection='row' alignItems='center'><Card.Icon name='cross'/>A bunch of stuff</Box>
<Box display='flex' flexDirection='row' alignItems='center'><Card.Icon name='num-pad'/>A bunch of stuff</Box>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
</Card.Body>
<Card.Footer>
<ButtonGroup align='end'>
<Button small>I'm a button in a footer</Button>
</ButtonGroup>
</Card.Footer>
</Card>
</Box>;
export const Double = () => (
<Box p='x40'>
<Card>
<Card.Title>A card</Card.Title>
<Card.Body>
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
<Card.Divider />
<Card.Col>
<Box>
<Card.Col.Title>A Section</Card.Col.Title>
<Box display='flex' flexDirection='row' alignItems='center'>
<Card.Icon name='document-eye' />A bunch of stuff
</Box>
<Box display='flex' flexDirection='row' alignItems='center'>
<Card.Icon name='pencil' />A bunch of stuff
</Box>
<Box display='flex' flexDirection='row' alignItems='center'>
<Card.Icon name='cross' />A bunch of stuff
</Box>
<Box display='flex' flexDirection='row' alignItems='center'>
<Card.Icon name='num-pad' />A bunch of stuff
</Box>
</Box>
<Box>
<Card.Col.Title>Another Section</Card.Col.Title>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
<div>A bunch of stuff</div>
</Box>
</Card.Col>
</Card.Body>
<Card.Footer>
<ButtonGroup align='end'>
<Button small>I'm a button in a footer</Button>
</ButtonGroup>
</Card.Footer>
</Card>
</Box>
);

@ -0,0 +1,6 @@
import { Divider } from '@rocket.chat/fuselage';
import React from 'react';
const CardDivider = () => <Divider width='x1' mi='x24' mb='none' alignSelf='stretch' />;
export default CardDivider;

@ -0,0 +1,16 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React from 'react';
const CardIcon = ({ name, children, ...props }) => (
<Box
minWidth='x16'
display='inline-flex'
flexDirection='row'
alignItems='flex-end'
justifyContent='center'
>
{children || <Icon size='x16' name={name} {...props} />}
</Box>
);
export default CardIcon;

@ -0,0 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const Col = ({ children }) => (
<Box display='flex' alignSelf='stretch' w='x228' flexDirection='column' fontScale='c1'>
{children}
</Box>
);
export default Col;

@ -0,0 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const ColSection = ({ children }) => (
<Box mb='x8' color='info'>
{children}
</Box>
);
export default ColSection;

@ -0,0 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const ColTitle = ({ children }) => (
<Box fontScale='c2' m='none'>
{children}
</Box>
);
export default ColTitle;

@ -0,0 +1,6 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const Footer = ({ children }) => <Box mb='x8'>{children}</Box>;
export default Footer;

@ -0,0 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
const Title = ({ children }) => (
<Box mb='x8' fontScale='p2'>
{children}
</Box>
);
export default Title;

@ -0,0 +1,23 @@
import Body from './Body';
import Card from './Card';
import CardDivider from './CardDivider';
import CardIcon from './CardIcon';
import Col from './Col';
import ColSection from './ColSection';
import ColTitle from './ColTitle';
import Footer from './Footer';
import Title from './Title';
export default Object.assign(Card, {
Title,
Body,
Col: Object.assign(Col, {
Title: ColTitle,
Section: ColSection,
}),
Footer,
Divider: CardDivider,
Icon: CardIcon,
});
export const DOUBLE_COLUMN_CARD_WIDTH = 552;

@ -1,53 +1,89 @@
import React from 'react';
import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage';
import React from 'react';
import RawText from './RawText';
import { useTranslation } from '../contexts/TranslationContext';
import RawText from './RawText';
const ConfirmOwnerChangeWarningModal = ({ onConfirm, onCancel, contentTitle = '', confirmLabel = '', shouldChangeOwner, shouldBeRemoved, ...props }) => {
const ConfirmOwnerChangeWarningModal = ({
onConfirm,
onCancel,
contentTitle = '',
confirmLabel = '',
shouldChangeOwner,
shouldBeRemoved,
...props
}) => {
const t = useTranslation();
let changeOwnerRooms = '';
if (shouldChangeOwner.length > 0) {
if (shouldChangeOwner.length === 1) {
changeOwnerRooms = t('A_new_owner_will_be_assigned_automatically_to_the__roomName__room', { roomName: shouldChangeOwner.pop() });
changeOwnerRooms = t('A_new_owner_will_be_assigned_automatically_to_the__roomName__room', {
roomName: shouldChangeOwner.pop(),
});
} else if (shouldChangeOwner.length <= 5) {
changeOwnerRooms = t('A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__', { count: shouldChangeOwner.length, rooms: shouldChangeOwner.join(', ') });
changeOwnerRooms = t(
'A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__',
{ count: shouldChangeOwner.length, rooms: shouldChangeOwner.join(', ') },
);
} else {
changeOwnerRooms = t('A_new_owner_will_be_assigned_automatically_to__count__rooms', { count: shouldChangeOwner.length });
changeOwnerRooms = t('A_new_owner_will_be_assigned_automatically_to__count__rooms', {
count: shouldChangeOwner.length,
});
}
}
let removedRooms = '';
if (shouldBeRemoved.length > 0) {
if (shouldBeRemoved.length === 1) {
removedRooms = t('The_empty_room__roomName__will_be_removed_automatically', { roomName: shouldBeRemoved.pop() });
removedRooms = t('The_empty_room__roomName__will_be_removed_automatically', {
roomName: shouldBeRemoved.pop(),
});
} else if (shouldBeRemoved.length <= 5) {
removedRooms = t('__count__empty_rooms_will_be_removed_automatically__rooms__', { count: shouldBeRemoved.length, rooms: shouldBeRemoved.join(', ') });
removedRooms = t('__count__empty_rooms_will_be_removed_automatically__rooms__', {
count: shouldBeRemoved.length,
rooms: shouldBeRemoved.join(', '),
});
} else {
removedRooms = t('__count__empty_rooms_will_be_removed_automatically', { count: shouldBeRemoved.length });
removedRooms = t('__count__empty_rooms_will_be_removed_automatically', {
count: shouldBeRemoved.length,
});
}
}
return <Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20}/>
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{contentTitle}
return (
<Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20} />
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel} />
</Modal.Header>
<Modal.Content fontScale='p1'>
{contentTitle}
{ changeOwnerRooms && <Box marginBlock='x16'><RawText>{changeOwnerRooms}</RawText></Box> }
{ removedRooms && <Box marginBlock='x16'><RawText>{removedRooms}</RawText></Box> }
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button ghost onClick={onCancel}>{t('Cancel')}</Button>
<Button primary danger onClick={onConfirm}>{confirmLabel}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
{changeOwnerRooms && (
<Box marginBlock='x16'>
<RawText>{changeOwnerRooms}</RawText>
</Box>
)}
{removedRooms && (
<Box marginBlock='x16'>
<RawText>{removedRooms}</RawText>
</Box>
)}
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button ghost onClick={onCancel}>
{t('Cancel')}
</Button>
<Button primary danger onClick={onConfirm}>
{confirmLabel}
</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
};
export default ConfirmOwnerChangeWarningModal;

@ -1,13 +1,23 @@
import React, { useMemo, useEffect, useState } from 'react';
import { TextInput, Select, Field } from '@rocket.chat/fuselage';
import React, { useMemo, useEffect, useState } from 'react';
import { useSetting } from '../contexts/SettingsContext';
import { useForm } from '../hooks/useForm';
import { useTranslation } from '../contexts/TranslationContext';
import { capitalize } from '../lib/capitalize';
import { useComponentDidUpdate } from '../hooks/useComponentDidUpdate';
import { useForm } from '../hooks/useForm';
import { capitalize } from '../lib/capitalize';
const CustomTextInput = ({ label, name, required, minLength, maxLength, setState, state, className, setCustomFieldsError = () => [] }) => {
const CustomTextInput = ({
label,
name,
required,
minLength,
maxLength,
setState,
state,
className,
setCustomFieldsError = () => [],
}) => {
const t = useTranslation();
const [inputError, setInputError] = useState('');
@ -26,67 +36,122 @@ const CustomTextInput = ({ label, name, required, minLength, maxLength, setState
}, [state, required, minLength, t, label, name]);
useEffect(() => {
setCustomFieldsError((oldErrors) => (verify ? [...oldErrors, { name }] : oldErrors.filter((item) => item.name !== name)));
setCustomFieldsError((oldErrors) =>
verify ? [...oldErrors, { name }] : oldErrors.filter((item) => item.name !== name),
);
}, [name, setCustomFieldsError, verify]);
useComponentDidUpdate(() => {
setInputError(verify);
}, [verify]);
return useMemo(() => <Field className={className}>
<Field.Label>{label || t(name)}{required && '*'}</Field.Label>
<Field.Row>
<TextInput name={name} error={inputError} maxLength={maxLength} flexGrow={1} value={state} onChange={(e) => setState(e.currentTarget.value)}/>
</Field.Row>
<Field.Error>{inputError}</Field.Error>
</Field>, [className, label, t, name, required, inputError, maxLength, state, setState]);
return useMemo(
() => (
<Field className={className}>
<Field.Label>
{label || t(name)}
{required && '*'}
</Field.Label>
<Field.Row>
<TextInput
name={name}
error={inputError}
maxLength={maxLength}
flexGrow={1}
value={state}
onChange={(e) => setState(e.currentTarget.value)}
/>
</Field.Row>
<Field.Error>{inputError}</Field.Error>
</Field>
),
[className, label, t, name, required, inputError, maxLength, state, setState],
);
};
const CustomSelect = ({ label, name, required, options = {}, setState, state, className, setCustomFieldsError = () => [] }) => {
const CustomSelect = ({
label,
name,
required,
options = {},
setState,
state,
className,
setCustomFieldsError = () => [],
}) => {
const t = useTranslation();
const [selectError, setSelectError] = useState('');
const mappedOptions = useMemo(() => Object.values(options).map((value) => [value, value]), [options]);
const verify = useMemo(() => (!state.length && required ? t('The_field_is_required', label || name) : ''), [name, label, required, state.length, t]);
const mappedOptions = useMemo(() => Object.values(options).map((value) => [value, value]), [
options,
]);
const verify = useMemo(
() => (!state.length && required ? t('The_field_is_required', label || name) : ''),
[name, label, required, state.length, t],
);
useEffect(() => {
setCustomFieldsError((oldErrors) => (verify ? [...oldErrors, { name }] : oldErrors.filter((item) => item.name !== name)));
setCustomFieldsError((oldErrors) =>
verify ? [...oldErrors, { name }] : oldErrors.filter((item) => item.name !== name),
);
}, [name, setCustomFieldsError, verify]);
useComponentDidUpdate(() => {
setSelectError(verify);
}, [verify]);
return useMemo(() => <Field className={className}>
<Field.Label>{label || t(name)}{required && '*'}</Field.Label>
<Field.Row>
<Select name={name} error={selectError} flexGrow={1} value={state} options={mappedOptions} onChange={(val) => setState(val)}/>
</Field.Row>
<Field.Error>{selectError}</Field.Error>
</Field>, [className, label, t, name, required, selectError, state, mappedOptions, setState]);
return useMemo(
() => (
<Field className={className}>
<Field.Label>
{label || t(name)}
{required && '*'}
</Field.Label>
<Field.Row>
<Select
name={name}
error={selectError}
flexGrow={1}
value={state}
options={mappedOptions}
onChange={(val) => setState(val)}
/>
</Field.Row>
<Field.Error>{selectError}</Field.Error>
</Field>
),
[className, label, t, name, required, selectError, state, mappedOptions, setState],
);
};
const CustomFieldsAssembler = ({ formValues, formHandlers, customFields, ...props }) => Object.entries(customFields).map(([key, value]) => {
const extraProps = {
key,
name: key,
setState: formHandlers[`handle${ capitalize(key) }`],
state: formValues[key],
...value,
};
if (value.type === 'select') {
return <CustomSelect {...extraProps} {...props}/>;
}
const CustomFieldsAssembler = ({ formValues, formHandlers, customFields, ...props }) =>
Object.entries(customFields).map(([key, value]) => {
const extraProps = {
key,
name: key,
setState: formHandlers[`handle${capitalize(key)}`],
state: formValues[key],
...value,
};
if (value.type === 'select') {
return <CustomSelect {...extraProps} {...props} />;
}
if (value.type === 'text') {
return <CustomTextInput {...extraProps} {...props}/>;
}
if (value.type === 'text') {
return <CustomTextInput {...extraProps} {...props} />;
}
return null;
});
return null;
});
export default function CustomFieldsForm({ jsonCustomFields, customFieldsData, setCustomFieldsData, onLoadFields = () => {}, ...props }) {
export default function CustomFieldsForm({
jsonCustomFields,
customFieldsData,
setCustomFieldsData,
onLoadFields = () => {},
...props
}) {
const accountsCustomFieldsJson = useSetting('Accounts_CustomFields');
const [customFields] = useState(() => {
@ -99,8 +164,11 @@ export default function CustomFieldsForm({ jsonCustomFields, customFieldsData, s
const hasCustomFields = Boolean(Object.values(customFields).length);
const defaultFields = useMemo(
() => Object.entries(customFields)
.reduce((data, [key, value]) => { data[key] = value.defaultValue ?? ''; return data; }, {}),
() =>
Object.entries(customFields).reduce((data, [key, value]) => {
data[key] = value.defaultValue ?? '';
return data;
}, {}),
[customFields],
);
@ -117,5 +185,12 @@ export default function CustomFieldsForm({ jsonCustomFields, customFieldsData, s
return null;
}
return <CustomFieldsAssembler formValues={values} formHandlers={handlers} customFields={customFields} {...props}/>;
return (
<CustomFieldsAssembler
formValues={values}
formHandlers={handlers}
customFields={customFields}
{...props}
/>
);
}

@ -1,27 +1,29 @@
import React from 'react';
import { Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from '../contexts/TranslationContext';
const DeleteChannelWarning = ({ onConfirm, onCancel, ...props }) => {
const t = useTranslation();
return <Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20}/>
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{t('Delete_Room_Warning')}
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button primary danger onClick={onConfirm}>{t('Yes_delete_it')}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
return (
<Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20} />
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel} />
</Modal.Header>
<Modal.Content fontScale='p1'>{t('Delete_Room_Warning')}</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button primary danger onClick={onConfirm}>
{t('Yes_delete_it')}
</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
};
export default DeleteChannelWarning;

@ -3,7 +3,13 @@ import React from 'react';
import { useTranslation } from '../contexts/TranslationContext';
import DeleteWarningModal from './DeleteWarningModal';
export default ({ onConfirm, onCancel }) => {
const DeleteFileWarning = ({ onConfirm, onCancel }) => {
const t = useTranslation();
return <DeleteWarningModal onCancel={onCancel} onDelete={onConfirm}>{t('Delete_File_Warning')}</DeleteWarningModal>;
return (
<DeleteWarningModal onCancel={onCancel} onDelete={onConfirm}>
{t('Delete_File_Warning')}
</DeleteWarningModal>
);
};
export default DeleteFileWarning;

@ -1,5 +1,5 @@
import React, { FC } from 'react';
import { Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
import { useTranslation } from '../contexts/TranslationContext';
@ -7,27 +7,25 @@ type DeleteSuccessModalProps = {
onClose: () => void;
};
const DeleteSuccessModal: FC<DeleteSuccessModalProps> = ({
children,
onClose,
...props
}) => {
const DeleteSuccessModal: FC<DeleteSuccessModalProps> = ({ children, onClose, ...props }) => {
const t = useTranslation();
return <Modal {...props}>
<Modal.Header>
<Icon color='success' name='checkmark-circled' size={20}/>
<Modal.Title>{t('Deleted')}</Modal.Title>
<Modal.Close onClick={onClose}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{children}
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button primary onClick={onClose}>{t('Ok')}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
return (
<Modal {...props}>
<Modal.Header>
<Icon color='success' name='checkmark-circled' size={20} />
<Modal.Title>{t('Deleted')}</Modal.Title>
<Modal.Close onClick={onClose} />
</Modal.Header>
<Modal.Content fontScale='p1'>{children}</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button primary onClick={onClose}>
{t('Ok')}
</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>
);
};
export default DeleteSuccessModal;

@ -22,24 +22,28 @@ const DeleteWarningModal: FC<DeleteWarningModalProps> = ({
}) => {
const t = useTranslation();
return <Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20}/>
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{children}
</Modal.Content>
<Modal.Footer>
<Box display='flex' flexDirection='row' justifyContent='space-between' alignItems='center'>
<ButtonGroup align='end'>
<Button ghost onClick={onCancel}>{cancelText ?? t('Cancel')}</Button>
<Button primary danger onClick={confirm}>{deleteText ?? t('Delete')}</Button>
</ButtonGroup>
</Box>
</Modal.Footer>
</Modal>;
return (
<Modal {...props}>
<Modal.Header>
<Icon color='danger' name='modal-warning' size={20} />
<Modal.Title>{t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onCancel} />
</Modal.Header>
<Modal.Content fontScale='p1'>{children}</Modal.Content>
<Modal.Footer>
<Box display='flex' flexDirection='row' justifyContent='space-between' alignItems='center'>
<ButtonGroup align='end'>
<Button ghost onClick={onCancel}>
{cancelText ?? t('Cancel')}
</Button>
<Button primary danger onClick={confirm}>
{deleteText ?? t('Delete')}
</Button>
</ButtonGroup>
</Box>
</Modal.Footer>
</Modal>
);
};
export default DeleteWarningModal;

@ -1,5 +1,5 @@
import React from 'react';
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import DotLeader from './DotLeader';
@ -8,8 +8,10 @@ export default {
component: DotLeader,
};
export const Default = () => <Box display='flex' flexDirection='row'>
Label
<DotLeader />
12345
</Box>;
export const Default = () => (
<Box display='flex' flexDirection='row'>
Label
<DotLeader />
12345
</Box>
);

@ -1,20 +1,21 @@
import React, { FC, CSSProperties } from 'react';
import { Box } from '@rocket.chat/fuselage';
import React, { FC, CSSProperties } from 'react';
type DotLeaderProps = {
color: CSSProperties['borderColor'];
dotSize: CSSProperties['borderBlockEndWidth'];
}
};
const DotLeader: FC<DotLeaderProps> = ({ color = 'neutral-300', dotSize = 'x2' }) => <Box
flexGrow={1}
h='full'
alignSelf='flex-end'
borderBlockEndStyle='dotted'
borderBlockEndWidth={dotSize}
m='x2'
borderColor={color}
/>;
const DotLeader: FC<DotLeaderProps> = ({ color = 'neutral-300', dotSize = 'x2' }) => (
<Box
flexGrow={1}
h='full'
alignSelf='flex-end'
borderBlockEndStyle='dotted'
borderBlockEndWidth={dotSize}
m='x2'
borderColor={color}
/>
);
export default DotLeader;

@ -3,8 +3,8 @@ import React from 'react';
import { renderEmoji } from '../../app/emoji/client/index';
function Emoji({ emojiHandle }) {
const markup = { __html: `${ renderEmoji(emojiHandle) }` };
return <div dangerouslySetInnerHTML={ markup }></div>;
const markup = { __html: `${renderEmoji(emojiHandle)}` };
return <div dangerouslySetInnerHTML={markup}></div>;
}
export default Emoji;

@ -5,9 +5,10 @@ type ExternalLinkProps = {
to: string;
};
const ExternalLink: FC<ExternalLinkProps> = ({ children, to, ...props }) =>
const ExternalLink: FC<ExternalLinkProps> = ({ children, to, ...props }) => (
<Box is='a' href={to} target='_blank' rel='noopener noreferrer' {...props}>
{children || to}
</Box>;
</Box>
);
export default ExternalLink;

@ -37,10 +37,27 @@ const FilterByText: FC<FilterByTextProps> = ({
event.preventDefault();
}, []);
return <Box mb='x16' is='form' onSubmit={handleFormSubmit} display='flex' flexDirection='row' {...props}>
<TextInput placeholder={placeholder ?? t('Search')} ref={inputRef} addon={<Icon name='magnifier' size='x20'/>} onChange={handleInputChange} value={text} />
<Button onClick={onButtonClick} display={display ? 'block' : 'none'} mis='x8' primary>{textButton}</Button>
</Box>;
return (
<Box
mb='x16'
is='form'
onSubmit={handleFormSubmit}
display='flex'
flexDirection='row'
{...props}
>
<TextInput
placeholder={placeholder ?? t('Search')}
ref={inputRef}
addon={<Icon name='magnifier' size='x20' />}
onChange={handleInputChange}
value={text}
/>
<Button onClick={onButtonClick} display={display ? 'block' : 'none'} mis='x8' primary>
{textButton}
</Button>
</Box>
);
};
export default memo(FilterByText);

@ -2,7 +2,6 @@ import React from 'react';
import GenericModal, { GenericModalDoNotAskAgain } from './GenericModal';
export default {
title: 'components/GenericModal',
component: GenericModal,
@ -15,4 +14,6 @@ export const _default = () => <GenericModal {...defaultProps} />;
export const Danger = () => <GenericModal {...defaultProps} variant='danger' />;
export const Warning = () => <GenericModal {...defaultProps} variant='warning' />;
export const Success = () => <GenericModal {...defaultProps} variant='success' />;
export const WithDontAskAgain = () => <GenericModalDoNotAskAgain dontAskAgain={{ action: '', label: '' }} {...defaultProps} />;
export const WithDontAskAgain = () => (
<GenericModalDoNotAskAgain dontAskAgain={{ action: '', label: '' }} {...defaultProps} />
);

@ -31,7 +31,7 @@ const getButtonProps = (variant: VariantType): ButtonProps => {
case 'warning':
return { primary: true };
default:
return { };
return {};
}
};
@ -50,25 +50,31 @@ const GenericModal: FC<GenericModalProps> = ({
}) => {
const t = useTranslation();
return <Modal {...props}>
<Modal.Header>
{icon !== null && <Icon color={variant} name={icon ?? iconMap[variant]} size={24}/>}
<Modal.Title>{title ?? t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onClose}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{children}
</Modal.Content>
<Modal.Footer>
<Box display='flex' flexDirection='row' justifyContent='space-between' alignItems='center'>
{dontAskAgain}
<ButtonGroup align='end' flexGrow={1}>
{onCancel && <Button ghost onClick={onCancel}>{cancelText ?? t('Cancel')}</Button>}
<Button {...getButtonProps(variant)} onClick={onConfirm}>{confirmText ?? t('Ok')}</Button>
</ButtonGroup>
</Box>
</Modal.Footer>
</Modal>;
return (
<Modal {...props}>
<Modal.Header>
{icon !== null && <Icon color={variant} name={icon ?? iconMap[variant]} size={24} />}
<Modal.Title>{title ?? t('Are_you_sure')}</Modal.Title>
<Modal.Close onClick={onClose} />
</Modal.Header>
<Modal.Content fontScale='p1'>{children}</Modal.Content>
<Modal.Footer>
<Box display='flex' flexDirection='row' justifyContent='space-between' alignItems='center'>
{dontAskAgain}
<ButtonGroup align='end' flexGrow={1}>
{onCancel && (
<Button ghost onClick={onCancel}>
{cancelText ?? t('Cancel')}
</Button>
)}
<Button {...getButtonProps(variant)} onClick={onConfirm}>
{confirmText ?? t('Ok')}
</Button>
</ButtonGroup>
</Box>
</Modal.Footer>
</Modal>
);
};
export const GenericModalDoNotAskAgain = withDoNotAskAgain<GenericModalProps>(GenericModal);

@ -1,21 +1,24 @@
import React from 'react';
import { TextInput, Box, Icon } from '@rocket.chat/fuselage';
import React from 'react';
import GenericTable from './GenericTable';
export default {
title: 'components/GenericTable',
component: GenericTable,
decorators: [(fn) => <div children={fn()} style={{ height: '100vh' }} />],
};
export const _default = () => {
const Search = () => <Box mb='x16' is='form' display='flex' flexDirection='column'>
<TextInput flexShrink={0} placeholder='Search...' addon={<Icon name='magnifier' size='x20'/>}/>
</Box>;
const Search = () => (
<Box mb='x16' is='form' display='flex' flexDirection='column'>
<TextInput
flexShrink={0}
placeholder='Search...'
addon={<Icon name='magnifier' size='x20' />}
/>
</Box>
);
const header = [
<GenericTable.HeaderCell>Name</GenericTable.HeaderCell>,
@ -23,8 +26,5 @@ export const _default = () => {
<GenericTable.HeaderCell>Data</GenericTable.HeaderCell>,
<GenericTable.HeaderCell>Info</GenericTable.HeaderCell>,
];
return <GenericTable
header={header}
renderFilter={(props) => <Search {...props} />}
/>;
return <GenericTable header={header} renderFilter={(props) => <Search {...props} />} />;
};

@ -19,12 +19,14 @@ const HeaderCell: FC<HeaderCellProps> = ({
...props
}) => {
const fn = useCallback(() => onClick && sort && onClick(sort), [sort, onClick]);
return <Table.Cell clickable={!!sort} onClick={fn} { ...props }>
<Box display='flex' alignItems='center' wrap='no-wrap'>
{children}
{sort && <SortIcon direction={active ? direction : undefined} />}
</Box>
</Table.Cell>;
return (
<Table.Cell clickable={!!sort} onClick={fn} {...props}>
<Box display='flex' alignItems='center' wrap='no-wrap'>
{children}
{sort && <SortIcon direction={active ? direction : undefined} />}
</Box>
</Table.Cell>
);
};
export default HeaderCell;

@ -5,7 +5,7 @@ type LoadingRowProps = {
cols: number;
};
const LoadingRow: FC<LoadingRowProps> = ({ cols }) =>
const LoadingRow: FC<LoadingRowProps> = ({ cols }) => (
<Table.Row>
<Table.Cell>
<Box display='flex'>
@ -16,9 +16,12 @@ const LoadingRow: FC<LoadingRowProps> = ({ cols }) =>
</Box>
</Box>
</Table.Cell>
{Array.from({ length: cols - 1 }, (_, i) => <Table.Cell key={i}>
<Skeleton width='100%' />
</Table.Cell>)}
</Table.Row>;
{Array.from({ length: cols - 1 }, (_, i) => (
<Table.Cell key={i}>
<Skeleton width='100%' />
</Table.Cell>
))}
</Table.Row>
);
export default LoadingRow;

@ -5,10 +5,30 @@ type SortIconProps = {
direction?: 'asc' | 'desc';
};
const SortIcon: FC<SortIconProps> = ({ direction }) =>
<Box is='svg' width='x16' height='x16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d='M5.33337 5.99999L8.00004 3.33333L10.6667 5.99999' stroke={direction === 'desc' ? '#9EA2A8' : '#E4E7EA' } strokeWidth='1.33333' strokeLinecap='round' strokeLinejoin='round'/>
<path d='M5.33337 10L8.00004 12.6667L10.6667 10' stroke={direction === 'asc' ? '#9EA2A8' : '#E4E7EA'} strokeWidth='1.33333' strokeLinecap='round' strokeLinejoin='round'/>
</Box>;
const SortIcon: FC<SortIconProps> = ({ direction }) => (
<Box
is='svg'
width='x16'
height='x16'
viewBox='0 0 16 16'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M5.33337 5.99999L8.00004 3.33333L10.6667 5.99999'
stroke={direction === 'desc' ? '#9EA2A8' : '#E4E7EA'}
strokeWidth='1.33333'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M5.33337 10L8.00004 12.6667L10.6667 10'
stroke={direction === 'asc' ? '#9EA2A8' : '#E4E7EA'}
strokeWidth='1.33333'
strokeLinecap='round'
strokeLinejoin='round'
/>
</Box>
);
export default SortIcon;

@ -4,23 +4,26 @@ import React, { useState, useEffect, useCallback, forwardRef } from 'react';
import flattenChildren from 'react-keyed-flatten-children';
import { useTranslation } from '../../contexts/TranslationContext';
import ScrollableContentWrapper from '../ScrollableContentWrapper';
import HeaderCell from './HeaderCell';
import LoadingRow from './LoadingRow';
import ScrollableContentWrapper from '../ScrollableContentWrapper';
const GenericTable = ({
children,
fixed = true,
header,
params: paramsDefault = '',
renderFilter,
renderRow: RenderRow,
results,
setParams = () => { },
total,
pagination = true,
...props
}, ref) => {
const GenericTable = (
{
children,
fixed = true,
header,
params: paramsDefault = '',
renderFilter,
renderRow: RenderRow,
results,
setParams = () => {},
total,
pagination = true,
...props
},
ref,
) => {
const t = useTranslation();
const [filter, setFilter] = useState(paramsDefault);
@ -40,49 +43,61 @@ const GenericTable = ({
return Array.from({ length: 10 }, (_, i) => <LoadingRow key={i} cols={headerCells.length} />);
}, [header]);
const showingResultsLabel = useCallback(({ count, current, itemsPerPage }) => t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count), [t]);
const showingResultsLabel = useCallback(
({ count, current, itemsPerPage }) =>
t('Showing_results_of', current + 1, Math.min(current + itemsPerPage, count), count),
[t],
);
const itemsPerPageLabel = useCallback(() => t('Items_per_page:'), [t]);
return <>
{typeof renderFilter === 'function' ? renderFilter({ onChange: setFilter, ...props }) : null}
{results && !results.length
? <Tile fontScale='p1' elevation='0' color='info' textAlign='center'>
{t('No_data_found')}
</Tile>
: <>
<Box mi='neg-x24' pi='x24' flexShrink={1} flexGrow={1} ref={ref} overflow='hidden'>
<ScrollableContentWrapper>
<Table fixed={fixed} sticky>
{header && <Table.Head>
<Table.Row>
{header}
</Table.Row>
</Table.Head>}
<Table.Body>
{RenderRow && (
results
? results.map((props, index) => <RenderRow key={props._id || index} { ...props }/>)
: <Loading/>
return (
<>
{typeof renderFilter === 'function' ? renderFilter({ onChange: setFilter, ...props }) : null}
{results && !results.length ? (
<Tile fontScale='p1' elevation='0' color='info' textAlign='center'>
{t('No_data_found')}
</Tile>
) : (
<>
<Box mi='neg-x24' pi='x24' flexShrink={1} flexGrow={1} ref={ref} overflow='hidden'>
<ScrollableContentWrapper>
<Table fixed={fixed} sticky>
{header && (
<Table.Head>
<Table.Row>{header}</Table.Row>
</Table.Head>
)}
{children && (results ? results.map(children) : <Loading />)}
</Table.Body>
</Table>
</ScrollableContentWrapper>
</Box>
{pagination && <Pagination
divider
current={current}
itemsPerPage={itemsPerPage}
itemsPerPageLabel={itemsPerPageLabel}
showingResultsLabel={showingResultsLabel}
count={total || 0}
onSetItemsPerPage={setItemsPerPage}
onSetCurrent={setCurrent}
/>}
</>
}
</>;
<Table.Body>
{RenderRow &&
(results ? (
results.map((props, index) => (
<RenderRow key={props._id || index} {...props} />
))
) : (
<Loading />
))}
{children && (results ? results.map(children) : <Loading />)}
</Table.Body>
</Table>
</ScrollableContentWrapper>
</Box>
{pagination && (
<Pagination
divider
current={current}
itemsPerPage={itemsPerPage}
itemsPerPageLabel={itemsPerPageLabel}
showingResultsLabel={showingResultsLabel}
count={total || 0}
onSetItemsPerPage={setItemsPerPage}
onSetCurrent={setCurrent}
/>
)}
</>
)}
</>
);
};
export default Object.assign(forwardRef(GenericTable), {

@ -0,0 +1,7 @@
import React, { FC } from 'react';
import Button from './Button';
const Avatar: FC<any> = (props) => <Button width='x36' {...props} />;
export default Avatar;

@ -0,0 +1,6 @@
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const Button: FC<any> = (props) => <Box mi='x4' display='flex' alignItems='center' {...props} />;
export default Button;

@ -0,0 +1,17 @@
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const Content: FC<any> = (props) => (
<Box
flexGrow={1}
width={1}
flexShrink={1}
mi='x4'
display='flex'
justifyContent='center'
flexDirection='column'
{...props}
/>
);
export default Content;

@ -1,12 +1,12 @@
import React from 'react';
import { SettingsContext } from '../../contexts/SettingsContext';
import Header from './Header';
import RoomAvatar from '../avatar/RoomAvatar';
import { useRoomIcon } from '../../hooks/useRoomIcon';
import ToolBox from '../../views/room/Header/ToolBox';
import { ToolboxProvider } from '../../views/room/providers/ToolboxProvider';
import { addAction } from '../../views/room/lib/Toolbox';
import { useRoomIcon } from '../../hooks/useRoomIcon';
import ToolboxProvider from '../../views/room/providers/ToolboxProvider';
import RoomAvatar from '../avatar/RoomAvatar';
import Header from './Header';
export default {
title: 'Chat Header',
@ -15,7 +15,8 @@ export default {
const room = {
t: 'c',
name: 'general general general general general general general general general general general general general general general general general general general',
name:
'general general general general general general general general general general general general general general general general general general general',
_id: 'GENERAL',
encrypted: true,
autoTranslate: true,
@ -28,7 +29,6 @@ const settings = {
E2E_Enable: true,
};
const settingContextValue = {
hasPrivateAccess: true,
isLoading: false,
@ -45,29 +45,31 @@ const settingContextValue = {
export const ChatHeader = () => {
const icon = useRoomIcon(room);
const avatar = <RoomAvatar room={room}/>;
return <SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{ icon && <Header.Icon icon={icon}/> }
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star'/>
<Header.State icon='key'/>
<Header.State icon='language'/>
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<Header.ToolBoxAction icon='magnifier'/>
<Header.ToolBoxAction icon='key'/>
<Header.ToolBoxAction icon='kebab'/>
</Header.ToolBox>
</Header>
</SettingsContext.Provider>;
const avatar = <RoomAvatar room={room} />;
return (
<SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{icon && <Header.Icon icon={icon} />}
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star' />
<Header.State icon='key' />
<Header.State icon='language' />
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<Header.ToolBoxAction icon='magnifier' />
<Header.ToolBoxAction icon='key' />
<Header.ToolBoxAction icon='kebab' />
</Header.ToolBox>
</Header>
</SettingsContext.Provider>
);
};
const toolboxRoom = {
@ -81,18 +83,23 @@ const toolboxRoom = {
// const renderWithRedBadge = createHeaderActionRenderer(<Header.Badge variant='danger'>2</Header.Badge>);
// const renderWithOrangeBadge = createHeaderActionRenderer(<Header.Badge variant='warning'>99</Header.Badge>);
const renderWithBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='primary'>1</Header.Badge>
</Header.ToolBoxAction>;
const renderWithBadge = (props, index) => (
<Header.ToolBoxAction index={index} {...props}>
<Header.Badge variant='primary'>1</Header.Badge>
</Header.ToolBoxAction>
);
const renderWithRedBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='danger'>2</Header.Badge>
</Header.ToolBoxAction>;
const renderWithOrangeBadge = (props, index) => <Header.ToolBoxAction index={index} {...props} >
<Header.Badge variant='warning'>99</Header.Badge>
</Header.ToolBoxAction>;
const renderWithRedBadge = (props, index) => (
<Header.ToolBoxAction index={index} {...props}>
<Header.Badge variant='danger'>2</Header.Badge>
</Header.ToolBoxAction>
);
const renderWithOrangeBadge = (props, index) => (
<Header.ToolBoxAction index={index} {...props}>
<Header.Badge variant='warning'>99</Header.Badge>
</Header.ToolBoxAction>
);
addAction('render-action-example-badge', {
groups: ['channel'],
@ -114,7 +121,6 @@ addAction('render-action-example-badge-warning', {
renderAction: renderWithOrangeBadge,
});
addAction('render-action-example-badge-danger', {
groups: ['channel'],
id: 'render-action-example',
@ -127,27 +133,29 @@ addAction('render-action-example-badge-danger', {
export const WithToolboxContext = () => {
const icon = useRoomIcon(room);
const avatar = <RoomAvatar room={room}/>;
return <SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{ icon && <Header.Icon icon={icon}/> }
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star'/>
<Header.State icon='key'/>
<Header.State icon='language'/>
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<ToolboxProvider room={toolboxRoom}>
<ToolBox />
</ToolboxProvider>
</Header.ToolBox>
</Header>
</SettingsContext.Provider>;
const avatar = <RoomAvatar room={room} />;
return (
<SettingsContext.Provider value={settingContextValue}>
<Header>
<Header.Avatar>{avatar}</Header.Avatar>
<Header.Content>
<Header.Content.Row>
{icon && <Header.Icon icon={icon} />}
<Header.Title>{room.name}</Header.Title>
<Header.State onClick icon='star' />
<Header.State icon='key' />
<Header.State icon='language' />
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{room.name}</Header.Subtitle>
</Header.Content.Row>
</Header.Content>
<Header.ToolBox>
<ToolboxProvider room={toolboxRoom}>
<ToolBox />
</ToolboxProvider>
</Header.ToolBox>
</Header>
</SettingsContext.Provider>
);
};

@ -1,118 +1,33 @@
import React, { FC, isValidElement, ReactElement } from 'react';
import { css } from '@rocket.chat/css-in-js';
import colors from '@rocket.chat/fuselage-tokens/colors';
import {
Box,
Icon,
Divider,
ButtonGroup,
ActionButton,
Badge,
BadgeProps,
Tag,
TagProps,
BoxProps,
Skeleton,
} from '@rocket.chat/fuselage';
type HeaderIconProps = {icon: ReactElement | { name: string; color?: string } | null};
const Title: FC = (props: any) => <Box color='default' mi='x4' fontScale='s2' withTruncatedText {...props}/>;
const Subtitle: FC = (props: any) => <Box color='hint' fontScale='p1' withTruncatedText {...props}/>;
const Row: FC = (props: any) => <Box alignItems='center' flexShrink={1} flexGrow={1} display='flex' {...props}/>;
const HeaderIcon: FC<HeaderIconProps> = ({ icon }) => icon && <Box display='flex' flexShrink={0} alignItems='center' size={18} overflow='hidden' justifyContent='center'>
{React.isValidElement(icon) ? icon : <Icon color='info' size='x18' { ...{ name: (icon as any).name }} />}
</Box>;
const ToolBox: FC = (props: any) => <ButtonGroup mi='x4' medium {...props}/>;
const HeaderLink: FC = (props: BoxProps) => <Box
is='a'
{...props}
className={[
css`
&:hover,
&:focus{
color: ${ colors.n800 } !important;
}
&:visited{
color: ${ colors.n800 };
}
`,
].filter(Boolean)}
/>;
const HeaderTag: FC = ({ children, ...props }: TagProps) => <Box mi='x4'><Tag {...props}><Box alignItems='center' fontScale='c2' display='flex'>{children}</Box></Tag></Box>;
const HeaderTagIcon: FC<HeaderIconProps> = ({ icon }) => (icon ? <Box w='x20' mi='x2' display='inline-flex' justifyContent='center'>
{isValidElement(icon) ? icon : <Icon size='x20' {...icon}/>}
</Box> : null);
const HeaderTagSkeleton: FC = () => <Skeleton width='x48'/>;
const ToolBoxAction: FC = ({ id, icon, color, title, action, className, tabId, index, ...props }: any) => <ActionButton
className={className}
primary={tabId === id}
onClick={action}
title={title}
data-toolbox={index}
key={id}
icon={icon}
position='relative'
ghost
tiny
overflow='visible'
color={!!color && color}
{...props}
/>;
const ToolBoxActionBadge: FC<BadgeProps> = (props) => <Box position='absolute' className={css`top: 0; right: 0; transform: translate(30%, -30%);`}><Badge {...props}/></Box>;
const State: FC = (props: any) => (props.onClick ? <ActionButton ghost mini {...props}/> : <Icon size={16} name={props.icon} {...props}/>);
const Content: FC = (props: any) => <Box flexGrow={1} width={1} flexShrink={1} mi='x4' display='flex' justifyContent='center' flexDirection='column' {...props}/>;
const Button: FC = (props: any) => <Box mi='x4' display='flex' alignItems='center' {...props}/>;
const Avatar: FC = (props: any) => <Button width='x36' {...props}/>;
const HeaderDivider: FC = () => <Divider { ...{ mbs: 'neg-x2', mbe: 0 } as any} />;
const Header: FC & { ToolBoxAction: FC; Badge: FC<BadgeProps> } = (props: any) => <Box rcx-room-header is='header' height='x64' display='flex' justifyContent='center' flexDirection='column' overflow='hidden' flexShrink={0}>
<Box height='x64' mi='neg-x4' pi='x24' display='flex' flexGrow={1} justifyContent='center' alignItems='center' overflow='hidden' flexDirection='row' {...props}/>
<HeaderDivider/>
</Box>;
Header.ToolBoxAction = ToolBoxAction;
Header.Badge = ToolBoxActionBadge;
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
import HeaderDivider from './HeaderDivider';
const Header: FC<any> = (props) => (
<Box
rcx-room-header
is='header'
height='x64'
display='flex'
justifyContent='center'
flexDirection='column'
overflow='hidden'
flexShrink={0}
>
<Box
height='x64'
mi='neg-x4'
pi='x24'
display='flex'
flexGrow={1}
justifyContent='center'
alignItems='center'
overflow='hidden'
flexDirection='row'
{...props}
/>
<HeaderDivider />
</Box>
);
export default Header;
Object.assign(Content, {
Row,
});
Object.assign(ToolBoxAction, {
Badge: ToolBoxActionBadge,
});
Object.assign(HeaderTag, {
Icon: HeaderTagIcon,
Skeleton: HeaderTagSkeleton,
});
Object.assign(Header, {
Button,
State,
Avatar,
Content,
Title,
Subtitle,
ToolBox,
ToolBoxAction,
Divider: HeaderDivider,
Icon: HeaderIcon,
Link: HeaderLink,
Tag: HeaderTag,
});

@ -0,0 +1,6 @@
import { Divider } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const HeaderDivider: FC = () => <Divider mbs={-2} mbe={0} />;
export default HeaderDivider;

@ -0,0 +1,24 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React, { FC, isValidElement, ReactElement } from 'react';
type HeaderIconProps = { icon: ReactElement | { name: string; color?: string } | null };
const HeaderIcon: FC<HeaderIconProps> = ({ icon }) =>
icon && (
<Box
display='flex'
flexShrink={0}
alignItems='center'
size={18}
overflow='hidden'
justifyContent='center'
>
{isValidElement(icon) ? (
icon
) : (
<Icon color='info' size='x18' {...{ name: (icon as any).name }} />
)}
</Box>
);
export default HeaderIcon;

@ -0,0 +1,24 @@
import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
import colors from '@rocket.chat/fuselage-tokens/colors';
import React, { ComponentProps, FC } from 'react';
const HeaderLink: FC<ComponentProps<typeof Box>> = (props) => (
<Box
is='a'
{...props}
className={[
css`
&:hover,
&:focus {
color: ${colors.n800} !important;
}
&:visited {
color: ${colors.n800};
}
`,
].filter(Boolean)}
/>
);
export default HeaderLink;

@ -0,0 +1,14 @@
import { Box, Tag } from '@rocket.chat/fuselage';
import React, { ComponentProps, FC } from 'react';
const HeaderTag: FC<ComponentProps<typeof Tag>> = ({ children, ...props }) => (
<Box mi='x4'>
<Tag {...props}>
<Box alignItems='center' fontScale='c2' display='flex'>
{children}
</Box>
</Tag>
</Box>
);
export default HeaderTag;

@ -0,0 +1,13 @@
import { Box, Icon } from '@rocket.chat/fuselage';
import React, { FC, isValidElement, ReactElement } from 'react';
type HeaderIconProps = { icon: ReactElement | { name: string; color?: string } | null };
const HeaderTagIcon: FC<HeaderIconProps> = ({ icon }) =>
icon ? (
<Box w='x20' mi='x2' display='inline-flex' justifyContent='center'>
{isValidElement(icon) ? icon : <Icon size='x20' {...icon} />}
</Box>
) : null;
export default HeaderTagIcon;

@ -0,0 +1,5 @@
import { Skeleton } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const HeaderTagSkeleton: FC = () => <Skeleton width='x48' />;
export default HeaderTagSkeleton;

@ -0,0 +1,8 @@
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const Row: FC<any> = (props) => (
<Box alignItems='center' flexShrink={1} flexGrow={1} display='flex' {...props} />
);
export default Row;

@ -0,0 +1,11 @@
import { Icon, ActionButton } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const State: FC<any> = (props) =>
props.onClick ? (
<ActionButton ghost mini {...props} />
) : (
<Icon size={16} name={props.icon} {...props} />
);
export default State;

@ -0,0 +1,8 @@
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const Subtitle: FC<any> = (props) => (
<Box color='hint' fontScale='p1' withTruncatedText {...props} />
);
export default Subtitle;

@ -0,0 +1,8 @@
import { Box } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const Title: FC<any> = (props) => (
<Box color='default' mi='x4' fontScale='s2' withTruncatedText {...props} />
);
export default Title;

@ -0,0 +1,6 @@
import { ButtonGroup } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const ToolBox: FC<any> = (props) => <ButtonGroup mi='x4' medium {...props} />;
export default ToolBox;

@ -0,0 +1,32 @@
import { ActionButton } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
const ToolBoxAction: FC<any> = ({
id,
icon,
color,
title,
action,
className,
tabId,
index,
...props
}) => (
<ActionButton
className={className}
primary={tabId === id}
onClick={action}
title={title}
data-toolbox={index}
key={id}
icon={icon}
position='relative'
ghost
tiny
overflow='visible'
color={!!color && color}
{...props}
/>
);
export default ToolBoxAction;

@ -0,0 +1,18 @@
import { css } from '@rocket.chat/css-in-js';
import { Box, Badge } from '@rocket.chat/fuselage';
import React, { ComponentProps, FC } from 'react';
const ToolBoxActionBadge: FC<ComponentProps<typeof Badge>> = (props) => (
<Box
position='absolute'
className={css`
top: 0;
right: 0;
transform: translate(30%, -30%);
`}
>
<Badge {...props} />
</Box>
);
export default ToolBoxActionBadge;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save