Skip to content

feat(trial): Reusable banner components #7106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/components/.storybook/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const GlobalStyle = createGlobalStyle`
min-height: 100%;
height: 100%;
font-size: 16px;
width: 400px;
margin: 0;
background-color: ${({ theme }: { theme: Theme }) =>
theme.colors.sideBar.background};
Expand Down
105 changes: 105 additions & 0 deletions packages/components/src/components/Banner/Banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';

import { Banner } from './Banner';
import { Stack } from '../Stack';
import { Text } from '../Text';
import { Button } from '../Button';
import { Icon } from '../Icon';
import { Grid, Column } from '../Grid';

export default {
title: 'components/facelift/Banner',
component: Banner,
};

export const WithDismiss = () => (
<Banner onDismiss={() => {}}>
You are no longer in a Pro team. This private repo is in view mode only.
Make it public or upgrade.
</Banner>
);

export const WithoutDismiss = () => (
<Banner>
You are no longer in a Pro team. This private repo is in view mode only.
Make it public or upgrade.
</Banner>
);

export const ExampleUsage = () => (
<Banner onDismiss={() => {}}>
<Stack gap={2} justify="space-between">
<Stack direction="vertical" gap={8} justify="space-between">
<Text fontFamily="everett" size={24}>
<Text color="#EDFFA5" weight="600" block>
Upgrade to <Text css={{ textTransform: 'uppercase' }}>pro</Text>
</Text>
<Text block>Enjoy the full CodeSandbox experience.</Text>
</Text>
<Button autoWidth>Learn more</Button>
</Stack>
<Stack align="flex-end">
<Grid
as="ul"
columnGap={3}
rowGap={3}
css={{
color: '#EBEBEB',
margin: 0,
padding: 0,
listStyleType: 'none',
}}
>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
Up to 20 editors
</Text>
</Stack>
</Column>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
Private NPM packages
</Text>
</Stack>
</Column>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
Unlimited sandboxes
</Text>
</Stack>
</Column>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
Advanced privacy settings
</Text>
</Stack>
</Column>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
Unlimited repositories
</Text>
</Stack>
</Column>
<Column as="li" span={6}>
<Stack gap={4}>
<Icon name="profile" />
<Text size={13} lineHeight="16px">
6GB RAM, 12GB Disk, 4 vCPUs
</Text>
</Stack>
</Column>
</Grid>
</Stack>
</Stack>
</Banner>
);
37 changes: 37 additions & 0 deletions packages/components/src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

import { Element } from '../Element';
import { IconButton } from '../IconButton';

interface BannerProps {
children: React.ReactNode;
onDismiss?: () => void;
}

export const Banner = ({ children, onDismiss }: BannerProps) => {
return (
<Element
css={{
position: 'relative',
padding: [4, 6, 8],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a styled-component or a styled-system feature? I thought it only works with the css() function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styled System! It does only work with the css() function, yes. The Element component uses it to generate the component, so we can pass it with the css prop:

export const Element = styled.div<IElementProps>(props =>
  css({
    // stuff
    ...(props.css || {}),
  })
);

backgroundColor: '#252525',
borderRadius: '4px',
}}
>
{children}

{onDismiss ? (
<Element
css={{ position: 'absolute', right: [1, 2, 4], top: [1, 2, 4] }}
>
<IconButton
name="cross"
variant="round"
title="Dismiss"
onClick={onDismiss}
/>
</Element>
) : null}
</Element>
);
};
18 changes: 17 additions & 1 deletion packages/components/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ const variantStyles = {
background: theme => theme.colors.dangerButton.hoverBackground,
},
},
light: {
backgroundColor: '#FFFFFF',
color: '#0E0E0E',

':hover': {
backgroundColor: '#E0E0E0', // three up in the gray scale (gray[400])
},
},
dark: {
backgroundColor: '#0E0E0E',
color: '#FFFFFF',

':hover': {
backgroundColor: '#252525', // three down in the black scale (black[500])
},
},
};

