[IMPROVE] Rewrite Federation Dashboard (#17900)

pull/17924/head
Tasso Evangelista 5 years ago committed by GitHub
parent ed7eca54c8
commit ac987a1d91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 141
      app/federation/client/admin/dashboard.css
  2. 32
      app/federation/client/admin/dashboard.html
  3. 85
      app/federation/client/admin/dashboard.js
  4. 1
      app/federation/client/index.js
  5. 23
      client/admin/federationDashboard/FederationDashboardPage.js
  6. 10
      client/admin/federationDashboard/FederationDashboardPage.stories.js
  7. 17
      client/admin/federationDashboard/FederationDashboardRoute.tsx
  8. 40
      client/admin/federationDashboard/OverviewSection.js
  9. 10
      client/admin/federationDashboard/OverviewSection.stories.js
  10. 26
      client/admin/federationDashboard/ServersSection.js
  11. 10
      client/admin/federationDashboard/ServersSection.stories.js
  12. 5
      client/admin/routes.js
  13. 14
      client/admin/sidebarItems.js
  14. 6
      client/components/data/Counter.js
  15. 20
      client/components/data/Counter.stories.js
  16. 6
      client/components/data/CounterSet.js
  17. 4
      client/components/data/CounterSet.stories.js
  18. 8
      client/components/data/Growth.js
  19. 26
      client/components/data/Growth.stories.js
  20. 4
      client/components/data/NegativeGrowthSymbol.js
  21. 22
      client/components/data/NegativeGrowthSymbol.stories.js
  22. 4
      client/components/data/PositiveGrowthSymbol.js
  23. 24
      client/components/data/PositiveGrowthSymbol.stories.js
  24. 28
      client/contexts/ServerContext.js
  25. 111
      client/contexts/ServerContext.ts
  26. 1
      client/importPackages.js
  27. 7
      client/providers/ServerProvider.js
  28. 2
      ee/app/engagement-dashboard/client/components/ChannelsTab/TableSection.js
  29. 2
      ee/app/engagement-dashboard/client/components/MessagesTab/MessagesSentSection.js
  30. 2
      ee/app/engagement-dashboard/client/components/UsersTab/ActiveUsersSection.js
  31. 2
      ee/app/engagement-dashboard/client/components/UsersTab/NewUsersSection.js
  32. 16
      ee/app/engagement-dashboard/client/components/data/Counter.stories.js
  33. 23
      ee/app/engagement-dashboard/client/components/data/Growth.stories.js
  34. 16
      ee/app/engagement-dashboard/client/components/data/NegativeGrowthSymbol.stories.js
  35. 16
      ee/app/engagement-dashboard/client/components/data/PositiveGrowthSymbol.stories.js

@ -1,141 +0,0 @@
.status {
flex: 0 0 auto;
width: 6px;
height: 6px;
margin: 0 7px;
border-radius: 50%;
}
.status.stable {
background-color: #2de0a5;
}
.status.unstable {
background-color: #ffd21f;
}
.status.failing {
background-color: #f5455c;
}
.frame {
display: flex;
flex-direction: row;
}
.group {
display: flex;
flex-direction: row;
flex: 100%;
max-width: 100%;
margin: 10px;
border-width: 1px;
align-items: center;
justify-content: center;
}
.group.left {
justify-content: flex-start;
}
.group.wrap {
flex-wrap: wrap;
}
.overview-column {
flex: 100%;
min-height: 20px;
margin: 15px 0;
}
.overview-column.small {
max-width: 20%;
}
.group .overview-column:not(:last-child) {
border-right: 1px solid #e9e9e9;
}
.group .overview-column:nth-child(5n) {
border-right: 0;
}
.overview-pill {
display: flex;
width: 100%;
padding: 0 10px;
user-select: text;
text-align: center;
align-items: center;
}
.overview-item {
width: 100%;
user-select: text;
text-align: center;
}
.overview-item > .title {
display: inline-block;
margin-top: 8px;
text-transform: uppercase;
color: #9ea2a8;
font-size: 0.875rem;
font-weight: 300;
}
.overview-item > .value {
display: inline-block;
width: 100%;
text-transform: capitalize;
color: #383838;
font-size: 1.75rem;
font-weight: 400;
line-height: 1;
}
@media screen and (max-width: 925px) {
.overview-item > .title {
font-size: 0.5rem;
}
.overview-item > .value {
font-size: 1rem;
}
}
@media screen and (max-width: 800px) {
.overview-item > .title {
font-size: 0.875rem;
}
.overview-item > .value {
font-size: 1.75rem;
}
}
@media screen and (max-width: 600px) {
.overview-item > .title {
font-size: 0.5rem;
}
.overview-item > .value {
font-size: 1rem;
}
}

@ -1,32 +0,0 @@
<template name="dashboard">
<div class="main-content-flex">
<section class="page-container page-list flex-tab-main-content">
{{> header sectionName="Federation_Dashboard"}}
<div class="content">
<div class="section">
<div class="section-content">
<div class="group border-component-color">
{{#each federationOverviewData}}
<div class="overview-column">
<div class="overview-item">
<span class="value">{{value}}</span>
<span class="title">{{_ title}}</span>
</div>
</div>
{{/each}}
</div>
<div class="group left wrap border-component-color">
{{#each federationPeers}}
<div class="overview-column small">
<div class="overview-pill">
<span class="title">{{domain}}</span>
</div>
</div>
{{/each}}
</div>
</div>
</div>
</div>
</section>
</div>
</template>

@ -1,85 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
import { hasRole } from '../../../authorization';
import { registerAdminRoute, registerAdminSidebarItem } from '../../../../client/admin';
import './dashboard.html';
import './dashboard.css';
// Template controller
let templateInstance; // current template instance/context
// Methods
const updateOverviewData = () => {
Meteor.call('federation:getOverviewData', (error, result) => {
if (error) {
return;
}
const { data } = result;
templateInstance.federationOverviewData.set(data);
});
};
const updateServers = () => {
Meteor.call('federation:getServers', (error, result) => {
if (error) {
return;
}
const { data } = result;
templateInstance.federationPeers.set(data);
});
};
const updateData = () => {
updateOverviewData();
updateServers();
};
Template.dashboard.helpers({
federationOverviewData() {
return templateInstance.federationOverviewData.get();
},
federationPeers() {
return templateInstance.federationPeers.get();
},
});
// Events
Template.dashboard.onCreated(function() {
templateInstance = Template.instance();
this.federationOverviewData = new ReactiveVar();
this.federationPeers = new ReactiveVar();
});
Template.dashboard.onRendered(() => {
Tracker.autorun(updateData);
setInterval(updateData, 10000);
});
// Route setup
registerAdminRoute('/federation-dashboard', {
name: 'federation-dashboard',
action() {
BlazeLayout.render('main', { center: 'dashboard', old: true });
},
});
registerAdminSidebarItem({
icon: 'discover',
href: 'federation-dashboard',
i18nLabel: 'Federation Dashboard',
permissionGranted() {
return hasRole(Meteor.userId(), 'admin');
},
});

@ -1 +0,0 @@
import './admin/dashboard';

@ -0,0 +1,23 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import Page from '../../components/basic/Page';
import { useTranslation } from '../../contexts/TranslationContext';
import OverviewSection from './OverviewSection';
import ServersSection from './ServersSection';
function FederationDashboardPage() {
const t = useTranslation();
return <Page>
<Page.Header title={t('Federation_Dashboard')} />
<Page.ScrollableContentWithShadow>
<Box margin='x24'>
<OverviewSection />
<ServersSection />
</Box>
</Page.ScrollableContentWithShadow>
</Page>;
}
export default FederationDashboardPage;

@ -0,0 +1,10 @@
import React from 'react';
import FederationDashboardPage from './FederationDashboardPage';
export default {
title: 'admin/federationDashboard/FederationDashboardPage',
component: FederationDashboardPage,
};
export const Default = () => <FederationDashboardPage />;

@ -0,0 +1,17 @@
import React, { FC } from 'react';
import { useRole } from '../../contexts/AuthorizationContext';
import NotAuthorizedPage from '../NotAuthorizedPage';
import FederationDashboardPage from './FederationDashboardPage';
const FederationDashboardRoute: FC<{}> = () => {
const authorized = useRole('admin');
if (!authorized) {
return <NotAuthorizedPage />;
}
return <FederationDashboardPage />;
};
export default FederationDashboardRoute;

@ -0,0 +1,40 @@
import { Box, Skeleton } from '@rocket.chat/fuselage';
import React from 'react';
import { useTranslation } from '../../contexts/TranslationContext';
import CounterSet from '../../components/data/CounterSet';
import { usePolledMethodData, AsyncState } from '../../contexts/ServerContext';
function OverviewSection() {
const t = useTranslation();
const [overviewData, overviewStatus] = usePolledMethodData('federation:getOverviewData', [], 10000);
const eventCount = (overviewStatus === AsyncState.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncState.ERROR && <Box color='danger'>Error</Box>)
|| overviewData?.data[0]?.value;
const userCount = (overviewStatus === AsyncState.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncState.ERROR && <Box color='danger'>Error</Box>)
|| overviewData?.data[1]?.value;
const serverCount = (overviewStatus === AsyncState.LOADING && <Skeleton variant='text' />)
|| (overviewStatus === AsyncState.ERROR && <Box color='danger'>Error</Box>)
|| overviewData?.data[2]?.value;
return <CounterSet
counters={[
{
count: eventCount,
description: t('Number_of_events'),
},
{
count: userCount,
description: t('Number_of_federated_users'),
},
{
count: serverCount,
description: t('Number_of_federated_servers'),
},
]}
/>;
}
export default OverviewSection;

@ -0,0 +1,10 @@
import React from 'react';
import OverviewSection from './OverviewSection';
export default {
title: 'admin/federationDashboard/OverviewSection',
component: OverviewSection,
};
export const Default = () => <OverviewSection />;

@ -0,0 +1,26 @@
import { Box, Throbber } from '@rocket.chat/fuselage';
import React from 'react';
import { usePolledMethodData, AsyncState } from '../../contexts/ServerContext';
function ServersSection() {
const [serversData, serversStatus] = usePolledMethodData('federation:getServers', [], 10000);
if (serversStatus === AsyncState.LOADING) {
return <Throbber align='center' />;
}
if (serversData?.data?.length === 0) {
return null;
}
return <Box withRichContent>
<ul>
{serversData?.data?.map(({ domain }) => (
<li key={domain}>{domain}</li>
))}
</ul>
</Box>;
}
export default ServersSection;

@ -0,0 +1,10 @@
import React from 'react';
import ServersSection from './ServersSection';
export default {
title: 'admin/federationDashboard/ServersSection',
component: ServersSection,
};
export const Default = () => <ServersSection />;

@ -117,6 +117,11 @@ registerAdminRoute('/view-logs', {
lazyRouteComponent: () => import('./viewLogs/ViewLogsRoute'),
});
registerAdminRoute('/federation-dashboard', {
name: 'federation-dashboard',
lazyRouteComponent: () => import('./federationDashboard/FederationDashboardRoute'),
});
Meteor.startup(() => {
registerAdminRoute('/:group+', {
name: 'admin',

@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { hasPermission } from '../../app/authorization/client';
import { hasPermission, hasRole } from '../../app/authorization/client';
import { createTemplateForComponent } from '../reactAdapters';
export const sidebarItems = new ReactiveVar([]);
@ -63,7 +64,12 @@ registerAdminSidebarItem({
href: 'custom-sounds',
i18nLabel: 'Custom_Sounds',
icon: 'volume',
permissionGranted() {
return hasPermission(['manage-sounds']);
},
permissionGranted: () => hasPermission(['manage-sounds']),
});
registerAdminSidebarItem({
icon: 'discover',
href: 'federation-dashboard',
i18nLabel: 'Federation Dashboard',
permissionGranted: () => hasRole(Meteor.userId(), 'admin'),
});

@ -1,9 +1,9 @@
import { Box, Flex, Margins } from '@rocket.chat/fuselage';
import React from 'react';
import { Growth } from './Growth';
import Growth from './Growth';
export function Counter({ count, variation = 0, description }) {
function Counter({ count, variation = 0, description }) {
return <>
<Flex.Container alignItems='end'>
<Box>
@ -22,3 +22,5 @@ export function Counter({ count, variation = 0, description }) {
</Margins>
</>;
}
export default Counter;

@ -0,0 +1,20 @@
import React from 'react';
import Counter from './Counter';
export default {
title: 'components/data/Counter',
component: Counter,
};
export const Default = () =>
<Counter count={123} />;
export const WithPositiveVariation = () =>
<Counter count={123} variation={4} />;
export const WithNegativeVariation = () =>
<Counter count={123} variation={-4} />;
export const WithDescription = () =>
<Counter count={123} description='Description' />;

@ -1,9 +1,9 @@
import { Grid } from '@rocket.chat/fuselage';
import React from 'react';
import { Counter } from './Counter';
import Counter from './Counter';
export function CounterSet({ counters = [] }) {
function CounterSet({ counters = [] }) {
return <Grid>
{counters.map(({ count, variation, description }, i) => <Grid.Item key={i}>
<Counter
@ -14,3 +14,5 @@ export function CounterSet({ counters = [] }) {
</Grid.Item>)}
</Grid>;
}
export default CounterSet;

@ -1,9 +1,9 @@
import React from 'react';
import { CounterSet } from './CounterSet';
import CounterSet from './CounterSet';
export default {
title: 'admin/enterprise/engagement/data/CounterSet',
title: 'components/data/CounterSet',
component: CounterSet,
};

@ -1,10 +1,10 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import { NegativeGrowthSymbol } from './NegativeGrowthSymbol';
import { PositiveGrowthSymbol } from './PositiveGrowthSymbol';
import NegativeGrowthSymbol from './NegativeGrowthSymbol';
import PositiveGrowthSymbol from './PositiveGrowthSymbol';
export function Growth({ children, ...props }) {
function Growth({ children, ...props }) {
if (children === 0) {
return null;
}
@ -14,3 +14,5 @@ export function Growth({ children, ...props }) {
{String(Math.abs(children))}
</Box>;
}
export default Growth;

@ -0,0 +1,26 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import Growth from './Growth';
export default {
title: 'components/data/Growth',
component: Growth,
decorators: [(fn) => <Box children={fn()} margin='x16' />],
};
export const Positive = () =>
<Growth>{3}</Growth>;
export const Zero = () =>
<Growth>{0}</Growth>;
export const Negative = () =>
<Growth>{-3}</Growth>;
export const WithTextStyle = () =>
['h1', 's1', 'c1', 'micro']
.map((fontScale) => <Box key={fontScale}>
<Growth fontScale={fontScale}>{3}</Growth>
<Growth fontScale={fontScale}>{-3}</Growth>
</Box>);

@ -2,7 +2,7 @@ import React from 'react';
const style = { width: '1.5em', height: '1.5em', verticalAlign: '-0.5em' };
export const NegativeGrowthSymbol = (props) =>
const NegativeGrowthSymbol = (props) =>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 25 24'
@ -24,3 +24,5 @@ export const NegativeGrowthSymbol = (props) =>
7.4768 4.3166 8.10996 4.70712 8.50049Z`}
/>
</svg>;
export default NegativeGrowthSymbol;

@ -0,0 +1,22 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import NegativeGrowthSymbol from './NegativeGrowthSymbol';
export default {
title: 'components/data/NegativeGrowthSymbol',
component: NegativeGrowthSymbol,
decorators: [
(fn) => <Box children={fn()} margin='x16' />,
],
};
export const Default = () => <NegativeGrowthSymbol />;
export const WithColor = () => <NegativeGrowthSymbol />;
WithColor.story = {
decorators: [
(storyFn) => <Box color='danger'>{storyFn()}</Box>,
],
};

@ -2,7 +2,7 @@ import React from 'react';
const style = { width: '1.5em', height: '1.5em', verticalAlign: '-0.5em' };
export const PositiveGrowthSymbol = (props) =>
const PositiveGrowthSymbol = (props) =>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 24 24'
@ -24,3 +24,5 @@ export const PositiveGrowthSymbol = (props) =>
16.3502 4.3166 15.7171 4.70712 15.3265Z`}
/>
</svg>;
export default PositiveGrowthSymbol;

@ -0,0 +1,24 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import PositiveGrowthSymbol from './PositiveGrowthSymbol';
export default {
title: 'components/data/PositiveGrowthSymbol',
component: PositiveGrowthSymbol,
decorators: [
(fn) => <Box children={fn()} margin='x16' />,
],
};
export const Default = () =>
<PositiveGrowthSymbol />;
export const WithColor = () =>
<PositiveGrowthSymbol />;
WithColor.story = {
decorators: [
(storyFn) => <Box color='success'>{storyFn()}</Box>,
],
};

@ -1,28 +0,0 @@
import { createContext, useCallback, useContext } from 'react';
export const ServerContext = createContext({
info: {},
absoluteUrl: (path) => path,
callMethod: async () => {},
callEndpoint: async () => {},
upload: async () => {},
});
export const useServerInformation = () => useContext(ServerContext).info;
export const useAbsoluteUrl = () => useContext(ServerContext).absoluteUrl;
export const useMethod = (methodName) => {
const { callMethod } = useContext(ServerContext);
return useCallback((...args) => callMethod(methodName, ...args), [callMethod, methodName]);
};
export const useEndpoint = (httpMethod, endpoint) => {
const { callEndpoint } = useContext(ServerContext);
return useCallback((...args) => callEndpoint(httpMethod, endpoint, ...args), [callEndpoint, httpMethod, endpoint]);
};
export const useUpload = (endpoint) => {
const { upload } = useContext(ServerContext);
return useCallback((...args) => upload(endpoint, ...args), [upload]);
};

@ -0,0 +1,111 @@
import { createContext, useCallback, useContext, useMemo, useState, useEffect, useRef } from 'react';
interface IServerStream {
on(eventName: string, callback: (data: any) => void): void;
off(eventName: string, callback: (data: any) => void): void;
}
type ServerContextValue = {
info: object;
absoluteUrl: (path: string) => string;
callMethod: (methodName: string, ...args: any[]) => Promise<any>;
callEndpoint: (httpMethod: 'GET' | 'POST' | 'DELETE', endpoint: string, ...args: any[]) => Promise<any>;
uploadToEndpoint: (endpoint: string) => Promise<void>;
getStream: (streamName: string, options?: object) => IServerStream;
};
export const ServerContext = createContext<ServerContextValue>({
info: {},
absoluteUrl: (path) => path,
callMethod: async () => undefined,
callEndpoint: async () => undefined,
uploadToEndpoint: async () => undefined,
getStream: () => ({
on: (): void => undefined,
off: (): void => undefined,
}),
});
export const useServerInformation = (): object => useContext(ServerContext).info;
export const useAbsoluteUrl = (): ((path: string) => string) => useContext(ServerContext).absoluteUrl;
export const useMethod = (methodName: string): (...args: any[]) => Promise<any> => {
const { callMethod } = useContext(ServerContext);
return useCallback((...args) => callMethod(methodName, ...args), [callMethod, methodName]);
};
export const useEndpoint = (httpMethod: 'GET' | 'POST' | 'DELETE', endpoint: string): (...args: any[]) => Promise<any> => {
const { callEndpoint } = useContext(ServerContext);
return useCallback((...args) => callEndpoint(httpMethod, endpoint, ...args), [callEndpoint, httpMethod, endpoint]);
};
export const useUpload = (endpoint: string): () => Promise<void> => {
const { uploadToEndpoint } = useContext(ServerContext);
return useCallback((...args) => uploadToEndpoint(endpoint, ...args), [endpoint, uploadToEndpoint]);
};
export const useStream = (streamName: string, options?: object): IServerStream => {
const { getStream } = useContext(ServerContext);
return useMemo(() => getStream(streamName, options), [streamName, options]);
};
export enum AsyncState {
LOADING = 'loading',
DONE = 'done',
ERROR = 'error',
}
export const useMethodData = <T>(methodName: string, args: any[] = []): [T | null, AsyncState, () => void] => {
const getData = useMethod(methodName);
const [[data, state], updateState] = useState<[T | null, AsyncState]>([null, AsyncState.LOADING]);
const isMountedRef = useRef(true);
useEffect(() => (): void => {
isMountedRef.current = false;
}, []);
const fetchData = useCallback(() => {
updateState(([data]) => [data, AsyncState.LOADING]);
getData(...args)
.then((data) => {
if (!isMountedRef.current) {
return;
}
updateState([data, AsyncState.DONE]);
})
.catch((error) => {
if (!isMountedRef.current) {
return;
}
updateState(([data]) => [data, AsyncState.ERROR]);
console.error(error);
});
}, [getData, ...args]);
useEffect(() => {
fetchData();
}, [fetchData]);
return [data, state, fetchData];
};
export const usePolledMethodData = <T>(methodName: string, args: any[] = [], intervalMs: number): [T | null, AsyncState, () => void] => {
const [data, state, fetchData] = useMethodData<T>(methodName, args);
useEffect(() => {
const timer = setInterval(() => {
fetchData();
}, intervalMs);
return (): void => {
clearInterval(timer);
};
}, []);
return [data, state, fetchData];
};

@ -17,7 +17,6 @@ import '../app/emoji';
import '../app/emoji-custom/client';
import '../app/emoji-emojione/client';
import '../app/favico';
import '../app/federation/client';
import '../app/file-upload';
import '../app/github-enterprise/client';
import '../app/gitlab/client';

@ -32,14 +32,17 @@ const callEndpoint = (httpMethod, endpoint, ...args) => {
return APIClient.v1[httpMethod.toLowerCase()](endpoint, ...args);
};
const upload = (endpoint, params, formData) => APIClient.v1.upload(endpoint, params, formData);
const uploadToEndpoint = (endpoint, params, formData) => APIClient.v1.upload(endpoint, params, formData);
const getStream = (streamName, options = {}) => new Meteor.Streamer(streamName, options);
const contextValue = {
info,
absoluteUrl,
callMethod,
callEndpoint,
upload,
uploadToEndpoint,
getStream,
};
export function ServerProvider({ children }) {

@ -4,7 +4,7 @@ import React, { useMemo, useState } from 'react';
import { useTranslation } from '../../../../../../client/contexts/TranslationContext';
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { Growth } from '../data/Growth';
import Growth from '../../../../../../client/components/data/Growth';
import { Section } from '../Section';
export function TableSection() {

@ -5,7 +5,7 @@ import React, { useMemo, useState } from 'react';
import { useTranslation } from '../../../../../../client/contexts/TranslationContext';
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { CounterSet } from '../data/CounterSet';
import CounterSet from '../../../../../../client/components/data/CounterSet';
import { Section } from '../Section';
export function MessagesSentSection() {

@ -5,7 +5,7 @@ import React, { useMemo } from 'react';
import { useTranslation } from '../../../../../../client/contexts/TranslationContext';
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { CounterSet } from '../data/CounterSet';
import CounterSet from '../../../../../../client/components/data/CounterSet';
import { LegendSymbol } from '../data/LegendSymbol';
import { Section } from '../Section';

@ -5,7 +5,7 @@ import React, { useMemo, useState } from 'react';
import { useTranslation } from '../../../../../../client/contexts/TranslationContext';
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { CounterSet } from '../data/CounterSet';
import CounterSet from '../../../../../../client/components/data/CounterSet';
import { Section } from '../Section';
export function NewUsersSection() {

@ -1,16 +0,0 @@
import React from 'react';
import { Counter } from './Counter';
export default {
title: 'admin/enterprise/engagement/data/Counter',
component: Counter,
};
export const _default = () => <Counter count={123} />;
export const withPositiveVariation = () => <Counter count={123} variation={4} />;
export const withNegativeVariation = () => <Counter count={123} variation={-4} />;
export const withDescription = () => <Counter count={123} description='Description' />;

@ -1,23 +0,0 @@
import { Box, Margins } from '@rocket.chat/fuselage';
import React from 'react';
import { Growth } from './Growth';
export default {
title: 'admin/enterprise/engagement/data/Growth',
component: Growth,
decorators: [(fn) => <Margins children={fn()} all='x16' />],
};
export const positive = () => <Growth>{3}</Growth>;
export const zero = () => <Growth>{0}</Growth>;
export const negative = () => <Growth>{-3}</Growth>;
export const withTextStyle = () =>
['h1', 's1', 'c1', 'micro']
.map((fontScale) => <Box key={fontScale}>
<Growth fontScale={fontScale}>{3}</Growth>
<Growth fontScale={fontScale}>{-3}</Growth>
</Box>);

@ -1,16 +0,0 @@
import { Box, Margins } from '@rocket.chat/fuselage';
import React from 'react';
import { NegativeGrowthSymbol } from './NegativeGrowthSymbol';
export default {
title: 'admin/enterprise/engagement/data/NegativeGrowthSymbol',
component: NegativeGrowthSymbol,
decorators: [(fn) => <Margins children={fn()} all='x16' />],
};
export const _default = () => <NegativeGrowthSymbol />;
export const withColor = () => <Box color='danger'>
<NegativeGrowthSymbol />
</Box>;

@ -1,16 +0,0 @@
import { Box, Margins } from '@rocket.chat/fuselage';
import React from 'react';
import { PositiveGrowthSymbol } from './PositiveGrowthSymbol';
export default {
title: 'admin/enterprise/engagement/data/PositiveGrowthSymbol',
component: PositiveGrowthSymbol,
decorators: [(fn) => <Margins children={fn()} all='x16' />],
};
export const _default = () => <PositiveGrowthSymbol />;
export const withColor = () => <Box color='success'>
<PositiveGrowthSymbol />
</Box>;
Loading…
Cancel
Save