diff --git a/src/components/alert/__snapshots__/alert.test.tsx.snap b/src/components/alert/__snapshots__/alert.test.tsx.snap new file mode 100644 index 0000000..d648c0f --- /dev/null +++ b/src/components/alert/__snapshots__/alert.test.tsx.snap @@ -0,0 +1,245 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IonAlert should render alert with type: info 1`] = ` +.c0 { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 1.4rem; + line-height: 2rem; + font-weight: 400; + color: #282b33; + border-radius: 8px; + height: 4rem; + padding: 8px 16px 8px 12px; + border: 1px solid #0bb2cb; + background-color: #e2f9fd; + border-left: 8px solid #0bb2cb; +} + +.c0 .c1 svg { + fill: #0bb2cb; +} + +.c2 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +
+
+
+ + + + + + + Example message + +
+
+
+`; + +exports[`IonAlert should render alert with type: negative 1`] = ` +.c0 { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 1.4rem; + line-height: 2rem; + font-weight: 400; + color: #282b33; + border-radius: 8px; + height: 4rem; + padding: 8px 16px 8px 12px; + border: 1px solid #d6293a; + background-color: #faeaec; + border-left: 8px solid #d6293a; +} + +.c0 .c1 svg { + fill: #d6293a; +} + +.c2 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +
+
+
+ + + + + + + Example message + +
+
+
+`; + +exports[`IonAlert should render alert with type: success 1`] = ` +.c0 { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 1.4rem; + line-height: 2rem; + font-weight: 400; + color: #282b33; + border-radius: 8px; + height: 4rem; + padding: 8px 16px 8px 12px; + border: 1px solid #2d9f70; + background-color: #e7f9f1; + border-left: 8px solid #2d9f70; +} + +.c0 .c1 svg { + fill: #2d9f70; +} + +.c2 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +
+
+
+ + + + + + + Example message + +
+
+
+`; + +exports[`IonAlert should render alert with type: warning 1`] = ` +.c0 { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 1.4rem; + line-height: 2rem; + font-weight: 400; + color: #282b33; + border-radius: 8px; + height: 4rem; + padding: 8px 16px 8px 12px; + border: 1px solid #f9a915; + background-color: #fff5e0; + border-left: 8px solid #f9a915; +} + +.c0 .c1 svg { + fill: #f9a915; +} + +.c2 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +
+
+
+ + + + + + + Example message + +
+
+
+`; diff --git a/src/components/alert/alert.test.tsx b/src/components/alert/alert.test.tsx index 6cc300c..cc80f51 100644 --- a/src/components/alert/alert.test.tsx +++ b/src/components/alert/alert.test.tsx @@ -1,15 +1,15 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { IonAlert, AlertProps } from './alert'; import { StatusType } from '../../core/types/status'; +import { renderWithTheme } from '../utils/test-utils'; +import { AlertProps, IonAlert } from './alert'; const defaultAlert: AlertProps = { message: 'Example message', }; -const sut = (props = defaultAlert) => render(); +const sut = (props = defaultAlert) => renderWithTheme(); const alertId = 'ion-alert'; const getAlert = () => screen.getByTestId(alertId); @@ -36,14 +36,17 @@ describe('IonAlert', () => { it.each(['success', 'info', 'warning', 'negative'] as StatusType[])( 'should render alert with type: %s', (type) => { - sut({ ...defaultAlert, type }); - expect(getAlert().className).toContain(`type-${type}`); + const { container } = sut({ ...defaultAlert, type }); + expect(container).toMatchSnapshot(); } ); it('should not render background alert when hideBackground is true', async () => { await sut({ ...defaultAlert, hideBackground: true }); - expect(getAlert().className).toContain('hideBackground-true'); + expect(getAlert()).not.toHaveStyleRule( + 'background-color', + expect.any(String) + ); }); it('should render icon close when closable is true', async () => { @@ -52,12 +55,11 @@ describe('IonAlert', () => { expect(iconClose).toBeTruthy(); }); - it('should remove alert when click on close icon', async () => { - await sut({ ...defaultAlert, closable: true }); - const iconClose = screen.getByTestId('ion-icon-close'); - expect(getAlert()).toBeTruthy(); + it('should emit onClose function when click on close icon', async () => { + const onClose = jest.fn(); + await sut({ ...defaultAlert, closable: true, onClose }); - await userEvent.click(iconClose); - expect(screen.queryByTestId(alertId)).not.toBeTruthy(); + await userEvent.click(screen.getByTestId('ion-icon-close')); + expect(onClose).toHaveBeenCalledTimes(1); }); }); diff --git a/src/components/alert/alert.tsx b/src/components/alert/alert.tsx index d556d55..2c15795 100644 --- a/src/components/alert/alert.tsx +++ b/src/components/alert/alert.tsx @@ -1,16 +1,17 @@ -import { useState } from 'react'; import { StatusType } from '../../core/types/status'; +import { IonButton } from '../button'; import ErrorBoundary from '../error/error-boundary'; import { IonIcon } from '../icons/icons'; import isValidLabel from '../utils/isValidLabel'; -import { AlertStyled } from './styled'; +import { Alert, Wrapper } from './styled'; export interface AlertProps { message: string; type?: StatusType; closable?: boolean; hideBackground?: boolean; + onClose?: () => void; } type iconType = @@ -19,47 +20,48 @@ type iconType = | 'info-solid' | 'close-solid'; -const icons = { +const icons: Record = { info: 'info-solid', warning: 'exclamation-solid', negative: 'close-solid', success: 'check-solid', }; -const sizeIcon = 24; +const SIZE_ICON = 24; -const getIcon = (alertType: StatusType) => icons[alertType] as iconType; +const getIcon = (alertType: StatusType): iconType => icons[alertType]; export const IonAlert = ({ message, type = 'success', closable = false, hideBackground = false, + onClose, }: AlertProps) => { - const [showAlert, setShowAlert] = useState(true); - const icon = getIcon(type); - if (!isValidLabel(message)) { return ; } - if (!showAlert) { - return <>; - } - return ( - - - {message} + + + {message} + {closable && ( -
setShowAlert(false)}> - -
+ )} -
+ ); }; diff --git a/src/components/alert/styled.ts b/src/components/alert/styled.ts index 639ae7c..64f3970 100644 --- a/src/components/alert/styled.ts +++ b/src/components/alert/styled.ts @@ -1,88 +1,48 @@ -import stitches from '../../stitches.config'; -import { getFillBgFontBorderColors } from '../utils'; -import { spacing } from '../utils/spacing'; - -const { styled } = stitches; - -export const AlertStyled = styled('div', { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - padding: `${spacing(1)} ${spacing(2)} ${spacing(1)} ${spacing(1.5)}`, - gap: spacing(1), - - minHeight: 24, - - backgroundColor: '$positive1', - - borderWidth: '1px 1px 1px 8px', - borderStyle: 'solid', - borderColor: '$positive6', - borderRadius: 8, - - fontSize: 14, - fontWeight: 400, - color: '$neutral8', - - svg: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: spacing(1), - - fill: '$primary6', - - borderRadius: 9, - }, - - span: { - width: '95%', - }, - - '& svg:last-child': { - fill: '$primary6', - }, - - variants: { - type: { - info: { - ...getFillBgFontBorderColors({ - backgroundColor: '$info1', - borderColor: '$info6', - svgFillColor: '$info6', - fontColor: '$neutral8', - }), - }, - warning: { - ...getFillBgFontBorderColors({ - backgroundColor: '$warning1', - borderColor: '$warning6', - svgFillColor: '$warning6', - fontColor: '$neutral8', - }), - }, - negative: { - ...getFillBgFontBorderColors({ - backgroundColor: '$negative1', - borderColor: '$negative6', - svgFillColor: '$negative6', - fontColor: '$neutral8', - }), - }, - success: { - ...getFillBgFontBorderColors({ - backgroundColor: '$positive1', - borderColor: '$positive6', - svgFillColor: '$positive6', - fontColor: '$neutral8', - }), - }, - }, - hideBackground: { - true: { - backgroundColor: 'transparent !important', - borderColor: 'transparent !important', - }, - }, - }, -}); +import { StatusType } from '@ion/core/types/status'; +import { css, styled } from 'styled-components'; + +type AlertStyledProps = { + $type: StatusType; + $hideBackground: boolean; + $closable: boolean; +}; + +const getColorsType = (type: StatusType) => { + return { + success: 'positive', + info: 'info', + warning: 'warning', + negative: 'negative', + }[type] as 'positive' | 'info' | 'warning' | 'negative'; +}; + +export const Alert = styled.div` + ${({ theme, $type, $hideBackground, $closable }) => css` + ${theme.utils.flex.spaceBetween(8)}; + ${theme.font.size[14]} + font-weight: 400; + color: ${theme.colors.neutral[8]}; + border-radius: 8px; + + ${!$hideBackground && + css` + height: 4rem; + padding: 8px ${$closable ? '16px' : '16px 8px 12px'}; + border: 1px solid ${theme.colors[getColorsType($type)][6]}; + background-color: ${!$hideBackground && + theme.colors[getColorsType($type)][1]}; + ${!$closable && + `border-left: 8px solid ${theme.colors[getColorsType($type)][6]}`}; + `} + + ${Wrapper} svg { + fill: ${theme.colors[getColorsType($type)][6]}; + } + `} +`; + +export const Wrapper = styled.div` + ${({ theme }) => css` + ${theme.utils.flex.center(8)} + `} +`;