diff --git a/packages/gamut/src/Anchor/Anchor.tsx b/packages/gamut/src/Anchor/Anchor.tsx new file mode 100644 index 00000000000..d51fc0c9658 --- /dev/null +++ b/packages/gamut/src/Anchor/Anchor.tsx @@ -0,0 +1,57 @@ +import { forwardRef, RefObject } from 'react'; + +import { ButtonBase } from '../ButtonBase/ButtonBase'; +import { AppendedIconProps, appendIconToContent } from '../helpers'; +import { AnchorBase, AnchorExtProps } from './types'; + +export const Anchor = forwardRef< + HTMLAnchorElement | HTMLButtonElement, + AnchorExtProps +>( + ( + { + children, + icon, + iconOffset, + iconPosition = 'left', + iconSize = 16, + isInlineIcon = true, + variant = 'inline', + ...rest + }, + ref + ) => { + const content = appendIconToContent({ + children, + iconOffset, + iconSize, + iconAndTextGap: 8, + isInlineIcon, + icon, + iconPosition, + } as AppendedIconProps); + + if (!rest.href || rest.disabled) { + return ( + } + variant={variant} + {...rest} + > + {content} + + ); + } + + return ( + } + variant={variant} + {...rest} + > + {content} + + ); + } +); diff --git a/packages/gamut/src/Anchor/index.ts b/packages/gamut/src/Anchor/index.ts new file mode 100644 index 00000000000..40e0aa2c777 --- /dev/null +++ b/packages/gamut/src/Anchor/index.ts @@ -0,0 +1,2 @@ +export * from './Anchor'; +export * from './types'; diff --git a/packages/gamut/src/Anchor/index.tsx b/packages/gamut/src/Anchor/index.tsx deleted file mode 100644 index 287ff0e6d37..00000000000 --- a/packages/gamut/src/Anchor/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { styledOptions, system, variant } from '@codecademy/gamut-styles'; -import { StyleProps, variance } from '@codecademy/variance'; -import styled from '@emotion/styled'; -import { ComponentProps, forwardRef, HTMLProps, RefObject } from 'react'; - -import { ButtonBase, ButtonSelectors } from '../ButtonBase/ButtonBase'; -import { AppendedIconProps, appendIconToContent } from '../helpers'; - -export interface AnchorProps - extends StyleProps, - StyleProps { - onClick?: HTMLProps['onClick']; -} - -const outlineFocusVisible = { - [ButtonSelectors.OUTLINE]: { - content: "''", - position: 'absolute', - inset: -4, - borderRadius: 'md', - border: 2, - borderColor: 'primary', - opacity: 0, - zIndex: 0, - }, - - [ButtonSelectors.OUTLINE_FOCUS_VISIBLE]: { - opacity: 1, - }, -} as const; - -const underlineFocusVisible = { - [ButtonSelectors.FOCUS_VISIBLE]: { - outline: 'currentColor solid 2px', - borderRadius: 'sm', - outlineOffset: '1.5px', - textDecoration: 'underline', - }, -} as const; - -const anchorVariants = variant({ - base: { - display: 'inline-block', - bg: 'transparent', - boxShadow: 'none', - border: 'none', - p: 0, - fontSize: 'inherit', - position: 'relative', - color: 'primary', - whiteSpace: 'nowrap', - [ButtonSelectors.HOVER]: { - textDecoration: 'none', - cursor: 'pointer', - }, - [ButtonSelectors.DISABLED]: { - cursor: 'not-allowed', - textDecoration: 'none', - color: 'text-disabled', - }, - }, - variants: { - standard: { - color: 'primary', - fontWeight: 'bold', - WebkitFontSmoothing: 'antialiased', - MozOsxFontSmoothing: 'grayscale', - [ButtonSelectors.HOVER]: { - textDecoration: 'underline', - }, - [ButtonSelectors.FOCUS_VISIBLE]: { - WebkitFontSmoothing: 'antialiased', - MozOsxFontSmoothing: 'grayscale', - outline: 'none', - }, - ...outlineFocusVisible, - }, - inline: { - display: 'inline', - whiteSpace: 'initial', - textDecoration: 'underline', - ...underlineFocusVisible, - }, - interface: { - color: 'text', - whiteSpace: 'initial', - [ButtonSelectors.HOVER]: { - color: 'primary', - }, - [ButtonSelectors.FOCUS_VISIBLE]: { - color: 'primary', - outline: 'none', - }, - ...outlineFocusVisible, - }, - 'standard-secondary': { - color: 'text', - textDecoration: 'underline', - ...underlineFocusVisible, - }, - }, -}); - -const anchorProps = variance.compose( - system.layout, - system.space, - system.typography -); - -export const AnchorBase = styled('a', styledOptions<'a'>())( - anchorVariants, - anchorProps -); - -type AnchorBaseProps = - | ComponentProps - | (Exclude, 'ref'> & - ComponentProps); - -type AnchorExtProps = Partial & AnchorBaseProps; - -export const Anchor = forwardRef< - HTMLAnchorElement | HTMLButtonElement, - AnchorExtProps ->( - ( - { - children, - icon, - iconOffset, - iconPosition = 'left', - iconSize = 16, - iconAndTextGap = 8, - isInlineIcon = true, - variant = 'inline', - ...rest - }, - ref - ) => { - const content = appendIconToContent({ - children, - icon, - iconOffset, - iconPosition, - iconSize, - iconAndTextGap, - isInlineIcon, - }); - if (!rest.href) { - return ( - } - variant={variant} - {...rest} - > - {content} - - ); - } - - return ( - } - variant={variant} - {...rest} - > - {content} - - ); - } -); diff --git a/packages/gamut/src/Anchor/styles.ts b/packages/gamut/src/Anchor/styles.ts new file mode 100644 index 00000000000..fa61679e4a6 --- /dev/null +++ b/packages/gamut/src/Anchor/styles.ts @@ -0,0 +1,92 @@ +import { variant } from '@codecademy/gamut-styles'; + +import { ButtonSelectors } from '../ButtonBase/ButtonBase'; + +const outlineFocusVisible = { + [ButtonSelectors.OUTLINE]: { + content: "''", + position: 'absolute', + inset: -4, + borderRadius: 'md', + border: 2, + borderColor: 'primary', + opacity: 0, + zIndex: 0, + }, + + [ButtonSelectors.OUTLINE_FOCUS_VISIBLE]: { + opacity: 1, + }, +} as const; + +const underlineFocusVisible = { + [ButtonSelectors.FOCUS_VISIBLE]: { + outline: 'currentColor solid 2px', + borderRadius: 'sm', + outlineOffset: '1.5px', + textDecoration: 'underline', + }, +} as const; + +export const anchorVariants = variant({ + base: { + display: 'inline-block', + bg: 'transparent', + boxShadow: 'none', + border: 'none', + p: 0, + fontSize: 'inherit', + position: 'relative', + color: 'primary', + whiteSpace: 'nowrap', + [ButtonSelectors.HOVER]: { + textDecoration: 'none', + cursor: 'pointer', + }, + [ButtonSelectors.DISABLED]: { + cursor: 'not-allowed', + textDecoration: 'none', + color: 'text-disabled', + }, + }, + variants: { + standard: { + color: 'primary', + fontWeight: 'bold', + WebkitFontSmoothing: 'antialiased', + MozOsxFontSmoothing: 'grayscale', + [ButtonSelectors.HOVER]: { + textDecoration: 'underline', + }, + [ButtonSelectors.FOCUS_VISIBLE]: { + WebkitFontSmoothing: 'antialiased', + MozOsxFontSmoothing: 'grayscale', + outline: 'none', + }, + ...outlineFocusVisible, + }, + inline: { + display: 'inline', + whiteSpace: 'initial', + textDecoration: 'underline', + ...underlineFocusVisible, + }, + interface: { + color: 'text', + whiteSpace: 'initial', + [ButtonSelectors.HOVER]: { + color: 'primary', + }, + [ButtonSelectors.FOCUS_VISIBLE]: { + color: 'primary', + outline: 'none', + }, + ...outlineFocusVisible, + }, + 'standard-secondary': { + color: 'text', + textDecoration: 'underline', + ...underlineFocusVisible, + }, + }, +}); diff --git a/packages/gamut/src/Anchor/types.ts b/packages/gamut/src/Anchor/types.ts new file mode 100644 index 00000000000..2a9e6fccfbf --- /dev/null +++ b/packages/gamut/src/Anchor/types.ts @@ -0,0 +1,36 @@ +import { styledOptions, system } from '@codecademy/gamut-styles'; +import { StyleProps, variance } from '@codecademy/variance'; +import styled from '@emotion/styled'; +import { ComponentProps, HTMLProps } from 'react'; + +import { ButtonBase } from '../ButtonBase'; +import { AppendedIconProps } from '../helpers'; +import { anchorVariants } from './styles'; + +export interface AnchorProps + extends StyleProps, + StyleProps { + onClick?: HTMLProps['onClick']; +} + +const anchorProps = variance.compose( + system.layout, + system.space, + system.typography +); + +export const AnchorBase = styled('a', styledOptions<'a'>())( + anchorVariants, + anchorProps +); + +type AnchorAsAnchor = ComponentProps & { + disabled?: never; +}; + +type AnchorAsButton = Exclude, 'ref'> & + ComponentProps + +type AnchorBaseProps = AnchorAsAnchor | AnchorAsButton; + +export type AnchorExtProps = Partial & AnchorBaseProps; diff --git a/packages/gamut/src/Button/shared/InlineIconButton.tsx b/packages/gamut/src/Button/shared/InlineIconButton.tsx index 7425b89a5fd..1584d7f91f9 100644 --- a/packages/gamut/src/Button/shared/InlineIconButton.tsx +++ b/packages/gamut/src/Button/shared/InlineIconButton.tsx @@ -1,7 +1,7 @@ import { forwardRef } from 'react'; import { ButtonBaseElements } from '../../ButtonBase/ButtonBase'; -import { appendIconToContent } from '../../helpers'; +import { AppendedIconProps, appendIconToContent } from '../../helpers'; import { FillButtonProps } from '../FillButton'; import { StrokeButtonProps } from '../StrokeButton'; import { TextButtonProps } from '../TextButton'; @@ -11,10 +11,11 @@ type InlineIconButtonComponents = | StrokeButtonProps | TextButtonProps; -type InlineIconButtonType = InlineIconButtonComponents & { - button: React.ComponentType; - iconSize?: number; -}; +type InlineIconButtonType = InlineIconButtonComponents & + AppendedIconProps & { + button: React.ComponentType; + iconSize?: number; + }; export const InlineIconButton = forwardRef< ButtonBaseElements, @@ -31,12 +32,18 @@ export const InlineIconButton = forwardRef< }, ref ) => { - const content = appendIconToContent({ - iconPosition, - icon, + const commonIconProps = { iconSize: props.size === 'small' ? 12 : 16, children, + }; + + const content = appendIconToContent({ + ...commonIconProps, + icon, + iconPosition, + isInlineIcon: true, }); + return (