From bcaf20caca2b58cb2bd7ebf2ee3089de6dbe435b Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Tue, 16 Nov 2021 16:51:49 +0200 Subject: [PATCH] refactor(app/widgets): create widgets react components [EE-1813] (#6097) --- app/assets/css/app.css | 1 - app/portainer/components/index.js | 3 +- app/portainer/components/loading.js | 7 -- app/portainer/components/widget-body.js | 13 --- .../components/widget-custom-header.js | 14 --- app/portainer/components/widget-footer.js | 9 -- app/portainer/components/widget-header.js | 26 ----- app/portainer/components/widget-taskbar.js | 12 --- app/portainer/components/widget.js | 11 --- app/portainer/components/widget/Loading.tsx | 12 +++ .../components/widget/Widget.stories.tsx | 95 +++++++++++++++++++ app/portainer/components/widget/Widget.tsx | 24 +++++ .../components/widget/WidgetBody.tsx | 39 ++++++++ .../components/widget/WidgetCustomHeader.tsx | 22 +++++ .../components/widget/WidgetFooter.tsx | 17 ++++ .../components/widget/WidgetTaskbar.tsx | 37 ++++++++ .../components/widget/WidgetTitle.tsx | 54 +++++++++++ app/portainer/components/widget/index.ts | 21 ++++ 18 files changed, 323 insertions(+), 94 deletions(-) delete mode 100644 app/portainer/components/loading.js delete mode 100644 app/portainer/components/widget-body.js delete mode 100644 app/portainer/components/widget-custom-header.js delete mode 100644 app/portainer/components/widget-footer.js delete mode 100644 app/portainer/components/widget-header.js delete mode 100644 app/portainer/components/widget-taskbar.js delete mode 100644 app/portainer/components/widget.js create mode 100644 app/portainer/components/widget/Loading.tsx create mode 100644 app/portainer/components/widget/Widget.stories.tsx create mode 100644 app/portainer/components/widget/Widget.tsx create mode 100644 app/portainer/components/widget/WidgetBody.tsx create mode 100644 app/portainer/components/widget/WidgetCustomHeader.tsx create mode 100644 app/portainer/components/widget/WidgetFooter.tsx create mode 100644 app/portainer/components/widget/WidgetTaskbar.tsx create mode 100644 app/portainer/components/widget/WidgetTitle.tsx create mode 100644 app/portainer/components/widget/index.ts diff --git a/app/assets/css/app.css b/app/assets/css/app.css index 95cdaeb73..e84e4cd53 100644 --- a/app/assets/css/app.css +++ b/app/assets/css/app.css @@ -307,7 +307,6 @@ a[ng-click] { .custom-header-ico { max-width: 32px; max-height: 32px; - margin-right: 2px; } .btn-responsive { diff --git a/app/portainer/components/index.js b/app/portainer/components/index.js index a6da97272..a437ec0e2 100644 --- a/app/portainer/components/index.js +++ b/app/portainer/components/index.js @@ -4,11 +4,12 @@ import sidebarModule from './sidebar'; import gitFormModule from './forms/git-form'; import porAccessManagementModule from './accessManagement'; import formComponentsModule from './form-components'; +import widgetModule from './widget'; import { ReactExampleAngular } from './ReactExample'; import { TooltipAngular } from './Tooltip'; export default angular - .module('portainer.app.components', [sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule]) + .module('portainer.app.components', [widgetModule, sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule]) .component('portainerTooltip', TooltipAngular) .component('reactExample', ReactExampleAngular).name; diff --git a/app/portainer/components/loading.js b/app/portainer/components/loading.js deleted file mode 100644 index b112eaa06..000000000 --- a/app/portainer/components/loading.js +++ /dev/null @@ -1,7 +0,0 @@ -angular.module('portainer.app').directive('rdLoading', function rdLoading() { - var directive = { - restrict: 'AE', - template: '
', - }; - return directive; -}); diff --git a/app/portainer/components/widget-body.js b/app/portainer/components/widget-body.js deleted file mode 100644 index d4ba7be93..000000000 --- a/app/portainer/components/widget-body.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.app').directive('rdWidgetBody', function rdWidgetBody() { - var directive = { - requires: '^rdWidget', - scope: { - loading: '@?', - classes: '@?', - }, - transclude: true, - template: '
', - restrict: 'E', - }; - return directive; -}); diff --git a/app/portainer/components/widget-custom-header.js b/app/portainer/components/widget-custom-header.js deleted file mode 100644 index d6c32e5a1..000000000 --- a/app/portainer/components/widget-custom-header.js +++ /dev/null @@ -1,14 +0,0 @@ -angular.module('portainer.app').directive('rdWidgetCustomHeader', function rdWidgetCustomHeader() { - var directive = { - requires: '^rdWidget', - scope: { - titleText: '=', - icon: '=', - }, - transclude: true, - template: - '
{{titleText}}
', - restrict: 'E', - }; - return directive; -}); diff --git a/app/portainer/components/widget-footer.js b/app/portainer/components/widget-footer.js deleted file mode 100644 index cba9ff444..000000000 --- a/app/portainer/components/widget-footer.js +++ /dev/null @@ -1,9 +0,0 @@ -angular.module('portainer.app').directive('rdWidgetFooter', function rdWidgetFooter() { - var directive = { - requires: '^rdWidget', - transclude: true, - template: '', - restrict: 'E', - }; - return directive; -}); diff --git a/app/portainer/components/widget-header.js b/app/portainer/components/widget-header.js deleted file mode 100644 index 9eb50e500..000000000 --- a/app/portainer/components/widget-header.js +++ /dev/null @@ -1,26 +0,0 @@ -angular.module('portainer.app').directive('rdWidgetHeader', function rdWidgetTitle() { - var directive = { - requires: '^rdWidget', - scope: { - titleText: '@', - icon: '@', - classes: '@?', - }, - transclude: { - title: '?headerTitle', - }, - template: ` -
-
- - - {{ titleText }} - - -
-
-`, - restrict: 'E', - }; - return directive; -}); diff --git a/app/portainer/components/widget-taskbar.js b/app/portainer/components/widget-taskbar.js deleted file mode 100644 index 3f34d30c0..000000000 --- a/app/portainer/components/widget-taskbar.js +++ /dev/null @@ -1,12 +0,0 @@ -angular.module('portainer.app').directive('rdWidgetTaskbar', function rdWidgetTaskbar() { - var directive = { - requires: '^rdWidget', - scope: { - classes: '@?', - }, - transclude: true, - template: '
', - restrict: 'E', - }; - return directive; -}); diff --git a/app/portainer/components/widget.js b/app/portainer/components/widget.js deleted file mode 100644 index e81f53153..000000000 --- a/app/portainer/components/widget.js +++ /dev/null @@ -1,11 +0,0 @@ -angular.module('portainer.app').directive('rdWidget', function rdWidget() { - var directive = { - scope: { - ngModel: '=', - }, - transclude: true, - template: '
', - restrict: 'EA', - }; - return directive; -}); diff --git a/app/portainer/components/widget/Loading.tsx b/app/portainer/components/widget/Loading.tsx new file mode 100644 index 000000000..d7f04d072 --- /dev/null +++ b/app/portainer/components/widget/Loading.tsx @@ -0,0 +1,12 @@ +import { react2angular } from '@/react-tools/react2angular'; + +export function Loading() { + return ( +
+
+
+
+ ); +} + +export const LoadingAngular = react2angular(Loading, []); diff --git a/app/portainer/components/widget/Widget.stories.tsx b/app/portainer/components/widget/Widget.stories.tsx new file mode 100644 index 000000000..534c4a105 --- /dev/null +++ b/app/portainer/components/widget/Widget.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta } from '@storybook/react'; + +import { Widget } from './Widget'; +import { WidgetBody } from './WidgetBody'; +import { WidgetTitle } from './WidgetTitle'; +import { WidgetFooter } from './WidgetFooter'; +import { WidgetTaskbar } from './WidgetTaskbar'; + +interface WidgetProps { + loading: boolean; + title: string; + icon: string; + bodyText: string; + footerText: string; +} + +const meta: Meta = { + title: 'Widget', + component: Widget, + args: { + loading: false, + title: 'Title', + icon: 'fa-rocket', + bodyText: 'Body', + footerText: 'Footer', + }, +}; + +export default meta; + +export function Default({ + loading, + bodyText, + footerText, + icon, + title, +}: WidgetProps) { + return ( + + + {bodyText} + {footerText} + + ); +} + +export function WidgetWithCustomImage({ + loading, + bodyText, + footerText, + icon, + title, +}: WidgetProps) { + return ( + + + } + /> + {bodyText} + {footerText} + + ); +} + +WidgetWithCustomImage.args = { + icon: 'https://via.placeholder.com/150', +}; + +export function WidgetWithTaskBar({ + loading, + bodyText, + footerText, + icon, + title, +}: WidgetProps) { + return ( + + + + + + {bodyText} + {footerText} + + ); +} diff --git a/app/portainer/components/widget/Widget.tsx b/app/portainer/components/widget/Widget.tsx new file mode 100644 index 000000000..ee069851f --- /dev/null +++ b/app/portainer/components/widget/Widget.tsx @@ -0,0 +1,24 @@ +import { createContext, PropsWithChildren, useContext } from 'react'; + +const Context = createContext(null); + +export function useWidgetContext() { + const context = useContext(Context); + + if (context == null) { + throw new Error('Should be inside a Widget component'); + } +} + +export const rdWidget = { + transclude: true, + template: `
`, +}; + +export function Widget({ children }: PropsWithChildren) { + return ( + +
{children}
+
+ ); +} diff --git a/app/portainer/components/widget/WidgetBody.tsx b/app/portainer/components/widget/WidgetBody.tsx new file mode 100644 index 000000000..4926ddf48 --- /dev/null +++ b/app/portainer/components/widget/WidgetBody.tsx @@ -0,0 +1,39 @@ +import clsx from 'clsx'; +import { PropsWithChildren } from 'react'; + +import { useWidgetContext } from './Widget'; +import { Loading } from './Loading'; + +export const rdWidgetBody = { + requires: '^rdWidget', + bindings: { + loading: '@?', + classes: '@?', + }, + transclude: true, + template: ` +
+ +
+
+ `, +}; + +interface Props { + loading?: boolean; + className?: string; +} + +export function WidgetBody({ + loading, + className, + children, +}: PropsWithChildren) { + useWidgetContext(); + + return ( +
+ {loading ? :
{children}
} +
+ ); +} diff --git a/app/portainer/components/widget/WidgetCustomHeader.tsx b/app/portainer/components/widget/WidgetCustomHeader.tsx new file mode 100644 index 000000000..f124fc2bb --- /dev/null +++ b/app/portainer/components/widget/WidgetCustomHeader.tsx @@ -0,0 +1,22 @@ +export const rdWidgetCustomHeader = { + requires: '^rdWidget', + bindings: { + titleText: '=', + icon: '=', + }, + transclude: true, + template: ` +
+
+ + header-icon +