Skip to content

Commit

Permalink
Add AlertInCard component. Auto color for Alert icons. (#82)
Browse files Browse the repository at this point in the history
* Add AlertInCard component. Auto color for Alert icons.

* Update Alert/readme.md

* Update CHANGELOG.md

* Remove useless generic in getTheme

* tiny fixes to match design specs

* Update typing ignores

* design feedback. remove useless typing

* hush now codecov

* Force add DSD specific icons if prop not filled

* PR feedback

Co-authored-by: maartenafink <[email protected]>
  • Loading branch information
xdrdak and maartenafink committed Jun 5, 2020
1 parent 68db885 commit deebe46
Show file tree
Hide file tree
Showing 22 changed files with 1,867 additions and 1,094 deletions.
16 changes: 16 additions & 0 deletions packages/flame/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Refer to the [CONTRIBUTING guide](https://github.com/lightspeed/flame/blob/master/.github/CONTRIBUTING.md) for more info.

## [Unreleased]

### Deprecation Warning

- Alert icon prop will be removed in the next major feature release. Icons will be automatically assigned to an alert based on the variant used ([#82](https://github.com/lightspeed/flame/pull/82))

### Added

- New AlertInCard component ([#82](https://github.com/lightspeed/flame/pull/82))
- Alert component will now automatically inject the right icons as per DSD specs ([#82](https://github.com/lightspeed/flame/pull/82))

### Fixed

- Icons in Alert will now automatically assign the right color that matches the type of Alert ([#82](https://github.com/lightspeed/flame/pull/82))
- Icons in Alert will now be properly centered ([#82](https://github.com/lightspeed/flame/pull/82))

## 1.6.0 - 2020-05-25

### Added
Expand Down
5 changes: 4 additions & 1 deletion packages/flame/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"release:dryrun": "npm publish dist --dry-run"
},
"dependencies": {
"@styled-system/css": "^5.1.5",
"@styled-system/theme-get": "5.0.16",
"@types/styled-system": "5.1.6",
"polished": "^2.3.0",
Expand All @@ -48,10 +49,12 @@
"type-fest": "^0.3.0"
},
"devDependencies": {
"@types/styled-system__css": "^5.0.8",
"@types/styled-system__theme-get": "^5.0.0",
"@babel/core": "^7.1.6",
"@lightspeed/flame-tokens": "^1.0.0",
"@types/react": "^16.8.23",
"@types/storybook-readme": "^4.0.0",
"@types/storybook-readme": "^5.0.0",
"concurrently": "^3.5.1",
"fs-extra": "7.0.1",
"glob": "7.1.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/flame/scripts/build-themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ import { themeGet as baseThemeGet } from '@styled-system/theme-get';
${types}
type ThemePath<T> = T | flameTheme;
type ThemePath = flameTheme;
function themeGet<T>(path: ThemePath<T>, defaultValue?: string) {
function themeGet(path: ThemePath, defaultValue?: string) {
return function drill(props: { theme : any }) {
return baseThemeGet(path, defaultValue)(props);
};
Expand Down
33 changes: 9 additions & 24 deletions packages/flame/src/Alert/Alert.test.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import * as React from 'react';
import { createComponent, customRender, fireEvent } from 'test-utils';
import { customRender, fireEvent } from 'test-utils';
import { Alert } from './Alert';

describe('Alert', () => {
it('renders correctly', () => {
const component = createComponent(<Alert>Hello World</Alert>);
expect(component.toJSON()).toMatchSnapshot();
});

['success', 'info', 'warning', 'danger'].forEach(type => {
it(`renders type ${type} correctly`, () => {
const component = createComponent(
<Alert key={type} type={type}>
Hello World
</Alert>,
);
expect(component.toJSON()).toMatchSnapshot();
});
});

it('renders a number', () => {
const component = createComponent(<Alert>{300}</Alert>);
expect(component.toJSON()).toMatchSnapshot();
const { getByText } = customRender(<Alert>Hello World</Alert>);
expect(getByText('Hello World')).toBeTruthy();
});

it('renders with an icon', () => {
Expand All @@ -32,17 +16,18 @@ describe('Alert', () => {
});

it('renders with a title', () => {
const component = createComponent(<Alert title="My Title">Hello World</Alert>);
expect(component.toJSON()).toMatchSnapshot();
const { getByText } = customRender(<Alert title="My Title">Hello World</Alert>);
expect(getByText('My Title')).toBeTruthy();
expect(getByText('Hello World')).toBeTruthy();
});

it('Removes close button', () => {
const component = createComponent(
it('does not render the close button', () => {
const { queryByRole } = customRender(
<Alert title="My Title" noCloseBtn>
Hello World
</Alert>,
);
expect(component.toJSON()).toMatchSnapshot();
expect(queryByRole('button')).toBeFalsy();
});

it('accepts a custom handleClose function', () => {
Expand Down
118 changes: 39 additions & 79 deletions packages/flame/src/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,20 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { transparentize } from 'polished';
import { space, variant, SpaceProps } from 'styled-system';
import { themeGet } from '@styled-system/theme-get';
import { SpaceProps } from 'styled-system';
import css from '@styled-system/css';

import { CloseButton } from './CloseButton';
import { AlertIcons } from './AlertIcons';

import { Flex, Box } from '../Core';
import { Text } from '../Text';

const alertStyles = variant({
key: 'alertVariants',
prop: 'type',
});

const AlertWrapper = styled('div')<{ type: string }>`
display: flex;
justify-content: space-between;
align-items: flex-start;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06), 0 3px 6px 0 rgba(0, 0, 0, 0.03),
0 1px 2px 0 rgba(0, 0, 0, 0.1);
border-top: 4px solid;
border-radius: ${themeGet('radii.radius-2')};
padding: ${themeGet('space.2')} ${themeGet('space.3')};
${space}
${alertStyles}
`;

AlertWrapper.defaultProps = {
type: 'info',
};

const CloseButton = styled('button')`
font-size: ${themeGet('fontSizes.text')};
color: ${themeGet('colors.textHeading')};
background-color: ${props => transparentize(0.9, themeGet('colors.textHeading', '#000')(props))};
border-radius: 50%;
border: none;
cursor: pointer;
width: 1em;
height: 1em;
padding: 0;
position: relative;
&:focus {
outline: none;
}
`;

CloseButton.displayName = 'CloseButton';
export type AlertTypes = 'info' | 'success' | 'warning' | 'danger' | string;

export interface AlertProps {
/** CSS class name */
className?: string;
/** Enum for preset Alert types */
type?: 'info' | 'success' | 'warning' | 'danger' | string;
type?: AlertTypes;
/** Function called when Close button is tapped */
onClose?: Function;
/** Whether a Close button appears */
Expand All @@ -74,6 +36,14 @@ export const Alert: React.FC<AlertProps & SpaceProps> = ({
icon = null,
...restProps
}) => {
if (icon) {
// eslint-disable-next-line no-console
console.warn(
'Warning: Starting from next major version, you will no longer be able to add a prop icon.',
'Icons will be automatically be injected based on the Alert type',
);
}

const [isHidden, setIsHidden] = React.useState(false);

const handleClose = (e: React.MouseEvent<HTMLElement>) => {
Expand All @@ -84,47 +54,37 @@ export const Alert: React.FC<AlertProps & SpaceProps> = ({
if (isHidden) return null;

return (
<AlertWrapper type={type} {...restProps}>
<Flex flex="1" alignItems="flex-start">
{icon && (
<Flex alignItems="center" pr={2} pt="2px">
{icon}
<Box
css={css({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
boxShadow:
'0 0 0 1px rgba(0, 0, 0, 0.06), 0 3px 6px 0 rgba(0, 0, 0, 0.03), 0 1px 2px 0 rgba(0, 0, 0, 0.1)',
borderTop: '3px solid',
borderRadius: 'radius-2',
px: 3,
py: 2,
variant: `alertVariants.${type}`,
})}
{...restProps}
>
<Flex flex="1">
<Box flex="1" css={css({ position: 'relative', pl: 5 })}>
<Flex className="fl-alert__icon" css={{ position: 'absolute', left: '0px', top: '2px' }}>
{icon || <AlertIcons type={type} />}
</Flex>
)}
<Box flex="1">
{title && (
<Text color="textHeading" fontWeight="bold" fontSize="text" mt={0} mr={0} mb={1} ml={0}>
{title}
</Text>
)}
<Box fontSize="text-s">{children}</Box>
<Text fontSize={['text', 'text-s']} lineHeight={[3, 2]}>
{children}
</Text>
</Box>
</Flex>
{!noCloseBtn && (
<CloseButton type="button" onClick={handleClose}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
css={{
width: '0.5em',
height: '0.5em',
fill: 'black',
position: 'absolute',
top: '0.25em',
left: '0.25em',
right: '0',
bottom: '0',
}}
>
<g fillRule="evenodd" transform="translate(-4 -4)">
<path
fillOpacity=".5"
d="M9.414 8l2.122-2.121a1 1 0 1 0-1.415-1.415L8 6.586 5.879 4.464A1 1 0 0 0 4.464 5.88L6.586 8l-2.122 2.121a1 1 0 0 0 1.415 1.415L8 9.414l2.121 2.122a1 1 0 0 0 1.415-1.415L9.414 8z"
/>
</g>
</svg>
</CloseButton>
)}
</AlertWrapper>
{!noCloseBtn && <CloseButton onClick={handleClose} />}
</Box>
);
};
32 changes: 32 additions & 0 deletions packages/flame/src/Alert/AlertIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';

import { AlertTypes } from './Alert';
import { IconWarning } from '../Icon/Warning';
import { IconInfo } from '../Icon/Info';
import { IconDanger } from '../Icon/Danger';
import { IconVerified } from '../Icon/Verified';

interface AlertIcons {
type: AlertTypes;
}
const AlertIcons: React.FC<AlertIcons> = ({ type }) => {
if (type === 'info') {
return <IconInfo />;
}

if (type === 'success') {
return <IconVerified />;
}

if (type === 'warning') {
return <IconWarning />;
}

if (type === 'danger') {
return <IconDanger />;
}

return null;
};

export { AlertIcons };
43 changes: 43 additions & 0 deletions packages/flame/src/Alert/AlertInCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';
import { customRender, fireEvent } from 'test-utils';
import { AlertInCard } from './AlertInCard';

describe('AlertInCard ', () => {
it('renders correctly', () => {
const { getByText } = customRender(<AlertInCard type="info">Hello World</AlertInCard>);
expect(getByText('Hello World')).toBeTruthy();
});

it('removes close button', () => {
const { queryByText, getByRole } = customRender(
<AlertInCard type="success">Hello World</AlertInCard>,
);
fireEvent.click(getByRole('button'));
expect(queryByText('Hello World')).toBeFalsy();
});

it('does not render the close button', () => {
const { queryByRole } = customRender(
<AlertInCard type="warning" noCloseBtn>
Hello World
</AlertInCard>,
);

expect(queryByRole('button')).toBeFalsy();
});

it('accepts a custom handleClose function', () => {
const handleClose = jest.fn();
const { getByRole } = customRender(
<AlertInCard type="danger" onClose={handleClose}>
Hello World
</AlertInCard>,
);

expect(handleClose).not.toHaveBeenCalled();

fireEvent.click(getByRole('button'));

expect(handleClose).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit deebe46

Please sign in to comment.