Skip to content

Commit

Permalink
feat(Buttons): Leading + trailing icons
Browse files Browse the repository at this point in the history
Adds standardized leading and trailing icons to our Fill, Stroke, and TextButtons.
  • Loading branch information
dreamwasp authored Feb 14, 2024
1 parent ab20ade commit 5209b31
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 61 deletions.
4 changes: 2 additions & 2 deletions packages/gamut/src/Button/CTAButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createButtonComponent } from './shared';
import { ctaButtonVariants } from './variants';
import { createButtonComponent } from './shared/styles';
import { ctaButtonVariants } from './shared/variants';

export const CTAButton = createButtonComponent<{ variant?: 'primary' }>(
ctaButtonVariants
Expand Down
21 changes: 17 additions & 4 deletions packages/gamut/src/Button/FillButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { createButtonComponent } from './shared';
import { fillButtonVariants, sizeVariants } from './variants';
import { forwardRef } from 'react';

export const FillButton = createButtonComponent(
import { ButtonBaseElements } from '../ButtonBase/ButtonBase';
import {
createButtonComponent,
fillButtonVariants,
InlineIconButton,
InlineIconButtonProps,
sizeVariants,
fillButtonVariants
} from './shared';

const FillButtonBase = createButtonComponent(sizeVariants, fillButtonVariants);

export type FillButtonProps = InlineIconButtonProps<typeof FillButtonBase>;

export const FillButton = forwardRef<ButtonBaseElements, FillButtonProps>(
({ ...props }, ref) => {
return <InlineIconButton button={FillButtonBase} {...props} ref={ref} />;
}
);
23 changes: 13 additions & 10 deletions packages/gamut/src/Button/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { GamutIconProps } from '@codecademy/gamut-icons';
import { ComponentProps, forwardRef } from 'react';

import { ButtonBaseElements } from '../ButtonBase/ButtonBase';
import { createButtonComponent } from './shared';
import { iconSizeVariants, textButtonVariants } from './variants';
import {
createButtonComponent,
IconComponentType,
iconSizeVariants,
textButtonVariants,
} from './shared';

const IconButtonInner = createButtonComponent(
const IconButtonBase = createButtonComponent(
iconSizeVariants,
textButtonVariants
);

export type IconButtonProps = ComponentProps<typeof IconButtonInner> & {
'aria-label': string;
icon: React.ComponentType<GamutIconProps>;
};
export type IconButtonProps = ComponentProps<typeof IconButtonBase> &
IconComponentType & {
'aria-label': string;
};

export const IconButton = forwardRef<ButtonBaseElements, IconButtonProps>(
({ children, icon: Icon, variant = 'secondary', ...props }, ref) => {
return (
<IconButtonInner {...props} variant={variant} ref={ref}>
<IconButtonBase {...props} variant={variant} ref={ref}>
{Icon && (
<Icon
width="calc(100% - 14px)"
Expand All @@ -27,7 +30,7 @@ export const IconButton = forwardRef<ButtonBaseElements, IconButtonProps>(
/>
)}
{children}
</IconButtonInner>
</IconButtonBase>
);
}
);
22 changes: 19 additions & 3 deletions packages/gamut/src/Button/StrokeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { createButtonComponent } from './shared';
import { sizeVariants, strokeButtonVariants } from './variants';
import { forwardRef } from 'react';

export const StrokeButton = createButtonComponent(
import { ButtonBaseElements } from '../ButtonBase/ButtonBase';
import {
createButtonComponent,
InlineIconButton,
InlineIconButtonProps,
sizeVariants,
strokeButtonVariants,
} from './shared';

const StrokeButtonBase = createButtonComponent(
sizeVariants,
strokeButtonVariants
);

export type StrokeButtonProps = InlineIconButtonProps<typeof StrokeButtonBase>;

export const StrokeButton = forwardRef<ButtonBaseElements, StrokeButtonProps>(
({ ...props }, ref) => {
return <InlineIconButton button={StrokeButtonBase} {...props} ref={ref} />;
}
);
21 changes: 17 additions & 4 deletions packages/gamut/src/Button/TextButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { createButtonComponent } from './shared';
import { sizeVariants, textButtonVariants } from './variants';
import { forwardRef } from 'react';

export const TextButton = createButtonComponent(
import { ButtonBaseElements } from '../ButtonBase/ButtonBase';
import {
createButtonComponent,
InlineIconButton,
InlineIconButtonProps,
sizeVariants,
textButtonVariants,
sizeVariants
} from './shared';

const TextButtonBase = createButtonComponent(textButtonVariants, sizeVariants);

export type TextButtonProps = InlineIconButtonProps<typeof TextButtonBase>;

export const TextButton = forwardRef<ButtonBaseElements, TextButtonProps>(
({ ...props }, ref) => {
return <InlineIconButton button={TextButtonBase} {...props} ref={ref} />;
}
);
34 changes: 34 additions & 0 deletions packages/gamut/src/Button/__tests__/TextButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { StarIcon } from '@codecademy/gamut-icons';
import { fireEvent } from '@testing-library/react';
import { setupRtl } from 'component-test-setup';

import { TextButton } from '../TextButton';

const onClick = jest.fn();

const renderView = setupRtl(TextButton, {
children: 'Click me!',
onClick,
});

describe('TextButton', () => {
it('renders a clickable button', () => {
const { view } = renderView();

const cta = view.getByRole('button', { name: 'Click me!' });

fireEvent.click(cta);

expect(onClick).toHaveBeenCalled();
});
it('renders an leading icon when an icon is provided', () => {
const { view } = renderView({ icon: StarIcon });

view.getByTitle('Star Icon');
});
it('renders an icon when an icon is provided', () => {
const { view } = renderView({ icon: StarIcon });

view.getByTitle('Star Icon');
});
});
65 changes: 65 additions & 0 deletions packages/gamut/src/Button/shared/InlineIconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { forwardRef } from 'react';

import { FlexBox } from '../../Box';
import { ButtonBaseElements } from '../../ButtonBase/ButtonBase';
import { FillButtonProps } from '../FillButton';
import { StrokeButtonProps } from '../StrokeButton';
import { TextButtonProps } from '../TextButton';

type InlineIconButtonComponents =
| FillButtonProps
| StrokeButtonProps
| TextButtonProps;

type InlineIconButtonType = InlineIconButtonComponents & {
button: React.ComponentType<InlineIconButtonComponents>;
};

const getButtonContent = ({
iconPosition,
icon: Icon,
children,
}: Pick<InlineIconButtonType, 'iconPosition' | 'icon' | 'children'>) => {
const iconSpacing = iconPosition === 'left' ? 'mr' : 'ml';
const iconPositioning = iconPosition === 'left' ? 0 : 1;

const iconProps = {
'aria-hidden': true,
size: 12,
[iconSpacing]: 8,
order: iconPositioning,
} as const;

return !Icon ? (
<>{children}</>
) : (
<FlexBox center height="100%">
{Icon && <Icon {...iconProps} />}
{children}
</FlexBox>
);
};

export const InlineIconButton = forwardRef<
ButtonBaseElements,
InlineIconButtonType
>(
(
{
children,
button: Button,
icon,
iconPosition = 'left',
variant,
...props
},
ref
) => {
const content = getButtonContent({ iconPosition, icon, children });
return (
<Button {...props} variant={variant} ref={ref}>
{content}
</Button>
);
}
);
4 changes: 4 additions & 0 deletions packages/gamut/src/Button/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './InlineIconButton';
export * from './styles';
export * from './types';
export * from './variants';
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import {
ColorModes,
fontSmoothPixel,
modeColorProps,
styledOptions,
system,
transitionConcat,
} from '@codecademy/gamut-styles';
import {
CSSObject,
StyleProps,
ThemeProps,
variance,
} from '@codecademy/variance';
import { CSSObject, ThemeProps, variance } from '@codecademy/variance';
import styled from '@emotion/styled';
import { ComponentProps, HTMLProps } from 'react';

import { ButtonBase, ButtonSelectors } from '../ButtonBase/ButtonBase';
import { CTAButton } from './CTAButton';
import { FillButton } from './FillButton';
import { IconButton } from './IconButton';
import { StrokeButton } from './StrokeButton';
import { TextButton } from './TextButton';
import { ButtonBase, ButtonSelectors } from '../../ButtonBase/ButtonBase';
import { ButtonBaseProps } from './types';

export const config = styledOptions<'button', 'size'>(['size']);

Expand Down Expand Up @@ -104,16 +93,6 @@ export const buttonStyles = system.css({
},
});

export interface ButtonBaseProps extends StyleProps<typeof buttonProps> {
onClick?: HTMLProps<HTMLButtonElement>['onClick'];
variant?: typeof buttonVariants[number];
size?: 'normal' | 'small' | 'large';
as?: never;
mode?: ColorModes;
}

export type ButtonProps = ButtonBaseProps & ComponentProps<typeof ButtonBase>;

export const createButtonComponent = <P>(
...args: (<T extends ThemeProps>(props: T) => CSSObject)[]
) =>
Expand All @@ -124,10 +103,3 @@ export const createButtonComponent = <P>(
...args,
buttonProps
);

export type ButtonTypes =
| typeof CTAButton
| typeof FillButton
| typeof IconButton
| typeof StrokeButton
| typeof TextButton;
39 changes: 39 additions & 0 deletions packages/gamut/src/Button/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { GamutIconProps } from '@codecademy/gamut-icons';
import { ColorModes } from '@codecademy/gamut-styles';
import { StyleProps } from '@codecademy/variance';
import { ComponentProps, HTMLProps } from 'react';

import { ButtonBase } from '../../ButtonBase';
import { CTAButton } from '../CTAButton';
import { FillButton } from '../FillButton';
import { IconButton } from '../IconButton';
import { StrokeButton } from '../StrokeButton';
import { TextButton } from '../TextButton';
import { buttonProps, buttonVariants } from './styles';

export interface ButtonBaseProps extends StyleProps<typeof buttonProps> {
onClick?: HTMLProps<HTMLButtonElement>['onClick'];
variant?: typeof buttonVariants[number];
size?: 'normal' | 'small' | 'large';
as?: never;
mode?: ColorModes;
}

export type ButtonProps = ButtonBaseProps & ComponentProps<typeof ButtonBase>;
export type IconComponentType = { icon: React.ComponentType<GamutIconProps> };

export type InlineIconButtonProps<
BaseButtonType extends
| keyof JSX.IntrinsicElements
| React.JSXElementConstructor<any>
> = ComponentProps<BaseButtonType> &
Partial<IconComponentType> & {
iconPosition?: 'right' | 'left';
};

export type ButtonTypes =
| typeof CTAButton
| typeof FillButton
| typeof IconButton
| typeof StrokeButton
| typeof TextButton;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { theme, variant } from '@codecademy/gamut-styles';

import { ButtonSelectors } from '../ButtonBase/ButtonBase';
import { buttonVariants, templateVariants } from './shared';
import { ButtonSelectors } from '../../ButtonBase/ButtonBase';
import { buttonVariants, templateVariants } from './styles';

export const fillButtonVariants = templateVariants(
buttonVariants,
Expand Down
2 changes: 1 addition & 1 deletion packages/gamut/src/GridForm/GridForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UnpackNestedValue,
} from 'react-hook-form';

import { ButtonProps } from '../Button/shared';
import { ButtonProps } from '../Button';
import { ConnectedForm, FormContextProps } from '../ConnectedForm';
import { FormValues } from '../Form/types';
import { LayoutGrid, LayoutGridProps } from '../Layout';
Expand Down
3 changes: 1 addition & 2 deletions packages/gamut/src/GridForm/GridFormButtons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { ComponentProps } from 'react';
import * as React from 'react';

import { GridBox } from '../../Box';
import { CTAButton, FillButton, TextButton } from '../../Button';
import { ButtonProps } from '../../Button/shared';
import { ButtonProps, CTAButton, FillButton, TextButton } from '../../Button';
import { SubmitButton, SubmitButtonProps } from '../../ConnectedForm';
import { Column } from '../../Layout';

Expand Down
2 changes: 1 addition & 1 deletion packages/gamut/src/Pagination/styles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { states, theme, transitionConcat } from '@codecademy/gamut-styles';

import { templateVariants } from '../Button/shared';
import { templateVariants } from '../Button/shared/styles';
import { ButtonSelectors } from '../ButtonBase/ButtonBase';

const paginationBaseStyles = {
Expand Down
Loading

0 comments on commit 5209b31

Please sign in to comment.