From 512bb4384993f0d8878b2f2fef596d6a85f1626c Mon Sep 17 00:00:00 2001 From: Henri Bernard <132286421+hebernardEquisoft@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:16:29 -0400 Subject: [PATCH] feat(Link): implement link component (#953) --- .../src/components/breadcrumb/breadcrumb.tsx | 2 +- .../components/buttons/abstract-button.tsx | 144 -- .../buttons/abstract/abstract-button.tsx | 29 + .../src/components/buttons/abstract/styled.ts | 70 + .../src/components/buttons/abstract/types.ts | 9 + .../components/buttons/button.test.tsx.snap | 20 +- .../react/src/components/buttons/button.tsx | 69 +- .../buttons/icon-button.test.tsx.snap | 4 +- .../src/components/buttons/icon-button.tsx | 6 +- .../react/src/components/buttons/index.ts | 8 + .../src/components/buttons/search-button.tsx | 2 +- .../react/src/components/buttons/styled.ts | 88 ++ .../react/src/components/buttons/types.ts | 51 + .../date-picker/date-picker.test.tsx.snap | 62 +- .../components/date-picker/date-picker.tsx | 2 +- .../disclosure/disclosure.test.tsx.snap | 4 + .../src/components/disclosure/disclosure.tsx | 8 +- .../dropdown-menu-button.test.tsx.snap | 124 +- .../dropdown-menu-button.tsx | 2 +- .../dropdown-navigation.tsx | 2 +- .../external-link/external-link.tsx | 3 + packages/react/src/components/link/index.ts | 1 + .../react/src/components/link/link.test.tsx | 200 +++ .../src/components/link/link.test.tsx.snap | 1168 +++++++++++++++++ packages/react/src/components/link/link.tsx | 95 ++ packages/react/src/components/link/styled.ts | 91 ++ packages/react/src/components/link/types.ts | 50 + .../components/menu-button/menu-button.tsx | 2 +- .../modal/dialog/modal-dialog.test.tsx.snap | 584 +++------ .../src/components/modal/modal.test.tsx.snap | 8 +- .../password-input.test.tsx.snap | 12 +- .../src/components/route-link/route-link.tsx | 3 + .../search/search-input.test.tsx.snap | 1 + .../src/components/search/search-input.tsx | 2 +- .../src/components/skip-link/skip-link.tsx | 10 +- .../src/components/tabs/tabs.test.tsx.snap | 186 +-- .../src/components/tag/tag.test.tsx.snap | 12 +- .../toast/toast-container.test.tsx.snap | 5 + .../toggletip/toggletip.test.tsx.snap | 4 + .../user-profile/user-profile.test.tsx.snap | 162 +-- packages/react/src/index.ts | 4 +- .../themes/tokens/component/link-tokens.ts | 19 +- .../stories/external-link.stories.tsx | 2 +- packages/storybook/stories/link.mdx | 17 +- packages/storybook/stories/link.stories.tsx | 101 +- packages/storybook/stories/route-link.mdx | 36 + .../storybook/stories/route-link.stories.tsx | 28 + 47 files changed, 2593 insertions(+), 919 deletions(-) delete mode 100755 packages/react/src/components/buttons/abstract-button.tsx create mode 100755 packages/react/src/components/buttons/abstract/abstract-button.tsx create mode 100644 packages/react/src/components/buttons/abstract/styled.ts create mode 100644 packages/react/src/components/buttons/abstract/types.ts create mode 100644 packages/react/src/components/buttons/index.ts create mode 100644 packages/react/src/components/buttons/styled.ts create mode 100644 packages/react/src/components/buttons/types.ts create mode 100644 packages/react/src/components/link/index.ts create mode 100644 packages/react/src/components/link/link.test.tsx create mode 100644 packages/react/src/components/link/link.test.tsx.snap create mode 100644 packages/react/src/components/link/link.tsx create mode 100644 packages/react/src/components/link/styled.ts create mode 100644 packages/react/src/components/link/types.ts create mode 100644 packages/storybook/stories/route-link.mdx create mode 100644 packages/storybook/stories/route-link.stories.tsx diff --git a/packages/react/src/components/breadcrumb/breadcrumb.tsx b/packages/react/src/components/breadcrumb/breadcrumb.tsx index cd699372c3..329ed5e3ad 100644 --- a/packages/react/src/components/breadcrumb/breadcrumb.tsx +++ b/packages/react/src/components/breadcrumb/breadcrumb.tsx @@ -2,7 +2,7 @@ import { KeyboardEvent, useCallback, useEffect, useRef, useState, VoidFunctionCo import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { eventIsInside } from '../../utils/events'; -import { IconButton } from '../buttons/icon-button'; +import { IconButton } from '../buttons'; import { Icon } from '../icon/icon'; import { NavList } from '../nav-list/nav-list'; import { NavListOption } from '../nav-list/nav-list-option'; diff --git a/packages/react/src/components/buttons/abstract-button.tsx b/packages/react/src/components/buttons/abstract-button.tsx deleted file mode 100755 index d3ec02e426..0000000000 --- a/packages/react/src/components/buttons/abstract-button.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { ButtonHTMLAttributes, forwardRef, PropsWithChildren, Ref } from 'react'; -import styled, { css, FlattenInterpolation, ThemeProps } from 'styled-components'; -import { ResolvedTheme } from '../../themes/theme'; -import { focus } from '../../utils/css-state'; - -type Size = 'small' | 'medium'; - -const getButtonMinHeight = ({ isMobile, size }: { isMobile: boolean, size?: Size }): string => { - switch (size) { - case 'small': - return isMobile ? 'var(--size-3x)' : 'var(--size-1halfx)'; - case 'medium': - default: - return isMobile ? 'var(--size-3x)' : 'var(--size-2x)'; - } -}; - -const getButtonPadding = ({ isMobile, size }: { isMobile: boolean, size?: Size }): string => { - switch (size) { - case 'small': - return isMobile ? '0 var(--spacing-3x);' : '0 var(--spacing-1halfx);'; - case 'medium': - default: - return isMobile ? '0 var(--spacing-3x);' : '0 var(--spacing-2x);'; - } -}; - -export const defaultButtonStyles = css<{ $focusable?: boolean, isMobile: boolean, size?: Size }>` - align-items: center; - appearance: none; - background: inherit; - border: 1px solid; - border-radius: 1.5rem; - box-sizing: border-box; - color: inherit; - display: inline-flex; - font-family: inherit; - font-size: ${({ isMobile }) => (isMobile ? 0.875 : 0.75)}rem; - font-weight: var(--font-bold); - justify-content: center; - letter-spacing: ${({ isMobile }) => (isMobile ? 0.033125 : 0.025)}rem; - line-height: ${({ isMobile }) => (isMobile ? 1.5 : 1)}rem; - min-height: ${getButtonMinHeight}; - min-width: 2rem; - outline: none; - padding: ${getButtonPadding}; - text-transform: uppercase; - user-select: none; - - ${(props) => props.$focusable !== false && focus}; - - > svg { - height: ${({ isMobile }) => (isMobile ? 'var(--size-1halfx)' : 'var(--size-1x)')}; - width: ${({ isMobile }) => (isMobile ? 'var(--size-1halfx)' : 'var(--size-1x)')}; - } -`; - -interface AbstractButtonProps extends ButtonHTMLAttributes { - focusable?: boolean; - isMobile: boolean; - size?: Size; -} - -const StyledButton = styled.button<{ $focusable?: boolean; isMobile: boolean; size?: Size }>` - ${defaultButtonStyles} -`; - -export const AbstractButton = forwardRef>(({ - children, - onClick, - focusable, - ...props -}: AbstractButtonProps, ref: Ref) => ( - - {children} - -)); - -AbstractButton.displayName = 'AbstractButton'; - -export type ButtonType = - | 'primary' - | 'secondary' - | 'tertiary' - | 'destructive-primary' - | 'destructive-secondary' - | 'destructive-tertiary'; - -export interface ButtonTypeStyles { - buttonType: ButtonType; - focusable?: boolean; - inverted?: boolean; - theme: ResolvedTheme; -} - -const getButtonStyles: (props: ButtonTypeStyles) => FlattenInterpolation> = ({ - focusable, - inverted, - buttonType, - theme, -}) => { - const inversionSuffix = inverted ? '-inverted' : ''; - - return css` - background-color: ${theme.component[`button-${buttonType}${inversionSuffix}-background-color`]}; - border-color: ${theme.component[`button-${buttonType}${inversionSuffix}-border-color`]}; - color: ${theme.component[`button-${buttonType}${inversionSuffix}-text-color`]}; - - &:hover, - &[aria-expanded='true'] { - background-color: ${theme.component[`button-${buttonType}${inversionSuffix}-hover-background-color`]}; - border-color: ${theme.component[`button-${buttonType}${inversionSuffix}-hover-border-color`]}; - color: ${theme.component[`button-${buttonType}${inversionSuffix}-hover-text-color`]}; - } - - &[aria-disabled='true'] { - background-color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-background-color`]}; - border-color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-border-color`]}; - color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-text-color`]}; - cursor: not-allowed; - ${buttonType === 'destructive-primary' && css` - &, - ${focusable !== false && '&:focus,'} - &:hover { - background-color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-background-color`]}; - border-color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-border-color`]}; - color: ${theme.component[`button-${buttonType}${inversionSuffix}-disabled-text-color`]}; - } - `} - } - `; -}; - -export const getButtonTypeStyles: (props: ButtonTypeStyles) => FlattenInterpolation> = (props) => css` - ${props.focusable !== false && focus(props, { inverted: props.inverted })}; - ${getButtonStyles(props)}; -`; diff --git a/packages/react/src/components/buttons/abstract/abstract-button.tsx b/packages/react/src/components/buttons/abstract/abstract-button.tsx new file mode 100755 index 0000000000..fcdd7c91cd --- /dev/null +++ b/packages/react/src/components/buttons/abstract/abstract-button.tsx @@ -0,0 +1,29 @@ +import { forwardRef, PropsWithChildren, Ref } from 'react'; +import { useDeviceContext } from '../../device-context-provider/device-context-provider'; +import { StyledAbstractButton } from './styled'; +import { AbstractButtonProps } from './types'; + +export const AbstractButton = forwardRef>(({ + children, + onClick, + focusable, + ...props +}: AbstractButtonProps, ref: Ref) => { + const { isMobile } = useDeviceContext(); + + return ( + + {children} + + ); +}); + +AbstractButton.displayName = 'AbstractButton'; diff --git a/packages/react/src/components/buttons/abstract/styled.ts b/packages/react/src/components/buttons/abstract/styled.ts new file mode 100644 index 0000000000..03a392cbf3 --- /dev/null +++ b/packages/react/src/components/buttons/abstract/styled.ts @@ -0,0 +1,70 @@ +import styled, { css, FlattenInterpolation, ThemeProps } from 'styled-components'; +import { ResolvedTheme } from '../../../themes/theme'; +import { focus } from '../../../utils/css-state'; +import { Size } from './types'; + +export interface BaseButtonStyles { + $size?: Size; + $isMobile: boolean; + $focusable?: boolean; + $inverted?: boolean; +} + +function getButtonMinHeight({ $isMobile, $size }: BaseButtonStyles): string { + switch ($size) { + case 'small': + return $isMobile ? 'var(--size-3x)' : 'var(--size-1halfx)'; + case 'medium': + default: + return $isMobile ? 'var(--size-3x)' : 'var(--size-2x)'; + } +} + +function getButtonPadding({ $isMobile, $size }: BaseButtonStyles): string { + switch ($size) { + case 'small': + return $isMobile ? '0 var(--spacing-3x);' : '0 var(--spacing-1halfx);'; + case 'medium': + default: + return $isMobile ? '0 var(--spacing-3x);' : '0 var(--spacing-2x);'; + } +} + +export const getBaseButtonStyles = ({ + $size, + $isMobile, + $focusable, + $inverted, +}: BaseButtonStyles): FlattenInterpolation> => css` + align-items: center; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: inline-flex; + font-family: inherit; + font-size: ${$isMobile ? 0.875 : 0.75}rem; + font-weight: var(--font-bold); + justify-content: center; + letter-spacing: ${$isMobile ? 0.033125 : 0.025}rem; + line-height: ${$isMobile ? 1.5 : 1}rem; + min-height: ${getButtonMinHeight({ $isMobile, $size })}; + min-width: 2rem; + outline: none; + padding: ${getButtonPadding({ $isMobile, $size })}; + text-transform: uppercase; + user-select: none; + + ${({ theme }) => $focusable !== false && focus({ theme }, { inverted: $inverted })}; + + > svg { + height: ${$isMobile ? 'var(--size-1halfx)' : 'var(--size-1x)'}; + width: ${$isMobile ? 'var(--size-1halfx)' : 'var(--size-1x)'}; + } +`; + +export const StyledAbstractButton = styled.button` + ${getBaseButtonStyles}; +`; diff --git a/packages/react/src/components/buttons/abstract/types.ts b/packages/react/src/components/buttons/abstract/types.ts new file mode 100644 index 0000000000..9eefb3ace9 --- /dev/null +++ b/packages/react/src/components/buttons/abstract/types.ts @@ -0,0 +1,9 @@ +import { ButtonHTMLAttributes } from 'react'; + +export type Size = 'small' | 'medium'; + +export interface AbstractButtonProps extends ButtonHTMLAttributes { + focusable?: boolean; + isMobile: boolean; + size?: Size; +} diff --git a/packages/react/src/components/buttons/button.test.tsx.snap b/packages/react/src/components/buttons/button.test.tsx.snap index 819eaa3dc4..4daeab1dc2 100644 --- a/packages/react/src/components/buttons/button.test.tsx.snap +++ b/packages/react/src/components/buttons/button.test.tsx.snap @@ -465,14 +465,6 @@ exports[`Button has left and right icons 1`] = ` width: var(--size-1x); } -.c2 { - margin-right: var(--spacing-1x); -} - -.c3 { - margin-left: var(--spacing-1x); -} - .c1 { background-color: #006296; border-color: #006296; @@ -504,6 +496,14 @@ exports[`Button has left and right icons 1`] = ` cursor: not-allowed; } +.c2 { + margin-right: var(--spacing-1x); +} + +.c3 { + margin-left: var(--spacing-1x); +} +