Skip to content

Instantly share code, notes, and snippets.

@crisu83
Last active May 19, 2021 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crisu83/551ceb406a18ae5fdfc6ae3849dae796 to your computer and use it in GitHub Desktop.
Save crisu83/551ceb406a18ae5fdfc6ae3849dae796 to your computer and use it in GitHub Desktop.
Design system written in TypeScript and built on top of Style System (and Styled Components).
// Example button component
import shouldForwardProp from '@styled-system/should-forward-prop';
import {darken, lighten} from 'polished';
import React, {FunctionComponent, forwardRef} from 'react';
import {variant as variantFn} from 'styled-system';
import {
Box,
BoxPropsWithRef,
StyledProps,
Theme,
styled,
useTheme,
} from '@/design-system';
type ButtonVariant =
| 'link'
| 'none'
| 'outlinePrimary'
| 'outlineSecondary'
| 'primary'
| 'secondary';
const getVariantProps = (variant: ButtonVariant, theme: Theme) => {
switch (variant) {
case 'link':
return {
bg: 'transparent',
color: 'primary',
};
case 'none':
return {};
case 'outlinePrimary':
return {
':disabled': {color: lighten(0.2, theme.colors.primary)},
':hover': {color: darken(0.1, theme.colors.primary)},
bg: 'transparent',
boxShadow: '0px 0px 0px 2px inset',
color: 'primary',
};
case 'outlineSecondary':
return {
':disabled': {color: lighten(0.2, theme.colors.secondary)},
':hover': {color: darken(0.1, theme.colors.secondary)},
bg: 'transparent',
boxShadow: '0px 0px 0px 2px inset',
color: 'secondary',
};
case 'secondary':
return {
':disabled': {bg: lighten(0.2, theme.colors.secondary)},
':hover': {bg: darken(0.1, theme.colors.secondary)},
bg: 'secondary',
color: 'noContrast',
};
case 'primary':
default:
return {
':disabled': {bg: lighten(0.2, theme.colors.primary)},
':hover': {bg: darken(0.1, theme.colors.primary)},
bg: 'primary',
color: 'noContrast',
};
}
};
export type ButtonProps = BoxPropsWithRef<'button'> & {
variant?: ButtonVariant;
};
const StyledComponent = styled(Box).withConfig({
shouldForwardProp: shouldForwardProp as any,
})(({theme}: StyledProps) =>
variantFn({
scale: 'buttons',
variants: {
none: getVariantProps('none', theme),
primary: getVariantProps('primary', theme),
outlinePrimary: getVariantProps('outlinePrimary', theme),
secondary: getVariantProps('secondary', theme),
outlineSecondary: getVariantProps('outlineSecondary', theme),
link: getVariantProps('link', theme),
},
})
) as FunctionComponent<ButtonProps>;
export const Button = forwardRef<any, ButtonProps>(
({children, disabled, sx, variant = 'primary', ...props}, ref) => {
const theme = useTheme();
const {bg, color} = getVariantProps(variant, theme);
return (
<StyledComponent
as="button"
border={0}
borderRadius="button"
disabled={disabled}
display="inline-block"
fontSize={1}
fontWeight="bold"
lineHeight="inherit"
minWidth={120}
px={3}
py={2}
ref={ref}
sx={{
appearance: 'none',
bg: bg ?? 'transparent',
color: color ?? 'primary',
cursor: !disabled ? 'pointer' : 'not-allowed',
outline: 'none',
textDecoration: 'none',
transition: 'background-color 0.3s, color 0.3s',
whiteSpace: 'nowrap',
...sx,
}}
textAlign="center"
type="button"
variant={variant}
{...props}
>
{children}
</StyledComponent>
);
}
);
Button.displayName = 'Button';
/* eslint-disable @typescript-eslint/no-explicit-any */
// Utility components on top of styled-system.
// Inspired by [Rebass](https://rebassjs.org/) and adopted to our needs.
// See: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase
import css, {SystemStyleObject} from '@styled-system/css';
import shouldForwardProp from '@styled-system/should-forward-prop';
import {
ComponentPropsWithRef,
ComponentPropsWithoutRef,
ElementType,
FunctionComponent,
} from 'react';
import {
ThemedStyledInterface,
ThemedStyledProps,
default as baseStyled,
useTheme as baseUseTheme,
} from 'styled-components';
import {
BorderProps,
ColorProps,
FlexboxProps,
GridProps as StyledSystemGridProps,
LayoutProps,
ShadowProps,
SpaceProps,
TypographyProps,
border,
color,
compose,
flexbox,
grid,
layout,
shadow,
space,
typography,
} from 'styled-system';
import {Theme} from '@/design-system';
export const styled: ThemedStyledInterface<Theme> = baseStyled;
export const useTheme = () => baseUseTheme() as Theme;
export type StyledProps<P = {}> = ThemedStyledProps<P, Theme>;
type SxProp = SystemStyleObject;
type BaseProps = {
as?: string;
ref?: any;
sx?: SxProp;
variant?: string;
};
const sx = (props: {sx: SxProp; theme: Theme}) =>
css(props.sx)(props.theme as any);
export type BoxProps = BaseProps &
BorderProps &
ColorProps &
LayoutProps &
ShadowProps &
SpaceProps &
TypographyProps;
export type BoxPropsWithoutRef<T extends ElementType<any>> = BoxProps &
ComponentPropsWithoutRef<T>;
export type BoxPropsWithRef<T extends ElementType<any>> = BoxProps &
ComponentPropsWithRef<T>;
export const Box = styled('div').withConfig({
shouldForwardProp: shouldForwardProp as any,
})(
{
boxSizing: 'border-box',
margin: 0,
minWidth: 0,
},
sx,
compose(border, color, layout, shadow, space, typography)
) as FunctionComponent<BoxPropsWithoutRef<'div'>>;
export type FlexProps = BaseProps & BoxPropsWithoutRef<'div'> & FlexboxProps;
export const Flex = styled(Box)(
{
display: 'flex',
},
flexbox
) as FunctionComponent<FlexProps>;
export const FlexColumn = styled(Flex)({
flexDirection: 'column',
}) as FunctionComponent<FlexProps>;
export const FlexRow = styled(Flex)({
flexDirection: 'row',
}) as FunctionComponent<FlexProps>;
export type GridProps = BaseProps & BoxProps & StyledSystemGridProps;
export const Grid = styled(Box)(
{
display: 'grid',
},
grid
) as FunctionComponent<GridProps>;
export type TextProps = BaseProps &
BorderProps &
ColorProps &
LayoutProps &
ShadowProps &
SpaceProps &
TypographyProps;
export type TextPropsWithoutRef<T extends ElementType<any>> =
ComponentPropsWithoutRef<T> & TextProps;
export type TextPropsWithRef<T extends ElementType<any>> =
ComponentPropsWithRef<T> & TextProps;
export const Text = styled('span').withConfig({
shouldForwardProp: shouldForwardProp as any,
})(
sx,
compose(border, color, layout, space, shadow, typography)
) as FunctionComponent<TextPropsWithoutRef<'span'>>;
export * from './core';
export {Button} from './Button';
export type {ButtonProps} from './Button';
// More components ...
export {theme} from './theme';
export type {Theme} from './theme';
export enum ThemeColor {
Error = '#f00',
Success = '#0f0',
Warning = '#ff0',
}
export const theme = {
borderWidths: {
large: 8,
medium: 5,
small: 3,
thin: 1,
},
colors: {
error: '#f00',
success: '#0f0',
warning: '#ff0',
},
fontWeights: {
bold: 700,
light: 300,
normal: 400,
},
fonts: {
body: "'Open Sans', sans-serif",
},
lineHeights: {
body: 1.5,
},
radii: {
large: 20,
medium: 10,
none: 0,
round: '100%',
small: 5,
},
shadows: {
large: '0 0 30px rgba(0, 0, 0, 0.1)',
medium: '0 0 20px rgba(0, 0, 0, 0.1)',
small: '0 0 10px rgba(0, 0, 0, 0.1)',
},
sizes: {
full: '100%',
},
};
export type Theme = typeof theme;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment