[NEW] New set of rules for client code (#21318)
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>pull/21493/head
parent
45b05602c4
commit
d8a24b044e
@ -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,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,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,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(); |
||||
}, |
||||
}); |
||||
@ -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 |
||||
} |
||||
@ -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; |
||||
|
||||
@ -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,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; |
||||
|
||||
@ -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; |
||||
|
||||
@ -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; |
||||
@ -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…
Reference in new issue