const commonStyles = {
Expand Down Expand Up @@ -101,7 +117,7 @@ const commonStyles = {
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
IElementProps {
variant?: 'primary' | 'secondary' | 'link' | 'danger';
variant?: 'primary' | 'secondary' | 'link' | 'danger' | 'light' | 'dark';
loading?: boolean;
href?: string;
rel?: string; // Only use when using href and as="a"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { MessageStripe } from './MessageStripe';

export default {
title: 'components/facelift/MessageStripe',
component: MessageStripe,
};

export const TrialVariant = () => (
<MessageStripe variant="trial">
You are no longer in a Pro team. This private repo is in view mode only.
Make it public or upgrade.
<MessageStripe.Action>Upgrade now</MessageStripe.Action>
</MessageStripe>
);

export const WarningVariant = () => (
<MessageStripe variant="warning">
There are some issues with your payment. Please update your payment details.
<MessageStripe.Action>Update payment</MessageStripe.Action>
</MessageStripe>
);

export const WithoutAction = () => (
<MessageStripe variant="warning">
You are no longer in a Pro team. This private repo is in view mode only,
contact your team admin to upgrade.
</MessageStripe>
);

export const SpaceBetween = () => (
<MessageStripe variant="trial" justify="space-between">
There are some issues with your payment. Please update your payment details.
<MessageStripe.Action>Update payment</MessageStripe.Action>
</MessageStripe>
);
90 changes: 90 additions & 0 deletions packages/components/src/components/MessageStripe/MessageStripe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';

import { Element } from '../Element';
import { Stack } from '../Stack';
import { Button } from '../Button';
import { Text } from '../Text';

type Variant = 'trial' | 'warning';

interface MessageActionProps
extends Omit<React.ComponentProps<typeof Button>, 'variant'> {
children: string;
variant?: Variant;
}

export const MessageAction = ({
children,
variant,
...buttonProps
}: MessageActionProps) => {
return (
<div>
<Button
variant={({ trial: 'light', warning: 'dark' } as const)[variant]}
{...buttonProps}
>
{children}
</Button>
</div>
);
};

const backgroundVariants = {
trial: '#644ED7',
warning: '#F7CC66',
};

const colorVariants = {
trial: 'inherit',
warning: '#0E0E0E',
};

interface MessageStripeProps {
children: React.ReactNode;
variant?: Variant;
justify?: 'center' | 'space-between';
}

const MessageStripe = ({
children,
variant = 'trial',
justify = 'center',
}: MessageStripeProps) => {
let hasAction: boolean;

const augmentedChildren = React.Children.map(children, child => {
if (React.isValidElement(child) && child.type === MessageAction) {
hasAction = true;

return React.cloneElement<Partial<MessageActionProps>>(child, {
variant,
});
}

return (
<Text size={13} lineHeight="16px">
{child}
</Text>
);
});

return (
<Element
paddingY={hasAction ? 2 : 3}
paddingX={4}
css={{
backgroundColor: backgroundVariants[variant],
color: colorVariants[variant],
}}
>
<Stack direction="horizontal" justify={justify} align="center" gap={2}>
{augmentedChildren}
</Stack>
</Element>
);
};

MessageStripe.Action = MessageAction;

export { MessageStripe };
4 changes: 3 additions & 1 deletion packages/components/src/components/Stack/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export const Stack = styled(Element)<{
justify?: React.CSSProperties['justifyContent'];
align?: React.CSSProperties['alignItems'];
inline?: boolean;
}>(({ gap = 0, direction = 'horizontal', justify, align, inline }) =>
wrap?: boolean;
}>(({ gap = 0, direction = 'horizontal', justify, align, inline, wrap }) =>
css({
display: inline ? 'inline-flex' : 'flex',
flexDirection: direction === 'horizontal' ? 'row' : 'column',
justifyContent: justify,
alignItems: align,
flexWrap: wrap ? 'wrap' : 'nowrap',

'> *:not(:last-child)': {
[direction === 'horizontal' ? 'marginRight' : 'marginBottom']: gap,
Expand Down
16 changes: 14 additions & 2 deletions packages/components/src/components/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const overflowStyles = {
whiteSpace: 'nowrap',
};

const fontFamilies = {
inter: 'Inter, sans-serif',
everett: 'Everett, sans-serif',
};

export interface ITextProps extends React.HTMLAttributes<HTMLSpanElement> {
size?: number;
align?: string;
Expand All @@ -25,6 +30,9 @@ export interface ITextProps extends React.HTMLAttributes<HTMLSpanElement> {
maxWidth?: number | string;
variant?: 'body' | 'muted' | 'danger' | 'active';
dateTime?: string;
lineHeight?: string;
color?: string;
fontFamily?: 'inter' | 'everett';
}

export const Text = styled(Element).attrs(p => ({
Expand All @@ -38,17 +46,21 @@ export const Text = styled(Element).attrs(p => ({
block,
variant = 'body',
maxWidth,
lineHeight,
color,
fontFamily,
...props
}) =>
css({
fontSize: size || 'inherit', // from theme.fontSizes
textAlign: align || 'left',
fontWeight: weight || null, // from theme.fontWeights
lineHeight: 'normal',
lineHeight: lineHeight || 'normal',
fontStyle: fontStyle || null, // from theme.fontWeights
display: block || maxWidth ? 'block' : 'inline',
color: variants[variant],
color: color || variants[variant],
maxWidth,
fontFamily: fontFamilies[fontFamily],
...(maxWidth ? overflowStyles : {}),
})
);
10 changes: 4 additions & 6 deletions packages/notifications/src/component/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { Stack, Element, Text } from '@codesandbox/components';
import { Stack, Element, Text, ButtonProps } from '@codesandbox/components';

import { NotificationToast } from './Toasts';
import { NotificationStatus } from '../state';
Expand Down Expand Up @@ -39,11 +39,9 @@ export interface IColors {
[NotificationStatus.NOTICE]: string;
}

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

export type Props = {
toast: NotificationToast;
Expand Down