Skip to content

Commit

Permalink
feat(components): theme alert
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao committed May 16, 2024
1 parent fe5f949 commit 73f42bb
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 48 deletions.
50 changes: 47 additions & 3 deletions packages/components/src/Alert/Alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,64 @@ export default {
export const Success: StoryObj<AlertProps> = {
args: {
status: 'success',
children: 'Transaction was succesful!'
children: 'Transaction was succesful!',
onClose: undefined
}
};

export const Info: StoryObj<AlertProps> = {
args: {
status: 'info',
children: 'This is some useful information!',
onClose: undefined
}
};

export const Warning: StoryObj<AlertProps> = {
args: {
status: 'warning',
children: 'This is a warning message...'
children: 'This is a warning message...',
onClose: undefined
}
};

export const Error: StoryObj<AlertProps> = {
args: {
status: 'error',
children: 'Error happened, please contact our support.'
children: 'Error happened, please contact our support.',
onClose: undefined
}
};

export const Outlined: StoryObj<AlertProps> = {
args: {
variant: 'outlined',
children: 'Transaction was succesful!',
onClose: undefined
}
};

export const Filled: StoryObj<AlertProps> = {
args: {
variant: 'filled',
children: 'Transaction was succesful!',
onClose: undefined
}
};

export const WithTitle: StoryObj<AlertProps> = {
args: {
status: 'success',
title: 'This is a successful alert',
children: 'Transaction was succesful!',
onClose: undefined
}
};

export const Closable: StoryObj<AlertProps> = {
args: {
status: 'success',
children: 'Transaction was succesful!',
onClose: () => {}
}
};
84 changes: 71 additions & 13 deletions packages/components/src/Alert/Alert.style.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,96 @@
import { CheckCircle, InformationCircle, Warning } from '@interlay/icons';
import { CheckCircle, ExclamationCircle, InformationCircle, Warning } from '@interlay/icons';
import { AlertStatus, AlertVariant } from '@interlay/theme';
import styled, { css } from 'styled-components';
import { AlertStatus } from '@interlay/theme';

import { Button } from '../Button';
import { Flex } from '../Flex';

type StyledAlertProps = {
$status: AlertStatus;
$variant: AlertVariant;
};

// FIXME: waiting on JAy
const StyledAlert = styled(Flex)<StyledAlertProps>`
${({ $status, theme }) => css`
${({ $status, $variant, theme }) => css`
${theme.alert.base}
${theme.alert.status[$status]}
${theme.alert.status[$status][$variant].base}
`}
`;

const StyledAlertTitle = styled.div<StyledAlertProps>`
${({ $status, $variant, theme }) => css`
${theme.alert.title}
${theme.alert.status[$status][$variant].title}
`}
`;

const StyledInformationCircle = styled(InformationCircle)<StyledAlertProps>`
${({ $status, theme }) => css`
${theme.alert.status[$status]}
${({ $status, $variant, theme }) => css`
${theme.alert.status[$status][$variant].icon}
`}
`;

const StyledCheckCircle = styled(CheckCircle)<StyledAlertProps>`
${({ $status, theme }) => css`
${theme.alert.base}
${theme.alert.status[$status]}
${({ $status, $variant, theme }) => css`
${theme.alert.status[$status][$variant].icon}
`}
`;

const StyledWarning = styled(Warning)<StyledAlertProps>`
${({ $status, theme }) => css`
${theme.alert.status[$status].color}
${({ $status, $variant, theme }) => css`
${theme.alert.status[$status][$variant].icon}
`}
`;

const StyledExclamationCircle = styled(ExclamationCircle)<StyledAlertProps>`
${({ $status, $variant, theme }) => css`
${theme.alert.status[$status][$variant].icon}
`}
`;

const StyledIconWrapper = styled.div`
display: flex;
${({ theme }) => css`
${theme.alert.icon}
`}
`;

export { StyledAlert, StyledInformationCircle, StyledCheckCircle, StyledWarning };
const StyledContent = styled.div`
min-width: 0px;
overflow: auto;
${({ theme }) => css`
${theme.alert.content}
`}
`;

const StyledButton = styled(Button)`
margin-left: auto;
color: inherit;
${({ theme }) => css`
${theme.alert.closeBtn}
`};
`;

const StyledButtonWrapper = styled.div`
margin-left: auto;
padding-left: ${({ theme }) => theme.spacing('xl')};
${({ theme }) => css`
${theme.alert.closeBtnWrapper}
`};
`;

export {
StyledAlert,
StyledAlertTitle,
StyledCheckCircle,
StyledContent,
StyledButtonWrapper,
StyledButton,
StyledExclamationCircle,
StyledIconWrapper,
StyledInformationCircle,
StyledWarning
};
56 changes: 48 additions & 8 deletions packages/components/src/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,72 @@
import { AlertStatus } from '@interlay/theme';
import { ForwardRefExoticComponent } from 'react';
import { XMark } from '@interlay/icons';
import { AlertStatus, AlertVariant, Color, Rounded } from '@interlay/theme';
import { ForwardRefExoticComponent, ReactNode } from 'react';

import { FlexProps } from '../Flex';

import { StyledAlert, StyledCheckCircle, StyledInformationCircle, StyledWarning } from './Alert.style';
import {
StyledAlert,
StyledAlertTitle,
StyledButton,
StyledButtonWrapper,
StyledCheckCircle,
StyledContent,
StyledExclamationCircle,
StyledIconWrapper,
StyledInformationCircle,
StyledWarning
} from './Alert.style';

const iconMap: Record<AlertStatus, ForwardRefExoticComponent<any>> = {
info: StyledInformationCircle,
success: StyledCheckCircle,
error: StyledWarning,
error: StyledExclamationCircle,
warning: StyledWarning
};

type Props = {
status?: AlertStatus;
variant?: AlertVariant;
bordered?: boolean | Color;
rounded?: Rounded;
title?: ReactNode;
onClose?: () => void;
};

type InheritAttrs = Omit<FlexProps, keyof Props>;

type AlertProps = Props & InheritAttrs;

const Alert = ({ status = 'success', children, ...props }: AlertProps): JSX.Element => {
const Alert = ({
status = 'success',
variant = 'standard',
children,
title,
onClose,
...props
}: AlertProps): JSX.Element => {
const Icon = iconMap[status];

return (
<StyledAlert $status={status} alignItems='center' gap='md' role='alert' {...props}>
<Icon $status={status} size='md' />
<div>{children}</div>
<StyledAlert $status={status} $variant={variant} role='alert' {...props}>
<StyledIconWrapper>
<Icon $status={status} $variant={variant} size='s' strokeWidth='2' />
</StyledIconWrapper>
<StyledContent>
{title && (
<StyledAlertTitle $status={status} $variant={variant}>
{title}
</StyledAlertTitle>
)}
{children}
</StyledContent>
{onClose && (
<StyledButtonWrapper>
<StyledButton isIconOnly aria-label='close' size='s' variant='ghost' onPress={onClose}>
<XMark color='inherit' size='s' />
</StyledButton>
</StyledButtonWrapper>
)}
</StyledAlert>
);
};
Expand Down
14 changes: 13 additions & 1 deletion packages/components/src/Alert/__tests__/Alert.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { testA11y, render } from '@interlay/test-utils';
import { render, testA11y } from '@interlay/test-utils';
import { screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';

import { Alert } from '..';

Expand All @@ -12,4 +14,14 @@ describe('Alert', () => {
it('should pass a11y', async () => {
await testA11y(<Alert>Alert</Alert>);
});

it.skip('should emit close event', () => {
const handleClose = jest.fn();

render(<Alert onClose={handleClose}>Alert</Alert>);

userEvent.click(screen.getByRole('button', { name: /close/i }));

expect(handleClose).toHaveBeenCalledTimes(1);
});
});
14 changes: 12 additions & 2 deletions packages/core/theme/src/components/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ import { StyledObject } from 'styled-components';

type AlertStatus = 'info' | 'success' | 'warning' | 'error';

type AlertVariant = 'filled' | 'outlined' | 'standard';

type AlertTheme = {
base: StyledObject<object>;
status: Record<AlertStatus, StyledObject<object>>;
icon: StyledObject<object>;
title: StyledObject<object>;
content: StyledObject<object>;
closeBtn: StyledObject<object>;
closeBtnWrapper: StyledObject<object>;
status: Record<
AlertStatus,
Record<AlertVariant, { base: StyledObject<object>; icon: StyledObject<object>; title: StyledObject<object> }>
>;
};

export type { AlertTheme, AlertStatus };
export type { AlertTheme, AlertStatus, AlertVariant };
12 changes: 9 additions & 3 deletions packages/core/theme/src/core/colors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CSSProperties } from 'styled-components';

type ColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;

type PrimaryColors = Record<`primary-${ColorShade}`, string>;
Expand All @@ -19,9 +21,13 @@ type Palette = {
GreenColors &
RedColors;

type Color = keyof Palette;
type NativeColor = CSSProperties['color'];

type PaletteColor = keyof Palette;

type Color = PaletteColor | NativeColor;

const color = (colors: Palette) => (color: Color | 'inherit') => (color === 'inherit' ? color : colors[color]);
const color = (colors: Palette) => (color: Color) => colors[color as PaletteColor] || color;

export { color };
export type { Color, Palette, PrimaryColors, GreyColors, BlueColors, GreenColors, RedColors };
export type { Color, Palette, PrimaryColors, PaletteColor, GreyColors, BlueColors, GreenColors, RedColors };
1 change: 1 addition & 0 deletions packages/core/theme/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type {
AccordionVariants,
ProgressBarSize,
AlertStatus,
AlertVariant,
TokenInputSize,
TabsSize,
SwitchSize,
Expand Down
Loading

0 comments on commit 73f42bb

Please sign in to comment.