Skip to content

Commit d2bed73

Browse files
feat(trial): Reusable banner components (#7106)
* Create MessageStripe component * Fix Toast Button prop props * Create Banner component and examples
1 parent 5b68c28 commit d2bed73

File tree

9 files changed

+306
-11
lines changed

9 files changed

+306
-11
lines changed

packages/components/.storybook/config.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const GlobalStyle = createGlobalStyle`
5151
min-height: 100%;
5252
height: 100%;
5353
font-size: 16px;
54-
width: 400px;
5554
margin: 0;
5655
background-color: ${({ theme }: { theme: Theme }) =>
5756
theme.colors.sideBar.background};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
3+
import { Banner } from './Banner';
4+
import { Stack } from '../Stack';
5+
import { Text } from '../Text';
6+
import { Button } from '../Button';
7+
import { Icon } from '../Icon';
8+
import { Grid, Column } from '../Grid';
9+
10+
export default {
11+
title: 'components/facelift/Banner',
12+
component: Banner,
13+
};
14+
15+
export const WithDismiss = () => (
16+
<Banner onDismiss={() => {}}>
17+
You are no longer in a Pro team. This private repo is in view mode only.
18+
Make it public or upgrade.
19+
</Banner>
20+
);
21+
22+
export const WithoutDismiss = () => (
23+
<Banner>
24+
You are no longer in a Pro team. This private repo is in view mode only.
25+
Make it public or upgrade.
26+
</Banner>
27+
);
28+
29+
export const ExampleUsage = () => (
30+
<Banner onDismiss={() => {}}>
31+
<Stack gap={2} justify="space-between">
32+
<Stack direction="vertical" gap={8} justify="space-between">
33+
<Text fontFamily="everett" size={24}>
34+
<Text color="#EDFFA5" weight="600" block>
35+
Upgrade to <Text css={{ textTransform: 'uppercase' }}>pro</Text>
36+
</Text>
37+
<Text block>Enjoy the full CodeSandbox experience.</Text>
38+
</Text>
39+
<Button autoWidth>Learn more</Button>
40+
</Stack>
41+
<Stack align="flex-end">
42+
<Grid
43+
as="ul"
44+
columnGap={3}
45+
rowGap={3}
46+
css={{
47+
color: '#EBEBEB',
48+
margin: 0,
49+
padding: 0,
50+
listStyleType: 'none',
51+
}}
52+
>
53+
<Column as="li" span={6}>
54+
<Stack gap={4}>
55+
<Icon name="profile" />
56+
<Text size={13} lineHeight="16px">
57+
Up to 20 editors
58+
</Text>
59+
</Stack>
60+
</Column>
61+
<Column as="li" span={6}>
62+
<Stack gap={4}>
63+
<Icon name="profile" />
64+
<Text size={13} lineHeight="16px">
65+
Private NPM packages
66+
</Text>
67+
</Stack>
68+
</Column>
69+
<Column as="li" span={6}>
70+
<Stack gap={4}>
71+
<Icon name="profile" />
72+
<Text size={13} lineHeight="16px">
73+
Unlimited sandboxes
74+
</Text>
75+
</Stack>
76+
</Column>
77+
<Column as="li" span={6}>
78+
<Stack gap={4}>
79+
<Icon name="profile" />
80+
<Text size={13} lineHeight="16px">
81+
Advanced privacy settings
82+
</Text>
83+
</Stack>
84+
</Column>
85+
<Column as="li" span={6}>
86+
<Stack gap={4}>
87+
<Icon name="profile" />
88+
<Text size={13} lineHeight="16px">
89+
Unlimited repositories
90+
</Text>
91+
</Stack>
92+
</Column>
93+
<Column as="li" span={6}>
94+
<Stack gap={4}>
95+
<Icon name="profile" />
96+
<Text size={13} lineHeight="16px">
97+
6GB RAM, 12GB Disk, 4 vCPUs
98+
</Text>
99+
</Stack>
100+
</Column>
101+
</Grid>
102+
</Stack>
103+
</Stack>
104+
</Banner>
105+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
3+
import { Element } from '../Element';
4+
import { IconButton } from '../IconButton';
5+
6+
interface BannerProps {
7+
children: React.ReactNode;
8+
onDismiss?: () => void;
9+
}
10+
11+
export const Banner = ({ children, onDismiss }: BannerProps) => {
12+
return (
13+
<Element
14+
css={{
15+
position: 'relative',
16+
padding: [4, 6, 8],
17+
backgroundColor: '#252525',
18+
borderRadius: '4px',
19+
}}
20+
>
21+
{children}
22+
23+
{onDismiss ? (
24+
<Element
25+
css={{ position: 'absolute', right: [1, 2, 4], top: [1, 2, 4] }}
26+
>
27+
<IconButton
28+
name="cross"
29+
variant="round"
30+
title="Dismiss"
31+
onClick={onDismiss}
32+
/>
33+
</Element>
34+
) : null}
35+
</Element>
36+
);
37+
};

packages/components/src/components/Button/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ const variantStyles = {
5757
background: theme => theme.colors.dangerButton.hoverBackground,
5858
},
5959
},
60+
light: {
61+
backgroundColor: '#FFFFFF',
62+
color: '#0E0E0E',
63+
64+
':hover': {
65+
backgroundColor: '#E0E0E0', // three up in the gray scale (gray[400])
66+
},
67+
},
68+
dark: {
69+
backgroundColor: '#0E0E0E',
70+
color: '#FFFFFF',
71+
72+
':hover': {
73+
backgroundColor: '#252525', // three down in the black scale (black[500])
74+
},
75+
},
6076
};
6177

6278
const commonStyles = {
@@ -101,7 +117,7 @@ const commonStyles = {
101117
export interface ButtonProps
102118
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
103119
IElementProps {
104-
variant?: 'primary' | 'secondary' | 'link' | 'danger';
120+
variant?: 'primary' | 'secondary' | 'link' | 'danger' | 'light' | 'dark';
105121
loading?: boolean;
106122
href?: string;
107123
rel?: string; // Only use when using href and as="a"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { MessageStripe } from './MessageStripe';
3+
4+
export default {
5+
title: 'components/facelift/MessageStripe',
6+
component: MessageStripe,
7+
};
8+
9+
export const TrialVariant = () => (
10+
<MessageStripe variant="trial">
11+
You are no longer in a Pro team. This private repo is in view mode only.
12+
Make it public or upgrade.
13+
<MessageStripe.Action>Upgrade now</MessageStripe.Action>
14+
</MessageStripe>
15+
);
16+
17+
export const WarningVariant = () => (
18+
<MessageStripe variant="warning">
19+
There are some issues with your payment. Please update your payment details.
20+
<MessageStripe.Action>Update payment</MessageStripe.Action>
21+
</MessageStripe>
22+
);
23+
24+
export const WithoutAction = () => (
25+
<MessageStripe variant="warning">
26+
You are no longer in a Pro team. This private repo is in view mode only,
27+
contact your team admin to upgrade.
28+
</MessageStripe>
29+
);
30+
31+
export const SpaceBetween = () => (
32+
<MessageStripe variant="trial" justify="space-between">
33+
There are some issues with your payment. Please update your payment details.
34+
<MessageStripe.Action>Update payment</MessageStripe.Action>
35+
</MessageStripe>
36+
);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react';
2+
3+
import { Element } from '../Element';
4+
import { Stack } from '../Stack';
5+
import { Button } from '../Button';
6+
import { Text } from '../Text';
7+
8+
type Variant = 'trial' | 'warning';
9+
10+
interface MessageActionProps
11+
extends Omit<React.ComponentProps<typeof Button>, 'variant'> {
12+
children: string;
13+
variant?: Variant;
14+
}
15+
16+
export const MessageAction = ({
17+
children,
18+
variant,
19+
...buttonProps
20+
}: MessageActionProps) => {
21+
return (
22+
<div>
23+
<Button
24+
variant={({ trial: 'light', warning: 'dark' } as const)[variant]}
25+
{...buttonProps}
26+
>
27+
{children}
28+
</Button>
29+
</div>
30+
);
31+
};
32+
33+
const backgroundVariants = {
34+
trial: '#644ED7',
35+
warning: '#F7CC66',
36+
};
37+
38+
const colorVariants = {
39+
trial: 'inherit',
40+
warning: '#0E0E0E',
41+
};
42+
43+
interface MessageStripeProps {
44+
children: React.ReactNode;
45+
variant?: Variant;
46+
justify?: 'center' | 'space-between';
47+
}
48+
49+
const MessageStripe = ({
50+
children,
51+
variant = 'trial',
52+
justify = 'center',
53+
}: MessageStripeProps) => {
54+
let hasAction: boolean;
55+
56+
const augmentedChildren = React.Children.map(children, child => {
57+
if (React.isValidElement(child) && child.type === MessageAction) {
58+
hasAction = true;
59+
60+
return React.cloneElement<Partial<MessageActionProps>>(child, {
61+
variant,
62+
});
63+
}
64+
65+
return (
66+
<Text size={13} lineHeight="16px">
67+
{child}
68+
</Text>
69+
);
70+
});
71+
72+
return (
73+
<Element
74+
paddingY={hasAction ? 2 : 3}
75+
paddingX={4}
76+
css={{
77+
backgroundColor: backgroundVariants[variant],
78+
color: colorVariants[variant],
79+
}}
80+
>
81+
<Stack direction="horizontal" justify={justify} align="center" gap={2}>
82+
{augmentedChildren}
83+
</Stack>
84+
</Element>
85+
);
86+
};
87+
88+
MessageStripe.Action = MessageAction;
89+
90+
export { MessageStripe };

packages/components/src/components/Stack/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ export const Stack = styled(Element)<{
99
justify?: React.CSSProperties['justifyContent'];
1010
align?: React.CSSProperties['alignItems'];
1111
inline?: boolean;
12-
}>(({ gap = 0, direction = 'horizontal', justify, align, inline }) =>
12+
wrap?: boolean;
13+
}>(({ gap = 0, direction = 'horizontal', justify, align, inline, wrap }) =>
1314
css({
1415
display: inline ? 'inline-flex' : 'flex',
1516
flexDirection: direction === 'horizontal' ? 'row' : 'column',
1617
justifyContent: justify,
1718
alignItems: align,
19+
flexWrap: wrap ? 'wrap' : 'nowrap',
1820

1921
'> *:not(:last-child)': {
2022
[direction === 'horizontal' ? 'marginRight' : 'marginBottom']: gap,

packages/components/src/components/Text/index.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const overflowStyles = {
1616
whiteSpace: 'nowrap',
1717
};
1818

19+
const fontFamilies = {
20+
inter: 'Inter, sans-serif',
21+
everett: 'Everett, sans-serif',
22+
};
23+
1924
export interface ITextProps extends React.HTMLAttributes<HTMLSpanElement> {
2025
size?: number;
2126
align?: string;
@@ -25,6 +30,9 @@ export interface ITextProps extends React.HTMLAttributes<HTMLSpanElement> {
2530
maxWidth?: number | string;
2631
variant?: 'body' | 'muted' | 'danger' | 'active';
2732
dateTime?: string;
33+
lineHeight?: string;
34+
color?: string;
35+
fontFamily?: 'inter' | 'everett';
2836
}
2937

3038
export const Text = styled(Element).attrs(p => ({
@@ -38,17 +46,21 @@ export const Text = styled(Element).attrs(p => ({
3846
block,
3947
variant = 'body',
4048
maxWidth,
49+
lineHeight,
50+
color,
51+
fontFamily,
4152
...props
4253
}) =>
4354
css({
4455
fontSize: size || 'inherit', // from theme.fontSizes
4556
textAlign: align || 'left',
4657
fontWeight: weight || null, // from theme.fontWeights
47-
lineHeight: 'normal',
58+
lineHeight: lineHeight || 'normal',
4859
fontStyle: fontStyle || null, // from theme.fontWeights
4960
display: block || maxWidth ? 'block' : 'inline',
50-
color: variants[variant],
61+
color: color || variants[variant],
5162
maxWidth,
63+
fontFamily: fontFamilies[fontFamily],
5264
...(maxWidth ? overflowStyles : {}),
5365
})
5466
);

packages/notifications/src/component/Toast.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { Stack, Element, Text } from '@codesandbox/components';
2+
import { Stack, Element, Text, ButtonProps } from '@codesandbox/components';
33

44
import { NotificationToast } from './Toasts';
55
import { NotificationStatus } from '../state';
@@ -39,11 +39,9 @@ export interface IColors {
3939
[NotificationStatus.NOTICE]: string;
4040
}
4141

42-
export type IButtonType = React.ComponentType<{
43-
style?: React.CSSProperties;
44-
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
45-
variant: 'primary' | 'secondary' | 'link' | 'danger';
46-
}>;
42+
export type IButtonType = React.ComponentType<
43+
Pick<ButtonProps, 'style' | 'onClick' | 'variant'>
44+
>;
4745

4846
export type Props = {
4947
toast: NotificationToast;

0 commit comments

Comments
 (0)