diff --git a/cypress/e2e/delegation.cy.ts b/cypress/e2e/delegation.cy.ts index 8c50be7d..13750e6c 100644 --- a/cypress/e2e/delegation.cy.ts +++ b/cypress/e2e/delegation.cy.ts @@ -5,7 +5,7 @@ describe('delegation', () => { cy.resetBlockchain().login(); // Approve, deposit and stake - cy.findByText('+ Deposit').click(); + cy.findByTestId('staking-card-deposit-btn').click(); cy.get('#modal').find('input').type('200'); cy.findByText('Approve').click(); cy.findByText('Deposit and Stake').click(); diff --git a/cypress/e2e/keyboard-navigation.cy.ts b/cypress/e2e/keyboard-navigation.cy.ts index dd803795..d2a5d4bb 100644 --- a/cypress/e2e/keyboard-navigation.cy.ts +++ b/cypress/e2e/keyboard-navigation.cy.ts @@ -23,6 +23,7 @@ describe('keyboard navigation and accessibility', () => { pressTabAndAssertFocusOutline(() => cy.findAllByText('Api3 Market').filter(':visible').closest('a')); pressTabAndAssertFocusOutline(() => cy.findAllByText('Docs').filter(':visible').closest('a')); + pressTabAndAssertFocusOutline(() => cy.findByTestId('connect-wallet-staking-btn')); pressTabAndAssertFocusOutline(() => cy.findByText('About API3')); pressTabAndAssertFocusOutline(() => cy.findByText('Error Reporting')); pressTabAndAssertFocusOutline(() => cy.findByText('Github')); @@ -36,7 +37,7 @@ describe('keyboard navigation and accessibility', () => { }); it('uses focus lock (cannot tab outside modal)', () => { - cy.findByText('+ Deposit').click(); + cy.findByText('Deposit').click(); cy.get('#modal').find('input').should('have.focus'); cy.get('#modal').find('input').type('123'); @@ -52,12 +53,12 @@ describe('keyboard navigation and accessibility', () => { it('can use keyboard to "press" the buttons', () => { // Can close the modal by pressing ESC - cy.findByText('+ Deposit').click(); + cy.findByText('Deposit').click(); cy.get('body').type('{esc}'); cy.get('#modal').find('input').should('not.exist'); // Can deposit by pressing ENTER when button has focus - cy.findByText('+ Deposit').click(); + cy.findByText('Deposit').click(); cy.get('#modal').find('input').type('123').tab(); // Tab over "Max" button pressTabAndAssertFocusOutline(() => cy.findByText('Approve')); diff --git a/cypress/e2e/staking.cy.ts b/cypress/e2e/staking.cy.ts index 81244aea..df378ff6 100644 --- a/cypress/e2e/staking.cy.ts +++ b/cypress/e2e/staking.cy.ts @@ -7,18 +7,19 @@ describe('staking', () => { cy.resetBlockchain().login(); // Approve and deposit - cy.findByText('+ Deposit').click(); + cy.findByTestId('staking-card-deposit-btn').click(); cy.get('#modal').find('input').type('500'); cy.findByText('Approve').click(); cy.findByText('Deposit').should('be.visible'); + cy.get('#modal').findByRole('button', { name: 'Deposit' }).should('be.visible'); cy.percySnapshot('Staking: Deposit modal'); - cy.findByText('Deposit').click(); + cy.get('#modal').findByRole('button', { name: 'Deposit' }).click(); cy.dataCy('balance').should('have.text', '500.0'); // Stake - cy.findByText('+ Stake').click(); + cy.findByTestId('staking-card-stake-btn').click(); cy.get('#modal').find('input').type('100'); - cy.findByText('Stake').click(); + cy.get('#modal').findByRole('button', { name: 'Stake' }).click(); cy.dataCy('unstaked').should('have.text', '400.0'); cy.dataCy('withdrawable').should('have.text', '400.0'); cy.dataCy('staked').should('have.text', '100.0'); @@ -31,7 +32,7 @@ describe('staking', () => { it('can deposit and stake', () => { cy.useChainSnapshot('user-staked'); - cy.findByText('+ Deposit').click(); + cy.findByTestId('staking-card-deposit-btn').click(); cy.get('#modal').find('input').type('200'); cy.findByText('Deposit and Stake').click(); @@ -86,7 +87,7 @@ it.skip('user can unstake & withdraw', () => { cy.resetBlockchain().login(); // Approve and deposit - cy.findByText('+ Deposit').click(); + cy.findByTestId('staking-card-deposit-btn').click(); cy.get('#modal').find('input').type('1000'); cy.findByText('Approve').click(); cy.findByText('Deposit and Stake').click(); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 2b342500..0ed113ea 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -65,7 +65,7 @@ Cypress.Commands.add('login', () => { }); // Login - cy.findByRole('button', { name: 'Connect Wallet' }).click(); + cy.findAllByRole('button', { name: 'Connect Wallet' }).first().click(); // Web3 Modal cy.get('w3m-modal') diff --git a/public/help-outline.svg b/public/help-outline.svg new file mode 100644 index 00000000..e5f5e183 --- /dev/null +++ b/public/help-outline.svg @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/public/info-circle.svg b/public/info-circle.svg new file mode 100644 index 00000000..b06119e2 --- /dev/null +++ b/public/info-circle.svg @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/src/components/button/button.module.scss b/src/components/button/button.module.scss index 285286e2..83f58c36 100644 --- a/src/components/button/button.module.scss +++ b/src/components/button/button.module.scss @@ -1,6 +1,10 @@ @import '../../styles/variables.module.scss'; @import '../../styles/fonts.module.scss'; -@import '../../styles/link-styles.module.scss'; +@import './variants/primary.module.scss'; +@import './variants/secondary.module.scss'; +@import './variants/secondary-neutral.module.scss'; +@import './variants/link-blue.module.scss'; +@import './variants/menu-link-secondary.module.scss'; .buttonWrapper { display: inline-block; @@ -21,139 +25,15 @@ cursor: pointer; &.primary { - background-color: $color-blue-500; - color: $color-base-light; - border: none; - - &.xxs { - padding: 0 10px; - border-radius: 24px; - height: 24px; - @include font-body-17; - } - - &.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 { - background-color: $color-blue-200; - } - - &:active { - background-color: $color-blue-600; - } - - &:disabled { - color: $color-blue-50; - background-color: $color-blue-10; - } - - &.dark { - background-color: $color-blue-400; - - &:hover { - background-color: $color-blue-200; - } - - &:active { - background-color: $color-blue-500; - } - - &:disabled { - color: $color-blue-400; - background-color: $color-dark-blue-400; - } - } + @include button-primary; } &.secondary { - position: relative; - color: $color-dark-blue-400; - border: 1px solid transparent; - @include gradient-border($gradient-soft-light); - - &.xxs { - padding: 0 10px; - border-radius: 24px; - height: 24px; - @include font-body-17; - } - - &.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, - &:active, - &:disabled { - &:before { - padding: 0px; - } - } - - &:hover { - color: $color-blue-200; - border: 1px solid $color-blue-200; - } - - &:active { - color: $color-blue-600; - border: 1px solid $color-blue-600; - } + @include button-secondary; + } - &:disabled { - color: $color-gray-400; - border: 1px solid $color-gray-400; - } + &.secondary-neutral { + @include button-secondary-neutral; } &.link-blue { diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index 7d013f21..e5551f5c 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -10,12 +10,13 @@ type BreakpointsProps = { [key in BreakpointKeys]?: { size?: Size } }; export interface Props extends BreakpointsProps { children: ReactNode; className?: string; - type?: 'primary' | 'secondary' | 'link' | 'text' | 'menu-link-secondary' | 'link-blue'; + type?: 'primary' | 'secondary' | 'secondary-neutral' | 'link' | 'text' | 'menu-link-secondary' | 'link-blue'; size?: Size; disabled?: boolean; href?: string; theme?: 'light' | 'dark'; onClick?: () => void; + 'data-testid'?: string; } const Button = ({ @@ -31,6 +32,7 @@ const Button = ({ sm, md, lg, + ...rest }: Props) => { const { width } = useWindowDimensions(); const sizeClass = getSizeClass(width, size, { xs, sm, md, lg }); @@ -42,6 +44,7 @@ const Button = ({ href={href} className={classNames(styles.button, styles[type], styles[theme], styles[sizeClass])} {...(isExternal(href) ? { target: '_blank', rel: 'noopener noreferrer' } : {})} + {...rest} > {children} @@ -50,6 +53,7 @@ const Button = ({ className={classNames(styles.button, styles[type], styles[theme], styles[sizeClass])} onClick={onClick} disabled={disabled} + {...rest} > {children} diff --git a/src/styles/link-styles.module.scss b/src/components/button/variants/link-blue.module.scss similarity index 55% rename from src/styles/link-styles.module.scss rename to src/components/button/variants/link-blue.module.scss index b46afed5..ed15d792 100644 --- a/src/styles/link-styles.module.scss +++ b/src/components/button/variants/link-blue.module.scss @@ -1,4 +1,4 @@ -@import './fonts.module.scss'; +@import '../../../styles/fonts.module.scss'; @mixin link-blue { color: $color-blue-500; @@ -50,40 +50,3 @@ } } } - -@mixin menu-link-secondary { - position: relative; - border: none; - color: $color-dark-blue-400; - height: 20px; - padding: 0; - - &.xs { - @include font-overline-2; - } - - &.sm { - @include font-body-15; - } - - &.md { - @include font-body-12; - } - - &.lg { - height: 24px; - @include font-description-6; - } - - &:hover { - color: $color-blue-400; - } - - &:active { - color: $color-blue-300; - } - - &:disabled { - color: $color-dark-blue-25; - } -} diff --git a/src/components/button/variants/menu-link-secondary.module.scss b/src/components/button/variants/menu-link-secondary.module.scss new file mode 100644 index 00000000..c5e76019 --- /dev/null +++ b/src/components/button/variants/menu-link-secondary.module.scss @@ -0,0 +1,38 @@ +@import '../../../styles/fonts.module.scss'; + +@mixin menu-link-secondary { + position: relative; + border: none; + color: $color-dark-blue-400; + height: 20px; + padding: 0; + + &.xs { + @include font-overline-2; + } + + &.sm { + @include font-body-15; + } + + &.md { + @include font-body-12; + } + + &.lg { + height: 24px; + @include font-description-6; + } + + &:hover { + color: $color-blue-400; + } + + &:active { + color: $color-blue-300; + } + + &:disabled { + color: $color-dark-blue-25; + } +} diff --git a/src/components/button/variants/primary.module.scss b/src/components/button/variants/primary.module.scss new file mode 100644 index 00000000..47aec2da --- /dev/null +++ b/src/components/button/variants/primary.module.scss @@ -0,0 +1,70 @@ +@mixin button-primary { + background-color: $color-blue-500; + color: $color-base-light; + border: none; + + &.xxs { + padding: 0 10px; + border-radius: 24px; + height: 24px; + @include font-body-17; + } + + &.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 { + background-color: $color-blue-200; + } + + &:active { + background-color: $color-blue-600; + } + + &:disabled { + color: $color-blue-50; + background-color: $color-blue-10; + } + + &.dark { + background-color: $color-blue-400; + + &:hover { + background-color: $color-blue-200; + } + + &:active { + background-color: $color-blue-500; + } + + &:disabled { + color: $color-blue-400; + background-color: $color-dark-blue-400; + } + } +} diff --git a/src/components/button/variants/secondary-neutral.module.scss b/src/components/button/variants/secondary-neutral.module.scss new file mode 100644 index 00000000..ccff7fdc --- /dev/null +++ b/src/components/button/variants/secondary-neutral.module.scss @@ -0,0 +1,56 @@ +@mixin button-secondary-neutral { + 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-body-17; + } + + &.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; + border: 1px solid $color-blue-200; + } + + &:active { + color: $color-blue-600; + border: 1px solid $color-blue-600; + } + + &:disabled { + color: $color-gray-400; + border: 1px solid $color-blue-50; + } +} diff --git a/src/components/button/variants/secondary.module.scss b/src/components/button/variants/secondary.module.scss new file mode 100644 index 00000000..2bb7301b --- /dev/null +++ b/src/components/button/variants/secondary.module.scss @@ -0,0 +1,64 @@ +@mixin button-secondary { + position: relative; + color: $color-dark-blue-400; + border: 1px solid transparent; + @include gradient-border($gradient-soft-light); + + &.xxs { + padding: 0 10px; + border-radius: 24px; + height: 24px; + @include font-body-17; + } + + &.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, + &:active, + &:disabled { + &:before { + padding: 0px; + } + } + + &: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-gray-400; + border: 1px solid $color-gray-400; + } +} diff --git a/src/components/card/card.module.scss b/src/components/card/card.module.scss new file mode 100644 index 00000000..ca4f5ac2 --- /dev/null +++ b/src/components/card/card.module.scss @@ -0,0 +1,44 @@ +@import '../../styles/variables.module.scss'; +@import '../../styles/fonts.module.scss'; + +.card { + position: relative; + border-radius: 20px; + border: 1px solid $color-blue-25; + background: $gradient-light; + box-shadow: 0px 4px 4px 0px rgba(190, 210, 255, 0.2); + padding: $space-lg; + padding-bottom: $space-xl; +} + +.cardHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-left: $space-md; + margin-bottom: $space-xxl; + + > h5 { + @include font-heading-9; + } + + @media (min-width: $md) { + > h5 { + @include font-heading-7; + } + } +} + +.contentFooter { + margin-top: $space-xl; + text-align: center; +} + +.cardChildren { + margin-top: $space-lg; +} + +.gradientBorder { + border: none; + @include gradient-border($gradient-base-blue-01); +} diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx new file mode 100644 index 00000000..1fe0cdb6 --- /dev/null +++ b/src/components/card/card.tsx @@ -0,0 +1,34 @@ +import { ReactNode } from 'react'; +import styles from './card.module.scss'; +import classNames from 'classnames'; + +type Props = { + header: ReactNode; + content: ReactNode; + contentFooter?: ReactNode; + children?: ReactNode; + gradientBorder?: boolean; +}; + +type HeaderProps = { + children: ReactNode; +}; + +export const Header = ({ children }: HeaderProps) => { + return
How This Works
Staking Pool
+ + {/* Staking Pool */} +Staking Pool
Pending API3 tokens unstaking
- - } - content={ -Amount
-Cooldown
-{timerDays}
-D
-{timerHours}
-HR
-{timerMinutes}
-MIN
-{timerSeconds}
-SEC
-Pending API3 tokens unstaking
+Amount
+Cooldown
+{timerDays}
+D
+{timerHours}
+HR
+{timerMinutes}
+MIN
+{timerSeconds}
+SEC
{currentApyText}
+{currentApyText}
Annual Rewards (APY)
+Annual Rewards (APY)
Annual Total Supply Growth
+Annual Total Supply Growth
total staked
-{totalStaked}
+total staked
+{totalStaked}
staking target
-{stakingTargetInTokens}
+staking target
+{stakingTargetInTokens}