From 2b4db7f8420b910bc06665a5cf36c97f4901ce84 Mon Sep 17 00:00:00 2001 From: Bogdan Iasinovschi Date: Thu, 28 Nov 2024 10:16:54 +0200 Subject: [PATCH] Rebrand modals (#495) * Remove unnecessary button wrapper * Update Modal component * Add button tertiary variant * Add button text-blue variant * Adapt Input component * Update Deposit modal * Update Withdraw/Stake/Unstake modals * Update Unstake Confirm modal * Update Unsupported Network modal * Self review * Show primary buttons first on mobile * Adapt to Button component without wrapper --- cypress/e2e/keyboard-navigation.cy.ts | 4 +- cypress/e2e/staking.cy.ts | 6 +- public/exclamation-triangle-fill.svg | 6 + public/unsupported-network.svg | 3 - src/components/button/button.module.scss | 25 ++- src/components/button/button.tsx | 63 ++++-- .../variants/tertiary-color.module.scss | 76 +++++++ .../button/variants/text-blue.module.scss | 63 ++++++ src/components/icons/close.tsx | 22 ++ src/components/icons/help-outline.tsx | 16 ++ src/components/icons/index.ts | 4 + src/components/input/input.module.scss | 57 ++--- src/components/input/input.tsx | 13 +- src/components/layout/layout.module.scss | 11 +- src/components/modal/modal.module.scss | 204 +++++++++++++----- src/components/modal/modal.tsx | 94 +++++--- src/components/sign-in/sign-in.module.scss | 48 ++++- src/components/sign-in/sign-in.tsx | 48 +++-- src/index.scss | 15 +- .../dashboard/forms/confirm-unstake-form.tsx | 11 +- src/pages/dashboard/forms/forms.module.scss | 97 +++++++-- .../dashboard/forms/token-amount-form.tsx | 20 +- .../dashboard/forms/token-deposit-form.tsx | 36 ++-- .../delegation/delegation.module.scss | 4 +- src/pages/proposals/proposals.module.scss | 5 +- src/styles/fonts.module.scss | 18 ++ src/utils/image-list.ts | 3 +- 27 files changed, 737 insertions(+), 235 deletions(-) create mode 100644 public/exclamation-triangle-fill.svg delete mode 100644 public/unsupported-network.svg create mode 100644 src/components/button/variants/tertiary-color.module.scss create mode 100644 src/components/button/variants/text-blue.module.scss create mode 100644 src/components/icons/close.tsx create mode 100644 src/components/icons/help-outline.tsx create mode 100644 src/components/icons/index.ts diff --git a/cypress/e2e/keyboard-navigation.cy.ts b/cypress/e2e/keyboard-navigation.cy.ts index d2a5d4bb..96fe85ed 100644 --- a/cypress/e2e/keyboard-navigation.cy.ts +++ b/cypress/e2e/keyboard-navigation.cy.ts @@ -44,11 +44,11 @@ describe('keyboard navigation and accessibility', () => { pressTabAndAssertFocusOutline(() => cy.findByText('Max')); pressTabAndAssertFocusOutline(() => cy.findByText('Approve')); - pressTabAndAssertFocusOutline(() => cy.get('#modal').find('img')); // Close icon + pressTabAndAssertFocusOutline(() => cy.get('#modal').findByTestId('modal-close-button')); // Focus is returned to the modal input (and it's text is selected) cy.tab().get('#modal').find('input').should('have.focus'); - cy.get('#modal').find('img').click(); // Close the modal + cy.get('#modal').findByTestId('modal-close-button').click(); }); it('can use keyboard to "press" the buttons', () => { diff --git a/cypress/e2e/staking.cy.ts b/cypress/e2e/staking.cy.ts index df378ff6..06fb96af 100644 --- a/cypress/e2e/staking.cy.ts +++ b/cypress/e2e/staking.cy.ts @@ -48,7 +48,7 @@ describe('staking', () => { cy.findByText('Initiate Unstake').click(); cy.get('#modal').find('input').type('20'); cy.findByText('Initiate Unstaking').click(); - cy.findByText('Initiate Unstaking').click(); // confirm the unstake in the second modal + cy.findByText('Yes, Initiate Unstaking').click(); // confirm the unstake in the second modal cy.findByText('Pending API3 tokens unstaking').should('exist'); // Assert balances cy.dataCy('balance').should('have.text', '480.0'); @@ -96,8 +96,8 @@ it.skip('user can unstake & withdraw', () => { // Schedule unstake cy.findByText('Initiate Unstake').click(); cy.get('#modal').find('input').type('550'); - cy.findByText('Initiate Unstaking').click(); - cy.findByText('Initiate Unstaking').click(); // confirm the unstake in the second modal + cy.findByText('Yes, Initiate Unstaking').click(); + cy.findByText('Yes, Initiate Unstaking').click(); // confirm the unstake in the second modal cy.findByText('Pending API3 tokens unstaking').should('exist'); // Assert balances cy.dataCy('balance').should('have.text', '450.0'); diff --git a/public/exclamation-triangle-fill.svg b/public/exclamation-triangle-fill.svg new file mode 100644 index 00000000..16c5fd63 --- /dev/null +++ b/public/exclamation-triangle-fill.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/public/unsupported-network.svg b/public/unsupported-network.svg deleted file mode 100644 index 0b0449d9..00000000 --- a/public/unsupported-network.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/button/button.module.scss b/src/components/button/button.module.scss index 83f58c36..d9a8cd08 100644 --- a/src/components/button/button.module.scss +++ b/src/components/button/button.module.scss @@ -3,26 +3,25 @@ @import './variants/primary.module.scss'; @import './variants/secondary.module.scss'; @import './variants/secondary-neutral.module.scss'; +@import './variants/tertiary-color.module.scss'; @import './variants/link-blue.module.scss'; @import './variants/menu-link-secondary.module.scss'; - -.buttonWrapper { - display: inline-block; - &.disabled { - pointer-events: none; - } -} +@import './variants/text-blue.module.scss'; .button { background-color: transparent; text-decoration: none; - display: flex; + display: inline-flex; align-items: center; justify-content: center; border-width: 1px; border-style: solid; - outline: none; cursor: pointer; + height: fit-content; + + &.disabled { + pointer-events: none; + } &.primary { @include button-primary; @@ -36,6 +35,10 @@ @include button-secondary-neutral; } + &.tertiary-color { + @include button-tertiary-color; + } + &.link-blue { @include link-blue; } @@ -44,6 +47,10 @@ @include menu-link-secondary; } + &.text-blue { + @include text-blue; + } + &.normal { font-size: $text-small; line-height: $lh-small; diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index e5551f5c..1d4d5883 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -10,7 +10,16 @@ type BreakpointsProps = { [key in BreakpointKeys]?: { size?: Size } }; export interface Props extends BreakpointsProps { children: ReactNode; className?: string; - type?: 'primary' | 'secondary' | 'secondary-neutral' | 'link' | 'text' | 'menu-link-secondary' | 'link-blue'; + type?: + | 'primary' + | 'secondary' + | 'secondary-neutral' + | 'tertiary-color' + | 'link' + | 'text' + | 'text-blue' + | 'menu-link-secondary' + | 'link-blue'; size?: Size; disabled?: boolean; href?: string; @@ -37,28 +46,38 @@ const Button = ({ const { width } = useWindowDimensions(); const sizeClass = getSizeClass(width, size, { xs, sm, md, lg }); - return ( -
- {href ? ( - - {children} - - ) : ( - + return href ? ( + + {...(isExternal(href) ? { target: '_blank', rel: 'noopener noreferrer' } : {})} + {...rest} + > + {children} + + ) : ( + ); }; diff --git a/src/components/button/variants/tertiary-color.module.scss b/src/components/button/variants/tertiary-color.module.scss new file mode 100644 index 00000000..46ae9269 --- /dev/null +++ b/src/components/button/variants/tertiary-color.module.scss @@ -0,0 +1,76 @@ +@mixin button-tertiary-color { + position: relative; + color: $color-blue-500; + border: 1px solid $color-blue-500; + background: $color-base-light; + + &.xxs { + padding: 0 10px; + border-radius: 24px; + height: 24px; + @include font-tagline-10; + } + + &.xs { + padding: 0 16px; + border-radius: 24px; + height: 32px; + @include font-tagline-10; + } + + &.sm { + padding: 0 20px; + border-radius: 24px; + height: 40px; + @include font-tagline-10; + } + + &.md { + padding: 0 24px; + border-radius: 28px; + height: 48px; + @include font-tagline-7; + } + + &.lg { + padding: 0 32px; + border-radius: 32px; + height: 56px; + @include font-tagline-7; + } + + &:hover { + color: $color-blue-200; + border: 1px solid $color-blue-200; + } + + &:active { + color: $color-blue-600; + border: 1px solid $color-blue-600; + } + + &:disabled { + color: $color-blue-25; + border: 1px solid $color-blue-25; + } + + &.dark { + color: $color-blue-400; + border: $color-blue-400; + + &:hover { + color: $color-blue-200; + border: $color-blue-200; + } + + &:active { + color: $color-blue-500; + border: $color-blue-500; + } + + &:disabled { + color: $color-blue-100; + border: $color-blue-100; + } + } +} diff --git a/src/components/button/variants/text-blue.module.scss b/src/components/button/variants/text-blue.module.scss new file mode 100644 index 00000000..215cb35c --- /dev/null +++ b/src/components/button/variants/text-blue.module.scss @@ -0,0 +1,63 @@ +@import '../../../styles/fonts.module.scss'; + +@mixin text-blue { + color: $color-blue-500; + height: 20px; + border: none; + + &.xs { + padding: 0 16px; + border-radius: 24px; + height: 32px; + @include font-body-14; + } + + &.sm { + padding: 0 20px; + border-radius: 24px; + height: 40px; + @include font-button-3; + } + + &.md { + padding: 0 24px; + border-radius: 28px; + height: 48px; + @include font-button-2; + } + + &.lg { + padding: 0 32px; + border-radius: 32px; + height: 56px; + @include font-button-1; + } + + &:hover { + color: $color-blue-200; + } + + &:active { + color: $color-blue-600; + } + + &:disabled { + color: $color-blue-50; + } + + &.dark { + color: $color-blue-100; + + &:hover { + color: $color-blue-200; + } + + &:active { + color: $color-blue-500; + } + + &:disabled { + color: $color-dark-blue-100; + } + } +} diff --git a/src/components/icons/close.tsx b/src/components/icons/close.tsx new file mode 100644 index 00000000..2a5207d4 --- /dev/null +++ b/src/components/icons/close.tsx @@ -0,0 +1,22 @@ +import { ComponentProps } from 'react'; + +export const CloseIcon = (props: ComponentProps<'svg'>) => { + return ( + + + + + ); +}; diff --git a/src/components/icons/help-outline.tsx b/src/components/icons/help-outline.tsx new file mode 100644 index 00000000..ac4de705 --- /dev/null +++ b/src/components/icons/help-outline.tsx @@ -0,0 +1,16 @@ +import { ComponentProps } from 'react'; + +export const HelpOutlineIcon = (props: ComponentProps<'svg'>) => { + return ( + + + + + ); +}; diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts new file mode 100644 index 00000000..bc6cd31d --- /dev/null +++ b/src/components/icons/index.ts @@ -0,0 +1,4 @@ +import { CloseIcon } from './close'; +import { HelpOutlineIcon } from './help-outline'; + +export { CloseIcon, HelpOutlineIcon }; diff --git a/src/components/input/input.module.scss b/src/components/input/input.module.scss index 33ee1de9..106cc9a2 100644 --- a/src/components/input/input.module.scss +++ b/src/components/input/input.module.scss @@ -1,8 +1,10 @@ @import '../../styles/variables.module.scss'; +@import '../../styles/fonts.module.scss'; .inputWrapper { display: inline-block; max-width: 100%; + &.disabled { opacity: 0.4; pointer-events: none; @@ -16,62 +18,67 @@ } } +@mixin normal { + @include font-heading-7; +} + +@mixin large { + @include font-heading-4; +} + .input { display: inline-block; max-width: 100%; - overflow: hidden; + &.textCenter { input { text-align: center; } } + input { - min-width: 50px; + min-width: 30px; background-color: transparent; - color: $secondary-color; - font-weight: 500; + color: $color-dark-blue-400; border-width: 0; outline: none; &:focus { - box-shadow: none; + color: $color-dark-blue-400; } - } - input { - &:focus { - color: $primary-color; - } &::placeholder { - color: $tertiary-color; + color: $color-dark-blue-400; } } &.normal { input { - font-size: $text-small; - line-height: 24px; - } - & > div { - font-size: $text-small !important; + @include normal; } } &.large { input { - font-size: $text-xlarge; - line-height: 44px; + @include large; } - & > div { - font-size: $text-xlarge !important; + } + + &:not(.normal):not(.large) { + input { + @include normal; } } -} -.inputUnderline { - height: 1px; - width: 100%; - background-color: $secondary-border; + @media (min-width: $sm) { + min-width: 50px; + + &:not(.normal):not(.large) { + input { + @include large; + } + } + } } // removing input background color/text color for Chrome autocomplete diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx index 488db02c..f11f4037 100644 --- a/src/components/input/input.tsx +++ b/src/components/input/input.tsx @@ -18,15 +18,7 @@ type Props = { allowNegative?: boolean; }; -const Input = ({ - size = 'normal', - type = 'text', - block, - autosize, - disabled, - allowNegative, - ...componentProps -}: Props) => { +const Input = ({ size, type = 'text', block, autosize, disabled, allowNegative, ...componentProps }: Props) => { const CustomNumberInput = useCallback( (props: any) => { return ( @@ -80,12 +72,13 @@ const Input = ({ customInput={AutosizeInput} allowNegative={allowNegative || false} decimalScale={18} + placeholderIsMinWidth + placeholder="00" /> )} {type === 'number' && !autosize && ( )} -
); }; diff --git a/src/components/layout/layout.module.scss b/src/components/layout/layout.module.scss index ebdfe8a2..ed6fb7e1 100644 --- a/src/components/layout/layout.module.scss +++ b/src/components/layout/layout.module.scss @@ -47,17 +47,18 @@ display: flex; align-items: center; text-decoration: none; + box-sizing: border-box; @include font-overline-2; @include menu-link-secondary; } - & > *, - & > a { - border-right: 1px solid $color-dark-blue-10; - padding: 0 8px; + > a, + > button { + border-right: 1px solid $color-dark-blue-10 !important; + padding: 0 8px !important; &:last-child { - border-right: none; + border-right: none !important; } } diff --git a/src/components/modal/modal.module.scss b/src/components/modal/modal.module.scss index fc3745d3..95daa544 100644 --- a/src/components/modal/modal.module.scss +++ b/src/components/modal/modal.module.scss @@ -1,84 +1,181 @@ @import '../../styles/variables.module.scss'; +@import '../../styles/fonts.module.scss'; .modalWrapper { - width: 100%; - height: 100%; position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; z-index: 100; - box-sizing: border-box; - outline: none; overflow-y: auto; overflow-x: hidden; - background-color: rgba($black-color, 0.9); - padding: $space-xxl; + background-color: rgba($color-base-light, 0.9); + display: flex; + align-items: center; + justify-content: center; - @media (max-width: $max-md) { - padding: $space-lg; + @media (min-width: $sm) { + padding: 40px; } } -.modalBody { +.modal { position: relative; width: 100%; min-height: 100%; display: flex; + flex-direction: column; align-items: center; - margin: 0 auto; + gap: 40px; + margin: auto; box-sizing: border-box; + background: $gradient-light; - &.small { - max-width: 380px; + &Normal { + padding: 40px 24px; } - &.medium { - max-width: 550px; + &Large { + padding: 24px; } - &.large { - max-width: 920px; + @media (min-width: $sm) { + @include gradient-border($gradient-base-blue-01); + border-radius: 16px; + min-height: unset; + width: auto; + + &Normal { + padding: 120px 144px; + } + + &Large { + padding: 120px 76px; + width: 100%; + } + } + + @media (min-width: $md) { + &Large { + width: auto; + } } } -.modal { - background-color: $black-color; - position: relative; - overflow: visible; - max-height: 100%; +.closeButtonWrapper { + display: flex; + align-items: center; + justify-content: flex-end; width: 100%; - border: 1px solid; - border-image-source: $primary-border; - border-image-slice: 1; - color: $secondary-color; - padding: 2 * $space-xxl; - box-sizing: border-box; - @media (max-width: $max-md) { - padding: 2 * $space-xxl $space-lg; + > button { + cursor: pointer; + margin-left: auto; + background: none; + border: none; + margin: 0; + padding: 0; + height: 40px; + color: $color-dark-blue-400; + transition: all 0.3s; + + &:hover { + color: $color-dark-blue-100; + } + } + + @media (min-width: $sm) { + position: absolute; + width: auto; + top: 32px; + right: 32px; } } -.closeButton { - position: absolute; - top: $space-xxl; - right: $space-xxl; - cursor: pointer; - transition: transform 0.35s; +.modalHeader { + position: relative; + display: flex; + text-align: center; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + color: $color-dark-blue-400; + margin-bottom: 48px; + + h5 { + text-wrap: pretty; + } - &:hover { - transform: scale(1.2); + &Normal { + h5 { + @include font-heading-9; + } } - .hidden { - display: none; + &Large { + justify-content: space-between; + + h5 { + @include font-heading-8; + } + + button { + display: flex; + gap: 4px; + height: auto; + + svg { + width: 17px; + height: 17px; + } + } } - @media (max-width: $max-md) { - top: $space-xl; - right: $space-xl; + @media (min-width: $sm) { + &Normal { + h5 { + @include font-heading-6; + } + } + + &Large { + justify-content: center; + padding-left: 64px; + + h5 { + @include font-heading-6; + margin: 0 auto; + } + + button { + gap: 6px; + + svg { + width: 22px; + height: 22px; + } + } + } + } +} + +.modalContent { + position: relative; + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + + @media (min-width: $sm) { + &Normal { + width: 510px; + } + } + + @media (min-width: $md) { + &Large { + width: 768px; + } } } @@ -86,11 +183,18 @@ display: flex; align-items: center; justify-content: center; - margin-top: 2 * $space-xl; + margin-top: 48px; flex-direction: column; + width: 100%; + padding: 0 24px; + box-sizing: border-box; + max-width: 510px; + + @media (min-width: $sm) { + padding: 0; + } } -.modalHeader { - margin-bottom: $space-xxxl; - text-align: center; +.noMargin { + margin: 0; } diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index 5c71f3d6..376da7fa 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -3,20 +3,24 @@ import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; import { useOnAccountOrNetworkChange } from '../../contracts'; -import { images } from '../../utils'; import styles from './modal.module.scss'; +import { CloseIcon, HelpOutlineIcon } from '../icons'; +import Button from '../button'; +import { Tooltip } from '../tooltip'; -interface ModalProps { +interface ModalSize { + size?: 'normal' | 'large'; +} +interface ModalProps extends ModalSize { children?: React.ReactNode; open: boolean; onClose: () => void; hideCloseButton?: true; - size?: 'small' | 'medium' | 'large'; closeOnAccountChange?: false; } export const ModalContent = (props: ModalProps) => { - const { onClose, hideCloseButton = false, children, size = 'medium' } = props; + const { onClose, hideCloseButton = false, children, size = 'normal' } = props; useEffect(() => { const handler = (e: KeyboardEvent) => { @@ -32,23 +36,28 @@ export const ModalContent = (props: ModalProps) => { return ( -
+