From a57eb7f373032e545e8cf25ad4987d0441c6e374 Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 13:51:05 +0800 Subject: [PATCH 1/7] fix: dashboard content menu and dialogs --- index.html | 1 + package.json | 2 +- public/assets/icons/IcCross.svg | 1 + public/assets/icons/IcMenuDots.svg | 1 + public/assets/icons/IcUserGuide.svg | 1 + src/app/app.scss | 23 +- src/components/shared_ui/button/button.scss | 289 ++++++++++++++++ src/components/shared_ui/button/button.tsx | 144 ++++++++ .../shared_ui/button/button_loading.tsx | 10 + src/components/shared_ui/button/index.ts | 4 + src/components/shared_ui/dialog/dialog.scss | 137 ++++++++ src/components/shared_ui/dialog/dialog.tsx | 197 +++++++++++ src/components/shared_ui/dialog/index.ts | 5 + src/components/shared_ui/input/input.scss | 46 ++- src/components/shared_ui/loading/index.ts | 4 + src/components/shared_ui/loading/loading.scss | 60 ++++ src/components/shared_ui/loading/loading.tsx | 45 +++ .../shared_ui/modal/__tests__/modal.spec.tsx | 40 +++ src/components/shared_ui/modal/index.ts | 4 + src/components/shared_ui/modal/modal-body.tsx | 12 + .../shared_ui/modal/modal-footer.tsx | 28 ++ src/components/shared_ui/modal/modal.scss | 276 ++++++++++++++++ src/components/shared_ui/modal/modal.tsx | 311 ++++++++++++++++++ src/pages/dashboard/cards.tsx | 5 +- src/pages/dashboard/dashboard.scss | 1 + .../load-bot-preview/delete-dialog.tsx | 25 +- .../load-bot-preview/google-drive.tsx | 3 +- .../dashboard/load-bot-preview/index.scss | 3 + .../dashboard/load-bot-preview/save-modal.tsx | 5 +- src/pages/dashboard/user-guide.tsx | 2 +- src/stores/save-modal-store.ts | 7 +- 31 files changed, 1667 insertions(+), 25 deletions(-) create mode 100644 public/assets/icons/IcCross.svg create mode 100644 public/assets/icons/IcMenuDots.svg create mode 100644 public/assets/icons/IcUserGuide.svg create mode 100644 src/components/shared_ui/button/button.scss create mode 100644 src/components/shared_ui/button/button.tsx create mode 100644 src/components/shared_ui/button/button_loading.tsx create mode 100644 src/components/shared_ui/button/index.ts create mode 100644 src/components/shared_ui/dialog/dialog.scss create mode 100644 src/components/shared_ui/dialog/dialog.tsx create mode 100644 src/components/shared_ui/dialog/index.ts create mode 100644 src/components/shared_ui/loading/index.ts create mode 100644 src/components/shared_ui/loading/loading.scss create mode 100644 src/components/shared_ui/loading/loading.tsx create mode 100644 src/components/shared_ui/modal/__tests__/modal.spec.tsx create mode 100644 src/components/shared_ui/modal/index.ts create mode 100644 src/components/shared_ui/modal/modal-body.tsx create mode 100644 src/components/shared_ui/modal/modal-footer.tsx create mode 100644 src/components/shared_ui/modal/modal.scss create mode 100644 src/components/shared_ui/modal/modal.tsx diff --git a/index.html b/index.html index 130dd626..e8189465 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ Deriv Bot +
diff --git a/package.json b/package.json index 3a12b541..a20e9c51 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "start": "vite", - "start:dev": "vite-live-preview --reload --port=8443", + "start:dev": "vite-live-preview --reload --port=8444", "build": "vite build", "preview": "vite preview", "test:lint": "prettier --log-level silent --write . && eslint \"./src/**/*.?(js|jsx|ts|tsx)\"", diff --git a/public/assets/icons/IcCross.svg b/public/assets/icons/IcCross.svg new file mode 100644 index 00000000..56e7b5f4 --- /dev/null +++ b/public/assets/icons/IcCross.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/IcMenuDots.svg b/public/assets/icons/IcMenuDots.svg new file mode 100644 index 00000000..dc5dc1fa --- /dev/null +++ b/public/assets/icons/IcMenuDots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/icons/IcUserGuide.svg b/public/assets/icons/IcUserGuide.svg new file mode 100644 index 00000000..11716aa9 --- /dev/null +++ b/public/assets/icons/IcUserGuide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/app.scss b/src/app/app.scss index 468ca76a..6743152c 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -1,6 +1,6 @@ .bot { --bot-content-height: calc(100vh - 140px); - --bot-content-width: calc(100vw - 366px); //run-panel is open by default + --bot-content-width: calc(100vw - 366px); --drawer-content-height: calc(100vh - 394px); --drawer-content-height-no-stat: calc(100vh - 233px); --drawer-scroll-height: calc(100vh - 365px); @@ -15,3 +15,24 @@ background: var(--general-section-1); } } + +.modal-root { + position: absolute; + top: 0; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, 0.72); + right: 0; + opacity: 0; + z-index: -1; +} +.modal-root:not(:empty) { + display: flex; + opacity: 1; + z-index: 10; +} diff --git a/src/components/shared_ui/button/button.scss b/src/components/shared_ui/button/button.scss new file mode 100644 index 00000000..166223ac --- /dev/null +++ b/src/components/shared_ui/button/button.scss @@ -0,0 +1,289 @@ +:root { + --button-primary-default: #ff444f; + --text-colored-background: #fff; + --button-primary-hover: #eb3e48; + --button-secondary-default: #6e6e6e; +} + +.dc-btn { + vertical-align: middle; + align-items: center; + justify-content: center; + touch-action: manipulation; + cursor: pointer; + white-space: nowrap; + padding: 0 1.6rem; + display: inline-flex; + border: 0; + height: 3.2rem; + border-radius: 4px; + transition: all 0.2s cubic-bezier(0.65, 0.05, 0.36, 1); + outline: 0; + position: relative; + text-decoration: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; + /* + * Text will be transformed to sentence case in JS + * text-transform is declared in .dc-btn instead of .dc-btn__text + * to ensure consistency (even for children) as per styleguide + */ + text-transform: none !important; + + &__toggle { + height: auto; + padding: 0.8rem 2.5rem; + } + &__text, + &__icon { + display: flex; + pointer-events: none; + } + &__icon { + @include typeface(--paragraph-center-bold-black, unset); + color: var(--text-general); + padding-right: 0.8rem; + pointer-events: none; + + &--circle { + padding-right: 0; + } + } + &:not([disabled]):hover { + text-decoration: none; + } + &__rounded { + border-radius: 24px; + } + &--plus { + width: 2.4rem; + height: 2.4rem !important; + border: 1px solid var(--border-hover) !important; + padding: 0 !important; + background: inherit; + background-repeat: no-repeat; + background-position: center; + } + &--circle { + height: 3.2rem; + width: 3.2rem !important; + border-radius: 50%; + } + &--circular { + border-radius: 100px; + } + &--transparent { + background: transparent; + } + &--primary { + background: var(--button-primary-default); + color: var(--text-colored-background); + + &:hover:not([disabled]) { + background: var(--button-primary-hover); + } + &:active:not([disabled]) { + background: var(--button-primary-default); + } + &:disabled, + &[disabled] { + opacity: 0.32; + cursor: not-allowed !important; + } + .dc-btn__text, + .dc-btn__icon { + color: var(--text-colored-background); + } + + &__light { + background: var(--button-primary-light-default); + + &:hover:not([disabled]) { + background: var(--button-primary-light-hover); + } + &:active:not([disabled]) { + background: var(--button-primary-light-default); + } + &:disabled, + &[disabled] { + opacity: 0.32; + cursor: not-allowed !important; + } + .dc-btn__text, + .dc-btn__icon { + color: var(--brand-red-coral); + } + } + } + &--secondary { + background: transparent; + border: 1px solid var(--button-secondary-default); + + &:hover:not([disabled]) { + background: var(--button-secondary-hover); + } + &:active:not([disabled]) { + border: 1px solid var(--button-secondary-default); + } + &:disabled, + &[disabled] { + opacity: 0.32; + cursor: not-allowed !important; + } + .dc-btn__text, + .dc-btn__icon { + color: var(--text-prominent); + } + } + &--tertiary { + background: var(--button-tertiary-default); + + &:hover:not([disabled]) { + background: var(--button-tertiary-hover); + } + &:active:not([disabled]) { + background: var(--button-tertiary-default); + } + .dc-btn__text, + .dc-btn__icon { + color: var(--brand-red-coral); + } + &:disabled, + &[disabled] { + opacity: 0.32; + cursor: not-allowed !important; + } + } + &--alternate { + background: var(--button-tertiary-default); + border: 2px solid var(--button-primary-default); + + &:hover:not([disabled]) { + background: var(--button-primary-default); + + .dc-btn__text { + color: var(--text-colored-background); + } + } + + .dc-btn__text, + .dc-btn__icon { + color: var(--brand-red-coral); + } + } + /* TODO: confirm this button with designer are we still using this? */ + &--green { + background: var(--status-success); + + &:hover:not([disabled]) { + background: var(--transparent-success); + } + &:active:not([disabled]) { + background: var(--status-success); + } + .dc-btn__text, + .dc-btn__icon { + color: var(--text-colored-background); + } + } + &--black { + background: var(--button-get-started-bg); + &:hover:not([disabled]) { + opacity: 0.7; + } + &:active:not([disabled]) { + opacity: 0.7; + } + + .dc-btn__text, + .dc-btn__icon { + color: var(--general-main-1); + } + } + &__small { + height: 2.4rem; + min-width: 4.8rem; + border-width: 1px; + + .dc-btn__text { + font-size: 1.2rem; + } + } + &__medium { + height: 3.2rem; + min-width: 5.6rem; + border-width: 1px; + + .dc-btn__text { + font-size: 1.4rem; + } + } + &__large { + height: 4rem; + min-width: 6.4rem; + border-width: 2px; + + .dc-btn__text { + font-size: 1.4rem; + } + } + &__wide { + width: 100%; + height: 4rem; + } + &__effect:focus:not(:active):after { + content: ''; + position: absolute; + top: -0.1em; + left: -0.1em; + bottom: -0.1em; + right: -0.1em; + border-radius: inherit; + border: 0 solid var(--brand-secondary); + opacity: 0.4; + animation: button-effect 0.4s; + animation-fill-mode: forwards; + display: block; + } + &__group { + white-space: nowrap; + + .dc-btn + .dc-btn { + margin-left: 8px; + } + } + &__button-group { + border-radius: 0 4px 4px 0; + } + /* TODO: confirm this button with designer are we still using this? */ + /* postcss-bem-linter: ignore */ + .initial-loader--btn { + background-color: unset; + + /* postcss-bem-linter: ignore */ + .initial-loader__barspinner--rect { + background-color: var(--text-colored-background); + } + /* postcss-bem-linter: ignore */ + .barspinner { + margin: 0.6rem 4px 0 -4px; + + /* postcss-bem-linter: ignore */ + &__rect { + height: 35%; + } + } + } +} + +@keyframes button-effect { + to { + opacity: 0; + top: -0.6em; + left: -0.6em; + bottom: -0.6em; + right: -0.6em; + border-width: 6px; + } +} diff --git a/src/components/shared_ui/button/button.tsx b/src/components/shared_ui/button/button.tsx new file mode 100644 index 00000000..f0b174ed --- /dev/null +++ b/src/components/shared_ui/button/button.tsx @@ -0,0 +1,144 @@ +import classNames from 'classnames'; +import React from 'react'; +import ButtonLoading from './button_loading'; +import Text from '../text'; +import { Icon } from '@/utils/tmp/dummy'; + +export type TButtonCommonProps = { + alternate: boolean; + black: boolean; + blue: boolean; + green: boolean; + has_effect: boolean; + is_button_toggle: boolean; + is_circular: boolean; + is_circle: boolean; + is_disabled: boolean; + is_loading: boolean; + is_plus: boolean; + is_submit_success: boolean; + large: boolean; + medium: boolean; + primary: boolean; + primary_light: boolean; + rounded: boolean; + secondary: boolean; + small: boolean; + tertiary: boolean; + text: string; + transparent: boolean; +}; +export type TButtonProps = React.PropsWithChildren> & + TButtonCommonProps & { + classNameSpan: string; + icon: React.ReactNode; + renderText: (param: React.ReactNode) => React.ReactNode; + type: 'button' | 'submit' | 'reset'; + wrapperClassName: string; + }; + +export type TButtonGroupProps = { + children: React.ReactNode | React.ReactNode[]; + className?: string; +}; + +const ButtonGroup = ({ children, className }: TButtonGroupProps) => ( +
{children}
+); +const Button = ({ + black, + blue, + children, + className = '', + classNameSpan, + green, + has_effect, + icon, + id, + is_disabled, + is_loading, + is_submit_success, + is_button_toggle, + is_circle, + is_circular, + is_plus, + large, + medium, + onClick, + rounded, + tabIndex = 0, + text, + wrapperClassName, + type, + primary, + primary_light, + secondary, + alternate, + transparent, + small, + tertiary, + renderText, + ...props +}: Partial) => { + const classes = classNames( + 'dc-btn', + { + 'dc-btn__effect': has_effect, + 'dc-btn--primary': primary, + 'dc-btn--black': black, + 'dc-btn--blue': blue, + 'dc-btn--secondary': secondary, + 'dc-btn--tertiary': tertiary, + 'dc-btn--primary__light': primary_light, + 'dc-btn--primary__blue': blue && primary, + 'dc-btn--tertiary__blue': blue && tertiary, + 'dc-btn--alternate': alternate, + 'dc-btn--green': green, + 'dc-btn__rounded': rounded, + 'dc-btn__large': large, + 'dc-btn__medium': medium, + 'dc-btn__small': small, + 'dc-btn__toggle': is_button_toggle, + 'dc-btn--plus': is_plus, + 'dc-btn--circle': is_circle, + 'dc-btn--circular': is_circular, + 'dc-btn--transparent': transparent, + }, + className + ); + const button = ( + + ); + const wrapper =
{button}
; + + return wrapperClassName ? wrapper : button; +}; + +Button.Group = ButtonGroup; + +export default Button; diff --git a/src/components/shared_ui/button/button_loading.tsx b/src/components/shared_ui/button/button_loading.tsx new file mode 100644 index 00000000..a9d8d9e7 --- /dev/null +++ b/src/components/shared_ui/button/button_loading.tsx @@ -0,0 +1,10 @@ +import Loading, { TLoadingProps } from '../loading/loading'; +import '../loading/loading.scss'; + +const ButtonLoading = ( + props: Omit +) => { + return ; +}; + +export default ButtonLoading; diff --git a/src/components/shared_ui/button/index.ts b/src/components/shared_ui/button/index.ts new file mode 100644 index 00000000..a6968c5e --- /dev/null +++ b/src/components/shared_ui/button/index.ts @@ -0,0 +1,4 @@ +import Button from './button'; +import './button.scss'; + +export default Button; diff --git a/src/components/shared_ui/dialog/dialog.scss b/src/components/shared_ui/dialog/dialog.scss new file mode 100644 index 00000000..07c4a6a9 --- /dev/null +++ b/src/components/shared_ui/dialog/dialog.scss @@ -0,0 +1,137 @@ +/** @define dc-dialog */ + +.dc-dialog { + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + flex-direction: column; + z-index: 999; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + &__wrapper { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 999; + transition: opacity 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25); + background-color: var(--overlay-outside-dialog); + + &--enter, + &--exit { + opacity: 0; + + .dc-dialog__dialog { + transform: translate3d(0, 50px, 0); + opacity: 0; + } + } + &--enter-done { + opacity: 1; + + .dc-dialog__dialog { + transform: translate3d(0, 0, 0); + opacity: 1; + } + } + &--has-portal { + background-color: transparent; + } + } + &__dialog { + max-width: 560px; + max-height: 338px; + min-width: 440px; + min-height: 176px; + margin-top: -#{$HEADER_HEIGHT}; + padding: 2.4rem; + border-radius: 8px; + box-sizing: border-box; + display: flex; + justify-content: space-around; + flex-direction: column; + align-items: center; + box-shadow: 0 2px 8px 0 var(--shadow-menu); + background-color: var(--general-main-2); + transition: + transform 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25), + opacity 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25); + } + &__header { + &-wrapper { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + &--end { + justify-content: flex-end; + } + } + &--title { + align-self: flex-start; + } + &--close { + cursor: pointer; + } + } + &__content { + align-self: flex-start; + margin-bottom: 2.4rem; + max-width: calc(440px - 4.8rem); + width: 100%; + + /* postcss-bem-linter: ignore */ + .dc-input__label { + background-color: var(--general-main-2); + } + &--centered { + align-self: center; + } + } + &__footer { + display: flex; + justify-content: flex-end; + align-items: center; + width: 100%; + @include mobile { + flex-wrap: wrap; + align-items: flex-start; + } + } + &__button { + margin-left: 0.8rem; + height: 4rem; + min-width: 6.4rem; + border-width: 2px; + @include mobile { + &:not(:last-child) { + margin-bottom: 1rem; + } + } + } +} + +@media screen and (max-width: 560px) { + .dc-dialog { + &__dialog { + min-width: auto; + + &--has-margin { + min-width: unset; + width: calc(100vw - 4.8rem); + } + } + } +} diff --git a/src/components/shared_ui/dialog/dialog.tsx b/src/components/shared_ui/dialog/dialog.tsx new file mode 100644 index 00000000..ea630f57 --- /dev/null +++ b/src/components/shared_ui/dialog/dialog.tsx @@ -0,0 +1,197 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { CSSTransition } from 'react-transition-group'; +import classNames from 'classnames'; + +import Text from '../text'; +import { Icon } from '@/utils/tmp/dummy'; +import Button from '../button'; + +type TDialog = { + cancel_button_text?: string; + className?: string; + confirm_button_text?: string; + dismissable?: boolean; + disableApp?: () => void; + enableApp?: () => void; + has_close_icon?: boolean; + is_closed_on_cancel?: boolean; + is_closed_on_confirm?: boolean; + is_content_centered?: boolean; + is_loading?: boolean; + is_mobile_full_width?: boolean; + is_visible: boolean; + onCancel?: () => void; + onClose?: () => void; + onConfirm: () => void; + onEscapeButtonCancel?: () => void; + portal_element_id?: string; + title?: React.ReactNode; +}; + +const Dialog = ({ + disableApp, + // dismissable, + enableApp, + is_closed_on_cancel = true, + is_closed_on_confirm = true, + is_visible, + onCancel, + onClose, + onConfirm, + onEscapeButtonCancel, + ...other_props +}: React.PropsWithChildren) => { + const { + cancel_button_text, + className, + children, + confirm_button_text, + is_loading, + is_mobile_full_width = true, + is_content_centered, + portal_element_id, + title, + has_close_icon, + } = other_props; + + const wrapper_ref = React.useRef() as React.MutableRefObject; + + React.useEffect(() => { + if (is_visible && !!disableApp) { + disableApp(); + } + }, [is_visible, disableApp]); + + React.useEffect(() => { + const close = (e: { key: string }) => { + if (e.key === 'Escape') { + onEscapeButtonCancel?.(); + } + }; + window.addEventListener('keydown', close); + return () => window.removeEventListener('keydown', close); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleCancel = () => { + if (is_closed_on_cancel && enableApp) { + enableApp(); + } + onCancel?.(); + }; + + const handleConfirm = () => { + if (is_closed_on_confirm && enableApp) { + enableApp(); + } + onConfirm(); + }; + + const handleClose = () => { + if (onClose) { + onClose(); + } else if (onCancel) { + handleCancel(); + } else { + handleConfirm(); + } + }; + + // const validateClickOutside = () => !!dismissable || !!(has_close_icon && is_visible && is_closed_on_cancel); + + // useOnClickOutside(wrapper_ref, handleClose, validateClickOutside); + + const content_classes = classNames('dc-dialog__content', { + 'dc-dialog__content--centered': is_content_centered, + }); + // + const is_text = + typeof children === 'string' || + (React.isValidElement(children) && typeof children?.props?.i18n_default_text === 'string'); + const dialog = ( + +
+
+ {(title || has_close_icon) && ( +
+ {!!title && ( + + {title} + + )} + {has_close_icon && ( +
+ +
+ )} +
+ )} + {is_text ? ( + + {children} + + ) : ( +
{children}
+ )} +
+ {!!onCancel && ( +
+
+
+
+ ); + + if (portal_element_id) { + const target_element = document.getElementById(portal_element_id); + if (target_element) return ReactDOM.createPortal(dialog, target_element); + } + + return dialog; +}; + +export default Dialog; diff --git a/src/components/shared_ui/dialog/index.ts b/src/components/shared_ui/dialog/index.ts new file mode 100644 index 00000000..afccdee4 --- /dev/null +++ b/src/components/shared_ui/dialog/index.ts @@ -0,0 +1,5 @@ +import Dialog from './dialog'; + +import './dialog.scss'; + +export default Dialog; diff --git a/src/components/shared_ui/input/input.scss b/src/components/shared_ui/input/input.scss index 121bd6f4..b7c51e53 100644 --- a/src/components/shared_ui/input/input.scss +++ b/src/components/shared_ui/input/input.scss @@ -1,5 +1,12 @@ @import './../../shared/styles/constants.scss'; +:root { + --brand-secondary: #85acb0; + --border-normal: #d6dadb; + --gneral-main-1: #fff; + --text-loss-danger: #ec3f3f; +} + .dc-input { position: relative; width: 100%; @@ -11,6 +18,7 @@ &:hover:not(.dc-input--disabled) { border-color: var(--border-hover); } + &:focus-within { border-color: var(--brand-secondary); @@ -18,6 +26,7 @@ border-color: var(--brand-secondary); } } + &--bottom-label-active { margin-bottom: unset; @@ -25,10 +34,12 @@ margin-bottom: calc(5rem - 12px); } } + &__bottom-label { margin-left: 1.2rem; margin-bottom: calc(3.2rem - 12px); } + &--disabled { border-color: var(--border-normal); @@ -36,6 +47,7 @@ color: var(--text-less-prominent); } } + &--error { @media (max-width: 992px) { margin-bottom: 5rem; @@ -50,6 +62,7 @@ opacity: 1 !important; } } + &__container { display: flex; align-items: center; @@ -61,6 +74,7 @@ &:hover:not(.dc-input--disabled) { border-color: var(--general-disabled); } + &:focus-within { border-color: var(--brand-secondary); @@ -68,13 +82,16 @@ border-color: var(--brand-secondary); } } + &--error { border-color: var(--brand-red-coral) !important; } + &--disabled { border-color: var(--general-disabled); } } + &__field { background: none; color: var(--text-prominent); @@ -93,9 +110,11 @@ line-height: 1.25; } } + &--placeholder-visible::placeholder { opacity: 0.4; } + /* Not empty fields */ &:focus, &:not(:focus):not([value='']) { @@ -106,6 +125,7 @@ padding: 0 4px; } } + &:disabled { -webkit-text-fill-color: var(--text-less-prominent); // iOS opacity: 1; // iOS @@ -114,11 +134,13 @@ & ~ label { color: var(--text-less-prominent) !important; } + & ~ svg { .color1-fill { fill: var(--text-less-prominent); } } + // TODO: Ugly safari override hack, find better way to override shadow dom generated by Safari /* stylelint-disable */ @media not all and (min-resolution: 0.001dpcm) { @@ -128,29 +150,35 @@ } /* stylelint-enable */ } + &:focus { outline: none; & ~ label { color: var(--brand-secondary); } + &::placeholder { opacity: 0.4; } } + &:not(.dc-input--no-placeholder):not(:focus):not([value='']) { & ~ label { color: var(--text-general); } } + &[type='number']::-webkit-inner-spin-button, &[type='number']::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } + &[type='number'] { -moz-appearance: textfield; } + &[type='textarea'] { height: 9.6rem; border: none; @@ -160,6 +188,7 @@ top: 10rem; } } + /* To get rid of ugly chrome autofill shadow dom-applied background color */ &:-webkit-autofill { border-radius: $BORDER_RADIUS; @@ -172,11 +201,13 @@ -webkit-box-shadow: 0 0 0 30px var(--fill-normal) inset !important; } } + // hide default eye icon in Edge browser &::-ms-reveal { display: none; } } + &__textarea { &:not(.dc-input--no-placeholder):not(:focus):empty { & ~ label { @@ -186,6 +217,7 @@ } } } + &__leading-icon { margin-left: 1rem; top: 1rem; @@ -202,12 +234,14 @@ // default padding for three letter currency symbols padding-left: calc(1.6rem + 2.4rem); } + &--usdc + .dc-input__field, &--ust + .dc-input__field { padding-left: calc(1.6rem + 3.2rem); } } } + &__trailing-icon { right: 0; font-size: var(--text-size-xs); @@ -219,22 +253,25 @@ // default padding for three letter currency symbols padding-right: calc(1.6rem + 2.4rem); } + &--usd { top: 1rem; right: 1.1rem; position: absolute; } + &--usdc + .dc-input__field, &--ust + .dc-input__field { padding-right: calc(1.6rem + 3.2rem); } } } + &__label { white-space: nowrap; color: var(--text-less-prominent); font-size: var(--text-size-xs); - background-color: var(--general-main-1); + background-color: #fff; position: absolute; pointer-events: none; left: 1.1rem; @@ -245,6 +282,7 @@ overflow: hidden; max-width: calc(100% - 1.4rem); } + &:not(&--no-placeholder) { $parent: &; @@ -254,6 +292,7 @@ transform: translateZ(0); } } + &__hint { margin: 0.1rem 0 -1.9rem 1.3rem; @@ -263,11 +302,13 @@ top: unset; } } + &__counter { color: var(--text-less-prominent); font-size: 1.2rem; margin-left: 1.2rem; } + &--no-placeholder { label { transform: translate(0, -1.8rem) scale(0.75); @@ -276,6 +317,7 @@ background-color: var(--fill-normal); } } + &__footer { display: flex; flex-direction: row; @@ -289,9 +331,11 @@ margin-left: auto; } } + &__wrapper { margin-bottom: 1.6rem; } + &__input-group { border-right-style: none; border-radius: 4px 0 0 4px; diff --git a/src/components/shared_ui/loading/index.ts b/src/components/shared_ui/loading/index.ts new file mode 100644 index 00000000..c170e770 --- /dev/null +++ b/src/components/shared_ui/loading/index.ts @@ -0,0 +1,4 @@ +import Loading from './loading'; +import './loading.scss'; + +export default Loading; diff --git a/src/components/shared_ui/loading/loading.scss b/src/components/shared_ui/loading/loading.scss new file mode 100644 index 00000000..58778e54 --- /dev/null +++ b/src/components/shared_ui/loading/loading.scss @@ -0,0 +1,60 @@ +/** @define barspinner */ +.barspinner { + margin: auto; + width: 6rem; + height: 2rem; + white-space: nowrap; + + &__rect { + margin: 0.4rem; + border-radius: 20px; + height: 60%; + width: 0.6rem; + display: inline-block; + @include createBarspinnerAnimation(5, 1.2s, 0.1); + } + &--dark .barspinner__rect { + background-color: var(--brand-secondary); + } +} + +@keyframes sk-stretchdelay { + 0%, + 40%, + 100% { + transform: scaleY(1); + } + 20% { + transform: scaleY(2); + } +} + +/** @define initial-loader */ +.initial-loader { + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + flex-direction: column; + display: flex; + background: transparent; + + &--fullscreen { + width: 100vw; + height: 100vh; + + @include desktop { + height: calc(100vh - #{$HEADER_HEIGHT} - #{$FOOTER_HEIGHT}); + } + @include mobile { + height: calc(100vh - #{$MOBILE_HEADER_HEIGHT}); + } + } + &__barspinner { + margin: 5rem auto; + + &--rect { + background-color: var(--brand-secondary); + } + } +} diff --git a/src/components/shared_ui/loading/loading.tsx b/src/components/shared_ui/loading/loading.tsx new file mode 100644 index 00000000..80a313ce --- /dev/null +++ b/src/components/shared_ui/loading/loading.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import classNames from 'classnames'; +import Text from '../text/text'; + +export type TLoadingProps = React.HTMLProps & { + is_fullscreen: boolean; + is_slow_loading: boolean; + status: string[]; + theme: string; +}; + +const Loading = ({ className, id, is_fullscreen = true, is_slow_loading, status, theme }: Partial) => { + const theme_class = theme ? `barspinner-${theme}` : 'barspinner-light'; + return ( +
+
+ {Array.from(new Array(5)).map((x, inx) => ( +
+ ))} +
+ {is_slow_loading && + status?.map((text, inx) => ( + + {text} + + ))} +
+ ); +}; + +export default Loading; diff --git a/src/components/shared_ui/modal/__tests__/modal.spec.tsx b/src/components/shared_ui/modal/__tests__/modal.spec.tsx new file mode 100644 index 00000000..c4b9a216 --- /dev/null +++ b/src/components/shared_ui/modal/__tests__/modal.spec.tsx @@ -0,0 +1,40 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import Modal from '../modal'; + +const modalRoot = document.createElement('div'); +modalRoot.setAttribute('id', 'modal_root'); +document.body.appendChild(modalRoot); + +describe('', () => { + test('shows the children', () => { + // Arrange + const handleToggleModal = jest.fn(); + + // Act + render( + +
test
+
+ ); + // Assert + expect(screen.getByText('test')).toBeInTheDocument(); + }); + + test('calls toggleModal on close', () => { + // Arrange + const handleToggleModal = jest.fn(); + + // Act + render( + +
test
+
+ ); + + // Assert + fireEvent.click(screen.getByRole('button')); + + // Assert + expect(handleToggleModal).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/shared_ui/modal/index.ts b/src/components/shared_ui/modal/index.ts new file mode 100644 index 00000000..b7aea668 --- /dev/null +++ b/src/components/shared_ui/modal/index.ts @@ -0,0 +1,4 @@ +import Modal from './modal'; +import './modal.scss'; + +export default Modal; diff --git a/src/components/shared_ui/modal/modal-body.tsx b/src/components/shared_ui/modal/modal-body.tsx new file mode 100644 index 00000000..4fcb85b8 --- /dev/null +++ b/src/components/shared_ui/modal/modal-body.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import classNames from 'classnames'; + +type TBody = { + className: string; +}; + +const Body = ({ children, className }: React.PropsWithChildren>) => ( +
{children}
+); + +export default Body; diff --git a/src/components/shared_ui/modal/modal-footer.tsx b/src/components/shared_ui/modal/modal-footer.tsx new file mode 100644 index 00000000..641b79f8 --- /dev/null +++ b/src/components/shared_ui/modal/modal-footer.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import classNames from 'classnames'; + +type TFooter = { + className: string; + has_separator: boolean; + is_bypassed: boolean; +}; + +const Footer = ({ children, className, has_separator, is_bypassed }: React.PropsWithChildren>) => { + if (is_bypassed) return {children}; + return ( +
+ {children} +
+ ); +}; + +export default Footer; diff --git a/src/components/shared_ui/modal/modal.scss b/src/components/shared_ui/modal/modal.scss new file mode 100644 index 00000000..1b93cd36 --- /dev/null +++ b/src/components/shared_ui/modal/modal.scss @@ -0,0 +1,276 @@ +/** Need to add new breakpoints for mixins */ +$max-mobile-width: 600px; +$min-desktop-width: 1280px; + +@mixin mobile-screen { + @media (max-width: #{$max-mobile-width}) { + @content; + } +} + +@mixin tablet-screen { + @media (min-width: #{$max-mobile-width + 1}) and (max-width: #{$min-desktop-width - 1}) { + @content; + } +} + +@mixin desktop-screen { + @media (min-width: #{$min-desktop-width}) { + @content; + } +} + +@mixin mobile-or-tablet-screen { + @media (max-width: #{$min-desktop-width - 1}) { + @content; + } +} + +@mixin tablet-or-desktop-screen { + @media (min-width: #{$max-mobile-width + 1}) { + @content; + } +} + +/** @define dc-modal; weak */ +.dc-modal { + &__container { + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + border-radius: 8px; + transition: transform 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25), opacity 0.25s cubic-bezier(0.25, 0.1, 0.1, 0.25); + background-color: #fff; + box-shadow: 0 4px 6px 0 var(--shadow-menu); + + &--is-vertical-centered { + position: absolute; + top: 50vh; + transform: translateY(-50%) !important; + + .dc-modal__container { + min-width: unset; + top: 2rem; + } + + @include mobile { + left: 2.4rem; + width: calc(100vw - 4.8rem) !important; + } + } + + &--is-vertical-top { + top: $HEADER_HEIGHT; + position: absolute; + + @include mobile-screen { + top: $MOBILE_HEADER_HEIGHT; + left: 1.6rem; + width: calc(100vw - 3.2rem) !important; + } + } + + &--is-vertical-bottom { + bottom: $FOOTER_HEIGHT; + position: absolute; + + @include mobile-screen { + left: 1.6rem; + width: calc(100vw - 3.2rem) !important; + } + } + + &--hide { + display: none; + } + + &--enter, + &--exit { + transform: translateY(50px); + opacity: 0; + pointer-events: none; + } + + &--enter-done { + transform: translateY(0); + opacity: 1; + pointer-events: auto; + } + + &--small { + max-width: 440px; + + .dc-modal-header { + border-bottom: none; + } + } + + &--is-confirmation-modal { + .dc-modal { + &-header { + border-bottom: none; + + &__title { + padding: 2.4rem; + } + } + + &-body { + padding: 0 2.4rem; + } + + &-footer { + padding: 2.4rem; + } + } + } + + @include tablet-or-desktop-screen { + min-width: 440px !important; + max-height: calc(100vh - #{$HEADER_HEIGHT} - #{$FOOTER_HEIGHT}) !important; + } + + @include mobile-screen { + max-width: calc(100vw - 3.2rem) !important; + } + } + + &__container_sent-email__modal { + @include mobile-or-tablet-screen { + height: 42rem !important; + overflow-y: scroll !important; + } + } + + /** @define dc-modal-header */ + &-header { + display: flex; + justify-content: space-between; + width: 100%; + line-height: 24px; + align-items: center; + + &__border-bottom { + border-bottom: 2px solid var(--general-section-5) !important; + } + + &__icon { + margin-right: 1rem; + + &:hover { + cursor: pointer; + } + } + + &__title { + padding: 1.6rem 2.4rem; + display: flex; + align-items: center; + height: 100%; + + &:empty { + padding: 0; + border: none; + } + + @include mobile { + font-size: 1.4rem; + padding: 1.6rem; + line-height: 1.2; + } + } + + &__section { + flex-grow: 1; + padding: 1.6rem; + line-height: 24px; + display: flex; + align-items: center; + } + + &__close { + display: block; + cursor: pointer; + padding: 0.6rem 0.8rem; + height: 30px; + width: 32px; + margin: 1.2rem 1.6rem 1.2rem auto; + + @include mobile { + margin: 0.8rem; + } + } + + &--is-title-centered { + justify-content: flex-end; + position: relative; + + .dc-modal-header__title { + + @include mobile { + position: absolute; + } + + justify-content: center; + width: 100%; + } + + .dc-modal-header__close { + z-index: 1; + } + } + + // fix for safari bug with header being truncated + @media not all and (min-resolution: 0.001dpcm) { + + // stylelint-disable-line + @supports (-webkit-appearance: none) { + /* postcss-bem-linter: ignore */ + min-height: 4.8rem; + } + + } + } + + /** @define dc-modal-body */ + &-body { + &:first-child { + padding-top: 2.4rem; + } + + padding: 2.4rem; + font-size: 1.4rem; + line-height: 1.43; + color: var(--text-prominent); + + &__expiration { + min-height: 12rem; + padding: 1.6rem; + } + + @include mobile { + font-size: 1.2rem; + padding: 0.8rem 2.4rem; + } + } + /** @define dc-modal-footer; weak */ + &-footer { + display: flex; + justify-content: flex-end; + padding: 1.6rem; + margin-top: auto; + + .dc-btn { + margin: 0 0.8rem; + + &:last-child { + margin: 0; + } + } + + &--separator { + border-top: 2px solid var(--general-section-1); + } + } +} diff --git a/src/components/shared_ui/modal/modal.tsx b/src/components/shared_ui/modal/modal.tsx new file mode 100644 index 00000000..b15f2634 --- /dev/null +++ b/src/components/shared_ui/modal/modal.tsx @@ -0,0 +1,311 @@ +import React from 'react'; +import classNames from 'classnames'; +import ReactDOM from 'react-dom'; +import { CSSTransition } from 'react-transition-group'; +import Body from './modal-body'; +import Footer from './modal-footer'; +import Text from '../text/text'; +import { useOnClickOutside } from '@/hooks/useOnClickOutside'; +import { Icon } from '@/utils/tmp/dummy'; + +interface IClickEvent extends MouseEvent { + path?: HTMLElement[]; +} + +type TModalElement = { + className?: string; + close_icon_color?: string; + elements_to_ignore?: HTMLElement[]; + has_close_icon?: boolean; + has_return_icon?: boolean; + header?: React.ReactNode; + header_background_color?: string; + height?: string; + id?: string; + is_confirmation_modal?: boolean; + is_open: boolean; + is_risk_warning_visible?: boolean; + is_title_centered?: boolean; + is_vertical_bottom?: boolean; + is_vertical_centered?: boolean; + is_vertical_top?: boolean; + onMount?: () => void; + onReturn?: () => void; + onUnmount?: () => void; + portalId?: string; + renderTitle?: () => React.ReactNode; + should_close_on_click_outside?: boolean; + should_header_stick_body?: boolean; + small?: boolean; + title?: string | React.ReactNode; + toggleModal?: (e?: React.MouseEvent | React.KeyboardEvent) => void; + width?: string; +}; + +const ModalElement = ({ + children, + className, + close_icon_color, + elements_to_ignore, + has_close_icon = true, + has_return_icon = false, + header, + header_background_color, + height, + id, + is_confirmation_modal, + is_open, + is_risk_warning_visible, + is_title_centered, + is_vertical_bottom, + is_vertical_centered, + is_vertical_top, + onMount, + onReturn, + onUnmount, + portalId = 'modal_root', + renderTitle, + should_close_on_click_outside, + should_header_stick_body = true, + small, + title, + toggleModal, + width, +}: React.PropsWithChildren) => { + const el_ref = React.useRef(document.createElement('div')); + const el_portal_node = portalId && document.getElementById(portalId); + const modal_root_ref = React.useRef(el_portal_node || document.getElementById(portalId)); + const wrapper_ref = React.useRef(null); + + const portal_elements_selector = [ + '.dc-datepicker__picker', + '.dc-mobile-dialog', + '.dc-dropdown-list', + '.dc-dropdown__list', + '.modal_root', + ]; + + const isPortalElementVisible = () => + modal_root_ref.current?.querySelectorAll(portal_elements_selector.join(', ')).length; + + const validateClickOutside = (e: IClickEvent): boolean => { + const is_absolute_modal_visible = document.getElementById('popup_root')?.hasChildNodes(); + const path = e.path ?? e.composedPath?.(); + return ( + should_close_on_click_outside || + (has_close_icon && + !isPortalElementVisible() && + is_open && + !is_absolute_modal_visible && + !(elements_to_ignore && path?.find(el => elements_to_ignore.includes(el as HTMLElement)))) + ); + }; + + const closeModal = () => { + if (is_open) toggleModal?.(); + }; + + useOnClickOutside(wrapper_ref, closeModal, validateClickOutside); + + React.useEffect(() => { + const local_el_ref = el_ref; + const local_modal_root_ref = modal_root_ref; + + local_el_ref.current.classList.add('dc-modal'); + local_modal_root_ref?.current?.appendChild?.(local_el_ref.current); + onMount?.(); + + return () => { + local_modal_root_ref?.current?.removeChild?.(local_el_ref.current); + onUnmount?.(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const closeOnEscButton = React.useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + toggleModal?.(); + } + }, + [toggleModal] + ); + React.useEffect(() => { + window.addEventListener('keydown', closeOnEscButton); + return () => window.removeEventListener('keydown', closeOnEscButton); + }, [closeOnEscButton]); + + const rendered_title = renderTitle ? renderTitle() : null; + + return ReactDOM.createPortal( +
+ {!is_risk_warning_visible && (header || title || rendered_title) && ( +
+ {rendered_title && ( + + {rendered_title} + + )} + {title && ( + + {has_return_icon && ( + + )} + {title} + + )} + {header && ( +
+ {header} +
+ )} + {has_close_icon && ( +
+ +
+ )} +
+ )} + {children} +
, + el_ref.current + ); +}; + +type TModal = TModalElement & { + exit_classname?: string; + onEntered?: () => void; + onExited?: () => void; + transition_timeout?: React.ComponentProps['timeout']; +}; + +const Modal = ({ + children, + className, + close_icon_color, + elements_to_ignore, + exit_classname, + has_close_icon = true, + has_return_icon = false, + header, + header_background_color, + height, + id, + is_confirmation_modal, + is_open, + is_risk_warning_visible, + is_title_centered, + is_vertical_bottom, + is_vertical_centered, + is_vertical_top, + onEntered, + onExited, + onMount, + onReturn, + onUnmount, + portalId = 'modal_root', + renderTitle, + should_close_on_click_outside = false, + should_header_stick_body = true, + small, + title, + transition_timeout, + toggleModal, + width, +}: React.PropsWithChildren) => ( + + + {children} + + +); + +Modal.Body = Body; +Modal.Footer = Footer; + +export default Modal; diff --git a/src/pages/dashboard/cards.tsx b/src/pages/dashboard/cards.tsx index 65f502bf..591584d2 100644 --- a/src/pages/dashboard/cards.tsx +++ b/src/pages/dashboard/cards.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { observer } from 'mobx-react-lite'; import { localize } from '@deriv-com/translations'; -import { Dialog, Text } from '@deriv-com/ui'; +import Dialog from '@/components/shared_ui/dialog'; import { NOTIFICATION_TYPE } from '@/components/bot-notification/bot-notification-utils'; import DesktopWrapper from '@/components/shared_ui/desktop-wrapper'; @@ -18,6 +18,7 @@ import { rudderStackSendQsOpenEventFromDashboard } from '../bot-builder/quick-st import DashboardBotList from './load-bot-preview/dashboard-bot-list'; import GoogleDrive from './load-bot-preview/google-drive'; +import Text from '@/components/shared_ui/text'; type TCardProps = { has_dashboard_strategies: boolean; @@ -130,7 +131,7 @@ const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) => method(); }} /> - + {content}
diff --git a/src/pages/dashboard/dashboard.scss b/src/pages/dashboard/dashboard.scss index e19c728e..7e1cdc58 100644 --- a/src/pages/dashboard/dashboard.scss +++ b/src/pages/dashboard/dashboard.scss @@ -433,6 +433,7 @@ width: 24px; @include mobile { + display: flex; height: 16px; width: 16px; } diff --git a/src/pages/dashboard/load-bot-preview/delete-dialog.tsx b/src/pages/dashboard/load-bot-preview/delete-dialog.tsx index 25cf3ea0..d138f2ca 100644 --- a/src/pages/dashboard/load-bot-preview/delete-dialog.tsx +++ b/src/pages/dashboard/load-bot-preview/delete-dialog.tsx @@ -1,15 +1,14 @@ +/* eslint-disable simple-import-sort/imports */ import localForage from 'localforage'; import LZString from 'lz-string'; import { observer } from 'mobx-react-lite'; - -import { Dialog, Text } from '@deriv-com/ui'; - +import { Text } from '@deriv-com/ui'; import { TStrategy } from 'Types'; - import { NOTIFICATION_TYPE } from '@/components/bot-notification/bot-notification-utils'; import { getSavedWorkspaces } from '@/external/bot-skeleton'; import { useStore } from '@/hooks/useStore'; -import { localize } from '@/utils/tmp/dummy'; +import './delete-dialog.scss'; +import Dialog from '@/components/shared_ui/dialog'; const DeleteDialog = observer(() => { const { load_modal, dashboard } = useStore(); @@ -65,15 +64,15 @@ const DeleteDialog = observer(() => { return (
{ removeBotStrategy(selected_strategy_id); onToggleDeleteDialog(false); setOpenSettings(NOTIFICATION_TYPE.BOT_DELETE); }} - cancel_button_text={localize('No')} + cancel_button_text='No' onCancel={() => { onToggleDeleteDialog(false); }} @@ -82,14 +81,14 @@ const DeleteDialog = observer(() => { has_close_icon >
- - {localize('Your bot will be permanently deleted when you hit ')} - {localize('Yes, delete.')} + + Your bot will be permanently deleted when you hit + Yes, delete.
- - {localize('Are you sure you want to delete it?')} + + Are you sure you want to delete it?
diff --git a/src/pages/dashboard/load-bot-preview/google-drive.tsx b/src/pages/dashboard/load-bot-preview/google-drive.tsx index a3659ea9..87ea7244 100644 --- a/src/pages/dashboard/load-bot-preview/google-drive.tsx +++ b/src/pages/dashboard/load-bot-preview/google-drive.tsx @@ -2,11 +2,10 @@ import React from 'react'; import classnames from 'classnames'; import { observer } from 'mobx-react-lite'; -import { Button } from '@deriv-com/ui'; - import StaticUrl from '@/components/shared_ui/static-url'; import { useStore } from '@/hooks/useStore'; import { Icon, Localize, localize } from '@/utils/tmp/dummy'; +import Button from '@/components/shared_ui/button'; const GoogleDrive = observer(() => { const { ui, google_drive, load_modal } = useStore(); diff --git a/src/pages/dashboard/load-bot-preview/index.scss b/src/pages/dashboard/load-bot-preview/index.scss index 07608e0f..2505f62d 100644 --- a/src/pages/dashboard/load-bot-preview/index.scss +++ b/src/pages/dashboard/load-bot-preview/index.scss @@ -477,6 +477,9 @@ width: 25%; padding: 0.8rem 1.6rem; + img { + width: 1.6rem; + } @include mobile { padding: 0; } diff --git a/src/pages/dashboard/load-bot-preview/save-modal.tsx b/src/pages/dashboard/load-bot-preview/save-modal.tsx index 1238fff8..da14c7ee 100644 --- a/src/pages/dashboard/load-bot-preview/save-modal.tsx +++ b/src/pages/dashboard/load-bot-preview/save-modal.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { Field, Form, Formik } from 'formik'; import { observer } from 'mobx-react-lite'; -import { Button, Input, Modal, Text } from '@deriv-com/ui'; +import { Text } from '@deriv-com/ui'; import MobileFullPageModal from '@/components/shared_ui/mobile-full-page-modal'; import RadioGroup from '@/components/shared_ui/radio-group'; @@ -13,6 +13,9 @@ import { useStore } from '@/hooks/useStore'; import { Icon, localize } from '@/utils/tmp/dummy'; import IconRadio from './icon-radio'; +import Modal from '@/components/shared_ui/modal'; +import Button from '@/components/shared_ui/button'; +import Input from '@/components/shared_ui/input'; type TSaveModalForm = { bot_name: string; diff --git a/src/pages/dashboard/user-guide.tsx b/src/pages/dashboard/user-guide.tsx index 41231d1d..eae81dd4 100644 --- a/src/pages/dashboard/user-guide.tsx +++ b/src/pages/dashboard/user-guide.tsx @@ -22,7 +22,7 @@ const UserGuide: React.FC = ({ is_mobile, handleTabChange, setActive }} data-testid='btn-user-guide' > - + {!is_mobile && ( {localize('User Guide')} diff --git a/src/stores/save-modal-store.ts b/src/stores/save-modal-store.ts index 4f5ebe4b..554d6a02 100644 --- a/src/stores/save-modal-store.ts +++ b/src/stores/save-modal-store.ts @@ -59,9 +59,10 @@ export default class SaveModalStore implements ISaveModalStore { bot_name = ''; toggleSaveModal = (): void => { - if (!this.is_save_modal_open) { - this.setButtonStatus(button_status.NORMAL); - } + // console.log(this.is_save_modal_open) + // if (!this.is_save_modal_open) { + // this.setButtonStatus(button_status.NORMAL); + // } this.is_save_modal_open = !this.is_save_modal_open; }; From c59fdc95961dded5f397d672db1aa4791c0f5c49 Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 13:54:25 +0800 Subject: [PATCH 2/7] fix: undo commented code --- src/stores/save-modal-store.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/stores/save-modal-store.ts b/src/stores/save-modal-store.ts index 554d6a02..4f5ebe4b 100644 --- a/src/stores/save-modal-store.ts +++ b/src/stores/save-modal-store.ts @@ -59,10 +59,9 @@ export default class SaveModalStore implements ISaveModalStore { bot_name = ''; toggleSaveModal = (): void => { - // console.log(this.is_save_modal_open) - // if (!this.is_save_modal_open) { - // this.setButtonStatus(button_status.NORMAL); - // } + if (!this.is_save_modal_open) { + this.setButtonStatus(button_status.NORMAL); + } this.is_save_modal_open = !this.is_save_modal_open; }; From adef95fa2878d928c76c0e5b1d3b7ce1855f87e2 Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 13:55:34 +0800 Subject: [PATCH 3/7] fix: reverted package json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a20e9c51..438e7d11 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "scripts": { "start": "vite", - "start:dev": "vite-live-preview --reload --port=8444", + "start:dev": "vite-live-preview --reload --port=8443", "build": "vite build", "preview": "vite preview", "test:lint": "prettier --log-level silent --write . && eslint \"./src/**/*.?(js|jsx|ts|tsx)\"", @@ -118,4 +118,4 @@ "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" } -} +} \ No newline at end of file From 0c91e61abc8e8567ddd2c2462da741db91deab6b Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 13:56:00 +0800 Subject: [PATCH 4/7] fix: reverted package json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 438e7d11..3a12b541 100644 --- a/package.json +++ b/package.json @@ -118,4 +118,4 @@ "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" } -} \ No newline at end of file +} From 3fd766a9a52ab538b25de149ed20fa1d5b5b5880 Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 14:00:29 +0800 Subject: [PATCH 5/7] fix: dialog component --- src/components/shared_ui/dialog/dialog.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/shared_ui/dialog/dialog.tsx b/src/components/shared_ui/dialog/dialog.tsx index ea630f57..3def5d0c 100644 --- a/src/components/shared_ui/dialog/dialog.tsx +++ b/src/components/shared_ui/dialog/dialog.tsx @@ -3,9 +3,11 @@ import ReactDOM from 'react-dom'; import { CSSTransition } from 'react-transition-group'; import classNames from 'classnames'; -import Text from '../text'; +import { useOnClickOutside } from '@/hooks/useOnClickOutside'; import { Icon } from '@/utils/tmp/dummy'; + import Button from '../button'; +import Text from '../text'; type TDialog = { cancel_button_text?: string; @@ -31,7 +33,7 @@ type TDialog = { const Dialog = ({ disableApp, - // dismissable, + dismissable, enableApp, is_closed_on_cancel = true, is_closed_on_confirm = true, @@ -98,14 +100,14 @@ const Dialog = ({ } }; - // const validateClickOutside = () => !!dismissable || !!(has_close_icon && is_visible && is_closed_on_cancel); + const validateClickOutside = () => !!dismissable || !!(has_close_icon && is_visible && is_closed_on_cancel); - // useOnClickOutside(wrapper_ref, handleClose, validateClickOutside); + useOnClickOutside(wrapper_ref, handleClose, validateClickOutside); const content_classes = classNames('dc-dialog__content', { 'dc-dialog__content--centered': is_content_centered, }); - // + const is_text = typeof children === 'string' || (React.isValidElement(children) && typeof children?.props?.i18n_default_text === 'string'); From 15eb1fc48734043b654fce12a46624f8e041c5bc Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 15:23:47 +0800 Subject: [PATCH 6/7] fix: eslint --- src/components/shared_ui/button/button.tsx | 9 ++++++--- src/components/shared_ui/button/button_loading.tsx | 1 + src/components/shared_ui/button/index.ts | 1 + src/components/shared_ui/loading/index.ts | 1 + src/components/shared_ui/loading/loading.tsx | 1 + .../shared_ui/modal/__tests__/modal.spec.tsx | 3 ++- src/components/shared_ui/modal/index.ts | 1 + src/components/shared_ui/modal/modal.tsx | 11 +++++++---- src/pages/dashboard/cards.tsx | 4 ++-- src/pages/dashboard/load-bot-preview/google-drive.tsx | 2 +- src/pages/dashboard/load-bot-preview/save-modal.tsx | 6 +++--- 11 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/shared_ui/button/button.tsx b/src/components/shared_ui/button/button.tsx index f0b174ed..50ff1c54 100644 --- a/src/components/shared_ui/button/button.tsx +++ b/src/components/shared_ui/button/button.tsx @@ -1,9 +1,12 @@ -import classNames from 'classnames'; import React from 'react'; -import ButtonLoading from './button_loading'; -import Text from '../text'; +import classNames from 'classnames'; + import { Icon } from '@/utils/tmp/dummy'; +import Text from '../text'; + +import ButtonLoading from './button_loading'; + export type TButtonCommonProps = { alternate: boolean; black: boolean; diff --git a/src/components/shared_ui/button/button_loading.tsx b/src/components/shared_ui/button/button_loading.tsx index a9d8d9e7..ef87162f 100644 --- a/src/components/shared_ui/button/button_loading.tsx +++ b/src/components/shared_ui/button/button_loading.tsx @@ -1,4 +1,5 @@ import Loading, { TLoadingProps } from '../loading/loading'; + import '../loading/loading.scss'; const ButtonLoading = ( diff --git a/src/components/shared_ui/button/index.ts b/src/components/shared_ui/button/index.ts index a6968c5e..64460f3e 100644 --- a/src/components/shared_ui/button/index.ts +++ b/src/components/shared_ui/button/index.ts @@ -1,4 +1,5 @@ import Button from './button'; + import './button.scss'; export default Button; diff --git a/src/components/shared_ui/loading/index.ts b/src/components/shared_ui/loading/index.ts index c170e770..ccb3022c 100644 --- a/src/components/shared_ui/loading/index.ts +++ b/src/components/shared_ui/loading/index.ts @@ -1,4 +1,5 @@ import Loading from './loading'; + import './loading.scss'; export default Loading; diff --git a/src/components/shared_ui/loading/loading.tsx b/src/components/shared_ui/loading/loading.tsx index 80a313ce..d3f63a62 100644 --- a/src/components/shared_ui/loading/loading.tsx +++ b/src/components/shared_ui/loading/loading.tsx @@ -1,5 +1,6 @@ import React from 'react'; import classNames from 'classnames'; + import Text from '../text/text'; export type TLoadingProps = React.HTMLProps & { diff --git a/src/components/shared_ui/modal/__tests__/modal.spec.tsx b/src/components/shared_ui/modal/__tests__/modal.spec.tsx index c4b9a216..75727ebf 100644 --- a/src/components/shared_ui/modal/__tests__/modal.spec.tsx +++ b/src/components/shared_ui/modal/__tests__/modal.spec.tsx @@ -1,4 +1,5 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; + import Modal from '../modal'; const modalRoot = document.createElement('div'); diff --git a/src/components/shared_ui/modal/index.ts b/src/components/shared_ui/modal/index.ts index b7aea668..ae6504d0 100644 --- a/src/components/shared_ui/modal/index.ts +++ b/src/components/shared_ui/modal/index.ts @@ -1,4 +1,5 @@ import Modal from './modal'; + import './modal.scss'; export default Modal; diff --git a/src/components/shared_ui/modal/modal.tsx b/src/components/shared_ui/modal/modal.tsx index b15f2634..b987388f 100644 --- a/src/components/shared_ui/modal/modal.tsx +++ b/src/components/shared_ui/modal/modal.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import classNames from 'classnames'; import ReactDOM from 'react-dom'; import { CSSTransition } from 'react-transition-group'; -import Body from './modal-body'; -import Footer from './modal-footer'; -import Text from '../text/text'; +import classNames from 'classnames'; + import { useOnClickOutside } from '@/hooks/useOnClickOutside'; import { Icon } from '@/utils/tmp/dummy'; +import Text from '../text/text'; + +import Body from './modal-body'; +import Footer from './modal-footer'; + interface IClickEvent extends MouseEvent { path?: HTMLElement[]; } diff --git a/src/pages/dashboard/cards.tsx b/src/pages/dashboard/cards.tsx index 591584d2..a3375626 100644 --- a/src/pages/dashboard/cards.tsx +++ b/src/pages/dashboard/cards.tsx @@ -4,12 +4,13 @@ import classNames from 'classnames'; import { observer } from 'mobx-react-lite'; import { localize } from '@deriv-com/translations'; -import Dialog from '@/components/shared_ui/dialog'; import { NOTIFICATION_TYPE } from '@/components/bot-notification/bot-notification-utils'; import DesktopWrapper from '@/components/shared_ui/desktop-wrapper'; +import Dialog from '@/components/shared_ui/dialog'; import MobileFullPageModal from '@/components/shared_ui/mobile-full-page-modal'; import MobileWrapper from '@/components/shared_ui/mobile-wrapper'; +import Text from '@/components/shared_ui/text'; import { DBOT_TABS } from '@/constants/bot-contents'; import { useStore } from '@/hooks/useStore'; import { Icon } from '@/utils/tmp/dummy'; @@ -18,7 +19,6 @@ import { rudderStackSendQsOpenEventFromDashboard } from '../bot-builder/quick-st import DashboardBotList from './load-bot-preview/dashboard-bot-list'; import GoogleDrive from './load-bot-preview/google-drive'; -import Text from '@/components/shared_ui/text'; type TCardProps = { has_dashboard_strategies: boolean; diff --git a/src/pages/dashboard/load-bot-preview/google-drive.tsx b/src/pages/dashboard/load-bot-preview/google-drive.tsx index 87ea7244..a6ec48a7 100644 --- a/src/pages/dashboard/load-bot-preview/google-drive.tsx +++ b/src/pages/dashboard/load-bot-preview/google-drive.tsx @@ -2,10 +2,10 @@ import React from 'react'; import classnames from 'classnames'; import { observer } from 'mobx-react-lite'; +import Button from '@/components/shared_ui/button'; import StaticUrl from '@/components/shared_ui/static-url'; import { useStore } from '@/hooks/useStore'; import { Icon, Localize, localize } from '@/utils/tmp/dummy'; -import Button from '@/components/shared_ui/button'; const GoogleDrive = observer(() => { const { ui, google_drive, load_modal } = useStore(); diff --git a/src/pages/dashboard/load-bot-preview/save-modal.tsx b/src/pages/dashboard/load-bot-preview/save-modal.tsx index da14c7ee..61ee7fd1 100644 --- a/src/pages/dashboard/load-bot-preview/save-modal.tsx +++ b/src/pages/dashboard/load-bot-preview/save-modal.tsx @@ -5,7 +5,10 @@ import { observer } from 'mobx-react-lite'; import { Text } from '@deriv-com/ui'; +import Button from '@/components/shared_ui/button'; +import Input from '@/components/shared_ui/input'; import MobileFullPageModal from '@/components/shared_ui/mobile-full-page-modal'; +import Modal from '@/components/shared_ui/modal'; import RadioGroup from '@/components/shared_ui/radio-group'; import ThemedScrollbars from '@/components/shared_ui/themed-scrollbars'; import { config, save_types } from '@/external/bot-skeleton'; @@ -13,9 +16,6 @@ import { useStore } from '@/hooks/useStore'; import { Icon, localize } from '@/utils/tmp/dummy'; import IconRadio from './icon-radio'; -import Modal from '@/components/shared_ui/modal'; -import Button from '@/components/shared_ui/button'; -import Input from '@/components/shared_ui/input'; type TSaveModalForm = { bot_name: string; From 21543293b85ced4f043a017a92ed54efe43ae3c8 Mon Sep 17 00:00:00 2001 From: Rupato Date: Thu, 11 Jul 2024 15:28:07 +0800 Subject: [PATCH 7/7] fix: test case --- .../shared_ui/modal/__tests__/modal.spec.tsx | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/components/shared_ui/modal/__tests__/modal.spec.tsx diff --git a/src/components/shared_ui/modal/__tests__/modal.spec.tsx b/src/components/shared_ui/modal/__tests__/modal.spec.tsx deleted file mode 100644 index 75727ebf..00000000 --- a/src/components/shared_ui/modal/__tests__/modal.spec.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react'; - -import Modal from '../modal'; - -const modalRoot = document.createElement('div'); -modalRoot.setAttribute('id', 'modal_root'); -document.body.appendChild(modalRoot); - -describe('', () => { - test('shows the children', () => { - // Arrange - const handleToggleModal = jest.fn(); - - // Act - render( - -
test
-
- ); - // Assert - expect(screen.getByText('test')).toBeInTheDocument(); - }); - - test('calls toggleModal on close', () => { - // Arrange - const handleToggleModal = jest.fn(); - - // Act - render( - -
test
-
- ); - - // Assert - fireEvent.click(screen.getByRole('button')); - - // Assert - expect(handleToggleModal).toHaveBeenCalledTimes(1); - }); -});