diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-09-26 18:43:11 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-10-24 12:25:00 +0200 |
| commit | 388e687857345c85ee550cd5da472675e05a6ff5 (patch) | |
| tree | 0f035a3cad57a75959c028949a57227a83d480e2 /src/components/atoms | |
| parent | 70efcfeaa0603415dd992cb662d8efb960e6e49a (diff) | |
refactor(components): rewrite Button and ButtonLink components
Both:
* move styles to Sass placeholders
Button:
* add `isPressed` prop to Button
* add `isLoading` prop to Button (to differentiate state from
disabled)
ButtonLink:
* replace `external` prop with `isExternal` prop
* replace `href` prop with `to` prop
Diffstat (limited to 'src/components/atoms')
14 files changed, 478 insertions, 362 deletions
diff --git a/src/components/atoms/buttons/button-link.test.tsx b/src/components/atoms/buttons/button-link.test.tsx deleted file mode 100644 index 8491101..0000000 --- a/src/components/atoms/buttons/button-link.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { ButtonLink } from './button-link'; - -describe('ButtonLink', () => { - it('renders a ButtonLink component', () => { - render(<ButtonLink target="#">Button Link</ButtonLink>); - expect(screen.getByRole('link')).toHaveTextContent('Button Link'); - }); -}); diff --git a/src/components/atoms/buttons/button-link.tsx b/src/components/atoms/buttons/button-link.tsx deleted file mode 100644 index c8180c9..0000000 --- a/src/components/atoms/buttons/button-link.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Link from 'next/link'; -import { AnchorHTMLAttributes, FC, ReactNode } from 'react'; -import styles from './buttons.module.scss'; - -export type ButtonLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & { - /** - * The button link body. - */ - children: ReactNode; - /** - * True if it is an external link. Default: false. - */ - external?: boolean; - /** - * ButtonLink kind. Default: secondary. - */ - kind?: 'primary' | 'secondary' | 'tertiary'; - /** - * ButtonLink shape. Default: rectangle. - */ - shape?: 'circle' | 'rectangle' | 'square'; - /** - * Define an URL as target. - */ - target: string; -}; - -/** - * ButtonLink component - * - * Use a button-like link as call to action. - */ -export const ButtonLink: FC<ButtonLinkProps> = ({ - children, - className, - target, - kind = 'secondary', - shape = 'rectangle', - external = false, - ...props -}) => { - const kindClass = styles[`btn--${kind}`]; - const shapeClass = styles[`btn--${shape}`]; - const btnClass = `${styles.btn} ${kindClass} ${shapeClass} ${className}`; - - return external ? ( - <a {...props} className={btnClass} href={target}> - {children} - </a> - ) : ( - <Link {...props} className={btnClass} href={target}> - {children} - </Link> - ); -}; diff --git a/src/components/atoms/buttons/button-link/button-link.module.scss b/src/components/atoms/buttons/button-link/button-link.module.scss new file mode 100644 index 0000000..0f35a24 --- /dev/null +++ b/src/components/atoms/buttons/button-link/button-link.module.scss @@ -0,0 +1,29 @@ +@use "../../../../styles/abstracts/placeholders"; + +.btn { + @extend %button; + + &--circle { + @extend %circle-button; + } + + &--rectangle { + @extend %rectangle-button; + } + + &--square { + @extend %square-button; + } + + &--primary { + @extend %primary-button; + } + + &--secondary { + @extend %secondary-button; + } + + &--tertiary { + @extend %tertiary-button; + } +} diff --git a/src/components/atoms/buttons/button-link.stories.tsx b/src/components/atoms/buttons/button-link/button-link.stories.tsx index 32c2a7f..f048ce9 100644 --- a/src/components/atoms/buttons/button-link.stories.tsx +++ b/src/components/atoms/buttons/button-link/button-link.stories.tsx @@ -1,4 +1,4 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { ButtonLink } from './button-link'; /** @@ -8,36 +8,10 @@ export default { title: 'Atoms/Buttons/ButtonLink', component: ButtonLink, args: { - external: false, + isExternal: false, shape: 'rectangle', }, argTypes: { - 'aria-label': { - control: { - type: 'text', - }, - description: 'An accessible label.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, - 'aria-labelledby': { - control: { - type: null, - }, - description: 'One or more ids that refer to an accessible label.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, children: { control: { type: 'text', @@ -48,20 +22,7 @@ export default { required: true, }, }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the button link.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - external: { + isExternal: { control: { type: 'boolean', }, @@ -95,7 +56,7 @@ export default { type: 'select', }, description: 'The link shape.', - options: ['rectangle', 'square'], + options: ['circle', 'rectangle', 'square'], table: { category: 'Options', defaultValue: { summary: 'rectangle' }, @@ -105,9 +66,9 @@ export default { required: false, }, }, - target: { + to: { control: { - type: null, + type: 'text', }, description: 'The link target.', type: { @@ -129,7 +90,7 @@ export const Primary = Template.bind({}); Primary.args = { children: 'Link', kind: 'primary', - target: '#', + to: '#', }; /** @@ -139,7 +100,7 @@ export const Secondary = Template.bind({}); Secondary.args = { children: 'Link', kind: 'secondary', - target: '#', + to: '#', }; /** @@ -149,5 +110,5 @@ export const Tertiary = Template.bind({}); Tertiary.args = { children: 'Link', kind: 'tertiary', - target: '#', + to: '#', }; diff --git a/src/components/atoms/buttons/button-link/button-link.test.tsx b/src/components/atoms/buttons/button-link/button-link.test.tsx new file mode 100644 index 0000000..d18120b --- /dev/null +++ b/src/components/atoms/buttons/button-link/button-link.test.tsx @@ -0,0 +1,129 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { ButtonLink } from './button-link'; + +describe('ButtonLink', () => { + it('renders a link with anchor and href', () => { + const target = 'eum'; + const body = 'est eaque nostrum'; + + render(<ButtonLink to={target}>{body}</ButtonLink>); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveAttribute( + 'href', + target + ); + }); + + it('renders an external link', () => { + const target = 'voluptatem'; + const body = 'impedit'; + + render( + <ButtonLink isExternal to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveAttribute( + 'rel', + expect.stringContaining('external') + ); + }); + + it('renders a primary button', () => { + const target = 'vero'; + const body = 'iure'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink kind="primary" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--primary' + ); + }); + + it('renders a secondary button', () => { + const target = 'voluptatem'; + const body = 'et'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink kind="secondary" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--secondary' + ); + }); + + it('renders a tertiary button', () => { + const target = 'vitae'; + const body = 'quo'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink kind="tertiary" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--tertiary' + ); + }); + + it('renders a circle button', () => { + const target = 'praesentium'; + const body = 'laudantium'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink shape="circle" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--circle' + ); + }); + + it('renders a rectangle button', () => { + const target = 'tempora'; + const body = 'ut'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink shape="rectangle" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--rectangle' + ); + }); + + it('renders a square button', () => { + const target = 'quia'; + const body = 'non'; + + render( + // eslint-disable-next-line react/jsx-no-literals -- Ignore kind. + <ButtonLink shape="square" to={target}> + {body} + </ButtonLink> + ); + + expect(rtlScreen.getByRole('link', { name: body })).toHaveClass( + 'btn--square' + ); + }); +}); diff --git a/src/components/atoms/buttons/button-link/button-link.tsx b/src/components/atoms/buttons/button-link/button-link.tsx new file mode 100644 index 0000000..f8bbadc --- /dev/null +++ b/src/components/atoms/buttons/button-link/button-link.tsx @@ -0,0 +1,67 @@ +import Link from 'next/link'; +import type { AnchorHTMLAttributes, FC, ReactNode } from 'react'; +import styles from './button-link.module.scss'; + +export type ButtonLinkProps = Omit< + AnchorHTMLAttributes<HTMLAnchorElement>, + 'href' +> & { + /** + * The button link body. + */ + children: ReactNode; + /** + * True if it is an external link. + * + * @default false + */ + isExternal?: boolean; + /** + * Define the button kind. + * + * @default 'secondary' + */ + kind?: 'primary' | 'secondary' | 'tertiary'; + /** + * Define the button shape. + * + * @default 'rectangle' + */ + shape?: 'circle' | 'rectangle' | 'square'; + /** + * Define an URL or anchor as target. + */ + to: string; +}; + +/** + * ButtonLink component + * + * Use a button-like link as call to action. + */ +export const ButtonLink: FC<ButtonLinkProps> = ({ + children, + className = '', + kind = 'secondary', + shape = 'rectangle', + isExternal = false, + rel = '', + to, + ...props +}) => { + const kindClass = styles[`btn--${kind}`]; + const shapeClass = styles[`btn--${shape}`]; + const btnClass = `${styles.btn} ${kindClass} ${shapeClass} ${className}`; + const linkRel = + isExternal && !rel.includes('external') ? `external ${rel}` : rel; + + return isExternal ? ( + <a {...props} className={btnClass} href={to} rel={linkRel}> + {children} + </a> + ) : ( + <Link {...props} className={btnClass} href={to} rel={rel}> + {children} + </Link> + ); +}; diff --git a/src/components/atoms/buttons/button-link/index.ts b/src/components/atoms/buttons/button-link/index.ts new file mode 100644 index 0000000..68d0a03 --- /dev/null +++ b/src/components/atoms/buttons/button-link/index.ts @@ -0,0 +1 @@ +export * from './button-link'; diff --git a/src/components/atoms/buttons/button.test.tsx b/src/components/atoms/buttons/button.test.tsx deleted file mode 100644 index b6bfc5d..0000000 --- a/src/components/atoms/buttons/button.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Button } from './button'; - -describe('Button', () => { - it('renders the Button component', () => { - render(<Button onClick={() => null}>Button</Button>); - expect(screen.getByRole('button')).toBeInTheDocument(); - }); - - it('renders the Button component with disabled state', () => { - render( - <Button onClick={() => null} disabled={true}> - Disabled Button - </Button> - ); - expect(screen.getByRole('button')).toBeDisabled(); - }); -}); diff --git a/src/components/atoms/buttons/button/button.module.scss b/src/components/atoms/buttons/button/button.module.scss new file mode 100644 index 0000000..508ff9a --- /dev/null +++ b/src/components/atoms/buttons/button/button.module.scss @@ -0,0 +1,37 @@ +@use "../../../../styles/abstracts/placeholders"; + +.btn { + @extend %button; + + &--initial { + border-radius: 0; + } + + &--circle { + @extend %circle-button; + } + + &--rectangle { + @extend %rectangle-button; + } + + &--square { + @extend %square-button; + } + + &--neutral { + background: inherit; + } + + &--primary { + @extend %primary-button; + } + + &--secondary { + @extend %secondary-button; + } + + &--tertiary { + @extend %tertiary-button; + } +} diff --git a/src/components/atoms/buttons/button.stories.tsx b/src/components/atoms/buttons/button/button.stories.tsx index ba09a0d..5ce28fb 100644 --- a/src/components/atoms/buttons/button.stories.tsx +++ b/src/components/atoms/buttons/button/button.stories.tsx @@ -1,4 +1,4 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { Button } from './button'; /** @@ -8,51 +8,53 @@ export default { title: 'Atoms/Buttons/Button', component: Button, args: { - disabled: false, type: 'button', }, argTypes: { - 'aria-label': { + children: { control: { type: 'text', }, - description: 'An accessible label.', - table: { - category: 'Accessibility', - }, + description: 'The button body.', type: { name: 'string', - required: false, + required: true, }, }, - children: { + isDisabled: { control: { - type: 'text', + type: 'boolean', + }, + description: 'Should the button be disabled?', + table: { + category: 'Options', + defaultValue: { summary: false }, }, - description: 'The button body.', type: { - name: 'string', - required: true, + name: 'boolean', + required: false, }, }, - className: { + isLoading: { control: { - type: 'text', + type: 'boolean', }, - description: 'Set additional classnames to the button wrapper.', + description: + 'Should the button be disabled because it is loading something?', table: { - category: 'Styles', + category: 'Options', + defaultValue: { summary: false }, }, type: { - name: 'string', + name: 'boolean', required: false, }, }, - disabled: { + isPressed: { control: { type: 'boolean', }, - description: 'Render button as disabled.', + description: 'Define if the button is currently pressed.', table: { category: 'Options', defaultValue: { summary: false }, @@ -123,28 +125,10 @@ export default { }, } as ComponentMeta<typeof Button>; -const Template: ComponentStory<typeof Button> = (args) => { - const { children, type, ...props } = args; - - const getBody = () => { - if (children) return children; +const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />; - switch (type) { - case 'reset': - return 'Reset'; - case 'submit': - return 'Submit'; - case 'button': - default: - return 'Button'; - } - }; - - return ( - <Button type={type} {...props}> - {getBody()} - </Button> - ); +const logClick = () => { + console.log('Button has been clicked!'); }; /** @@ -152,7 +136,9 @@ const Template: ComponentStory<typeof Button> = (args) => { */ export const Primary = Template.bind({}); Primary.args = { + children: 'Click on the button', kind: 'primary', + onClick: logClick, }; /** @@ -160,7 +146,9 @@ Primary.args = { */ export const Secondary = Template.bind({}); Secondary.args = { + children: 'Click on the button', kind: 'secondary', + onClick: logClick, }; /** @@ -168,5 +156,17 @@ Secondary.args = { */ export const Tertiary = Template.bind({}); Tertiary.args = { + children: 'Click on the button', kind: 'tertiary', + onClick: logClick, +}; + +/** + * Button Story - Neutral + */ +export const Neutral = Template.bind({}); +Neutral.args = { + children: 'Click on the button', + kind: 'neutral', + onClick: logClick, }; diff --git a/src/components/atoms/buttons/button/button.test.tsx b/src/components/atoms/buttons/button/button.test.tsx new file mode 100644 index 0000000..f7de1b3 --- /dev/null +++ b/src/components/atoms/buttons/button/button.test.tsx @@ -0,0 +1,133 @@ +/* eslint-disable max-statements */ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Button } from './button'; + +describe('Button', () => { + it('renders the button body', () => { + const body = 'aliquid'; + + render(<Button>{body}</Button>); + expect(rtlScreen.getByRole('button')).toHaveTextContent(body); + }); + + it('renders a disabled button', () => { + const body = 'quod'; + + render(<Button isDisabled>{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toBeDisabled(); + }); + + it('renders a button currently loading something', () => { + const body = 'quod'; + + render(<Button isLoading>{body}</Button>); + + expect(rtlScreen.getByRole('button', { busy: true })).toHaveAccessibleName( + body + ); + }); + + it('renders a pressed button', () => { + const body = 'quod'; + + render(<Button isPressed>{body}</Button>); + + expect( + rtlScreen.getByRole('button', { pressed: true }) + ).toHaveAccessibleName(body); + }); + + it('renders a submit button', () => { + const body = 'dolorum'; + + render(<Button type="submit">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveAttribute( + 'type', + 'submit' + ); + }); + + it('renders a reset button', () => { + const body = 'consectetur'; + + render(<Button type="reset">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveAttribute( + 'type', + 'reset' + ); + }); + + it('renders a primary button', () => { + const body = 'iure'; + + render(<Button kind="primary">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--primary' + ); + }); + + it('renders a secondary button', () => { + const body = 'et'; + + render(<Button kind="secondary">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--secondary' + ); + }); + + it('renders a tertiary button', () => { + const body = 'quo'; + + render(<Button kind="tertiary">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--tertiary' + ); + }); + + it('renders a neutral button', () => { + const body = 'voluptatem'; + + render(<Button kind="neutral">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--neutral' + ); + }); + + it('renders a circle button', () => { + const body = 'laudantium'; + + render(<Button shape="circle">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--circle' + ); + }); + + it('renders a rectangle button', () => { + const body = 'ut'; + + render(<Button shape="rectangle">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--rectangle' + ); + }); + + it('renders a square button', () => { + const body = 'non'; + + render(<Button shape="square">{body}</Button>); + + expect(rtlScreen.getByRole('button', { name: body })).toHaveClass( + 'btn--square' + ); + }); +}); diff --git a/src/components/atoms/buttons/button.tsx b/src/components/atoms/buttons/button/button.tsx index 6ef5775..8489b31 100644 --- a/src/components/atoms/buttons/button.tsx +++ b/src/components/atoms/buttons/button/button.tsx @@ -1,22 +1,37 @@ import { - ButtonHTMLAttributes, + type ButtonHTMLAttributes, forwardRef, - ForwardRefRenderFunction, - ReactNode, + type ForwardRefRenderFunction, + type ReactNode, } from 'react'; -import styles from './buttons.module.scss'; +import styles from './button.module.scss'; -export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { +export type ButtonProps = Omit< + ButtonHTMLAttributes<HTMLButtonElement>, + 'aria-busy' | 'aria-disabled' | 'aria-pressed' | 'aria-selected' | 'disabled' +> & { /** * The button body. */ children: ReactNode; /** - * Button state. + * Should the button be disabled? * - * @default false + * @default undefined */ - disabled?: boolean; + isDisabled?: boolean; + /** + * Is the button already executing some action? + * + * @default undefined + */ + isLoading?: boolean; + /** + * Is the button a toggle and is it currently pressed? + * + * @default undefined + */ + isPressed?: boolean; /** * Button kind. * @@ -44,7 +59,9 @@ const ButtonWithRef: ForwardRefRenderFunction< { className = '', children, - disabled = false, + isPressed, + isDisabled, + isLoading, kind = 'secondary', shape = 'rectangle', type = 'button', @@ -59,9 +76,13 @@ const ButtonWithRef: ForwardRefRenderFunction< return ( <button {...props} + aria-busy={isLoading} + aria-disabled={isDisabled} + aria-pressed={isPressed} className={btnClass} - disabled={disabled} + disabled={isDisabled ?? isLoading} ref={ref} + // eslint-disable-next-line react/button-has-type -- Default value is set. type={type} > {children} diff --git a/src/components/atoms/buttons/button/index.ts b/src/components/atoms/buttons/button/index.ts new file mode 100644 index 0000000..eaf5eea --- /dev/null +++ b/src/components/atoms/buttons/button/index.ts @@ -0,0 +1 @@ +export * from './button'; diff --git a/src/components/atoms/buttons/buttons.module.scss b/src/components/atoms/buttons/buttons.module.scss deleted file mode 100644 index a46f55c..0000000 --- a/src/components/atoms/buttons/buttons.module.scss +++ /dev/null @@ -1,179 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; - -.btn { - display: inline-flex; - place-content: center; - align-items: center; - border: none; - border-radius: fun.convert-px(5); - font-size: var(--font-size-md); - font-weight: 600; - text-decoration: none; - transition: all 0.3s ease-in-out 0s; - - &--initial { - border-radius: 0; - } - - &--rectangle { - padding: var(--spacing-2xs) var(--spacing-sm); - } - - &--square, - &--circle { - min-width: fit-content; - min-height: fit-content; - padding: var(--spacing-xs); - aspect-ratio: 1 / 1; - } - - &--circle { - border-radius: 50%; - } - - &:disabled { - cursor: wait; - } - - &--neutral { - background: inherit; - } - - &--primary { - background: var(--color-primary); - border: fun.convert-px(2) solid var(--color-bg); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(3) - var(--color-primary-dark); - color: var(--color-fg-inverted); - text-shadow: fun.convert-px(2) fun.convert-px(2) 0 var(--color-shadow); - - &:disabled { - background: var(--color-primary-darker); - } - - &:not(:disabled) { - &:hover, - &:focus { - background: var(--color-primary-light); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary-light), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - fun.convert-px(7) fun.convert-px(7) 0 fun.convert-px(2) - var(--color-primary-dark); - color: var(--color-fg-inverted); - transform: translateX(#{fun.convert-px(-4)}) - translateY(#{fun.convert-px(-4)}); - } - - &:focus { - text-decoration: underline solid var(--color-fg-inverted) - fun.convert-px(2); - } - - &:active { - background: var(--color-primary-dark); - box-shadow: 0 0 0 fun.convert-px(2) var(--color-primary), - 0 0 0 fun.convert-px(3) var(--color-primary-darker), - 0 0 0 0 var(--color-primary-dark); - text-decoration: none; - transform: translateX(#{fun.convert-px(4)}) - translateY(#{fun.convert-px(4)}); - } - } - } - - &--secondary { - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) - var(--color-shadow), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) - var(--color-shadow); - color: var(--color-primary); - - &:disabled { - border-color: var(--color-border-dark); - color: var(--color-fg-light); - } - - &:not(:disabled) { - &:hover, - &:focus { - border-color: var(--color-primary-light); - color: var(--color-primary-light); - box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) - var(--color-shadow-light), - fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) - fun.convert-px(-2) var(--color-shadow-light), - fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) - fun.convert-px(-4) var(--color-shadow-light), - fun.convert-px(7) fun.convert-px(10) fun.convert-px(12) - fun.convert-px(-3) var(--color-shadow-light); - transform: scale(var(--scale-up, 1.1)); - } - - &:focus { - text-decoration: underline var(--color-primary-light) fun.convert-px(3); - } - - &:active { - border-color: var(--color-primary-dark); - box-shadow: 0 0 0 0 var(--color-shadow); - color: var(--color-primary-dark); - text-decoration: none; - transform: scale(var(--scale-down, 0.94)); - } - } - } - - &--tertiary { - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg), - fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-dark), - fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg), - fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-dark); - color: var(--color-primary); - - &:disabled { - color: var(--color-fg-light); - border-color: var(--color-border-dark); - box-shadow: fun.convert-px(2) fun.convert-px(2) 0 0 var(--color-bg), - fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-primary-darker), - fun.convert-px(5) fun.convert-px(5) 0 0 var(--color-bg), - fun.convert-px(6) fun.convert-px(6) 0 0 var(--color-primary-darker); - } - - &:not(:disabled) { - &:hover, - &:focus { - border-color: var(--color-primary-light); - box-shadow: fun.convert-px(2) fun.convert-px(3) 0 0 var(--color-bg), - fun.convert-px(4) fun.convert-px(5) 0 0 var(--color-primary), - fun.convert-px(6) fun.convert-px(8) 0 0 var(--color-bg), - fun.convert-px(8) fun.convert-px(10) 0 0 var(--color-primary), - fun.convert-px(10) fun.convert-px(12) fun.convert-px(1) 0 - var(--color-shadow-light), - fun.convert-px(10) fun.convert-px(12) fun.convert-px(5) - fun.convert-px(1) var(--color-shadow-light); - color: var(--color-primary-light); - transform: translateX(#{fun.convert-px(-3)}) - translateY(#{fun.convert-px(-5)}); - } - - &:focus { - text-decoration: underline var(--color-primary) fun.convert-px(2); - } - - &:active { - box-shadow: 0 0 0 0 var(--color-shadow); - text-decoration: none; - transform: translateX(#{fun.convert-px(5)}) - translateY(#{fun.convert-px(6)}); - } - } - } -} |
