Skip to content

Instantly share code, notes, and snippets.

@jord-goldberg
Last active March 15, 2021 17:45
Show Gist options
  • Save jord-goldberg/6c46ffce82530525e5da6783e8fe02c5 to your computer and use it in GitHub Desktop.
Save jord-goldberg/6c46ffce82530525e5da6783e8fe02c5 to your computer and use it in GitHub Desktop.
Opinionated styled-system css and variant function typings that enforce use of theme-defined values.
/**
* `Multiples`, `Scales` & `Aliases` types from
* [@styled-system/css](https://github.com/styled-system/styled-system/blob/master/packages/css/src/index.js)
*/
declare module "@styled-system/css" {
import * as CSS from "csstype";
export interface Theme {}
type CSSPropertiesFallback = CSS.PropertiesFallback<number | string>;
type CSSProperties = {
[Prop in keyof CSSPropertiesFallback]:
| CSSPropertiesFallback[Prop]
| Array<Extract<CSSPropertiesFallback[Prop], string>>;
};
type CSSPseudos<CSSObjectType = CSSObject> = {
[Prop in CSS.Pseudos]?: CSSObjectType;
};
interface NestedCSSObject<CSSObjectType = CSSObject> {
[propertyName: string]:
| undefined
| null
| boolean
| number
| string
| Array<number | string>
| CSSObjectType;
}
export interface CSSObject
extends CSSProperties,
CSSPseudos,
NestedCSSObject {}
type Multiples = {
marginX: ["marginLeft", "marginRight"];
marginY: ["marginTop", "marginBottom"];
paddingX: ["paddingLeft", "paddingRight"];
paddingY: ["paddingTop", "paddingBottom"];
size: ["width", "height"];
};
type MaybeMultiple<Prop> = keyof Multiples[Prop] extends never
? Prop
: Multiples[Prop][0];
type Scales = {
color: "colors";
backgroundColor: "colors";
borderColor: "colors";
margin: "space";
marginTop: "space";
marginRight: "space";
marginBottom: "space";
marginLeft: "space";
marginX: "space";
marginY: "space";
padding: "space";
paddingTop: "space";
paddingRight: "space";
paddingBottom: "space";
paddingLeft: "space";
paddingX: "space";
paddingY: "space";
top: "space";
right: "space";
bottom: "space";
left: "space";
gridGap: "space";
gridColumnGap: "space";
gridRowGap: "space";
gap: "space";
columnGap: "space";
rowGap: "space";
fontFamily: "fonts";
fontSize: "fontSizes";
fontWeight: "fontWeights";
lineHeight: "lineHeights";
letterSpacing: "letterSpacings";
border: "borders";
borderTop: "borders";
borderRight: "borders";
borderBottom: "borders";
borderLeft: "borders";
borderWidth: "borderWidths";
borderStyle: "borderStyles";
borderRadius: "radii";
borderTopRightRadius: "radii";
borderTopLeftRadius: "radii";
borderBottomRightRadius: "radii";
borderBottomLeftRadius: "radii";
borderTopWidth: "borderWidths";
borderTopColor: "colors";
borderTopStyle: "borderStyles";
borderBottomWidth: "borderWidths";
borderBottomColor: "colors";
borderBottomStyle: "borderStyles";
borderLeftWidth: "borderWidths";
borderLeftColor: "colors";
borderLeftStyle: "borderStyles";
borderRightWidth: "borderWidths";
borderRightColor: "colors";
borderRightStyle: "borderStyles";
outlineColor: "colors";
boxShadow: "shadows";
textShadow: "shadows";
zIndex: "zIndices";
width: "sizes";
minWidth: "sizes";
maxWidth: "sizes";
height: "sizes";
minHeight: "sizes";
maxHeight: "sizes";
flexBasis: "sizes";
size: "sizes";
// svg
fill: "colors";
stroke: "colors";
};
type ScalesCSSObject = {
[Scale in keyof Scales]?: keyof Theme[Scales[Scale]] extends never
? CSSProperties[MaybeMultiple<Scale>]
: keyof Theme[Scales[Scale]] | Array<keyof Theme[Scales[Scale]]>;
};
type Aliases = {
bg: "backgroundColor";
m: "margin";
mt: "marginTop";
mr: "marginRight";
mb: "marginBottom";
ml: "marginLeft";
mx: "marginX";
my: "marginY";
p: "padding";
pt: "paddingTop";
pr: "paddingRight";
pb: "paddingBottom";
pl: "paddingLeft";
px: "paddingX";
py: "paddingY";
};
type AliasesCSSObject = {
[Alias in keyof Aliases]?: ScalesCSSObject[Aliases[Alias]];
};
export interface StyledCSSObject
extends ScalesCSSObject,
AliasesCSSObject,
Omit<CSSProperties, keyof ScalesCSSObject | keyof AliasesCSSObject>,
CSSPseudos<StyledCSSObject>,
NestedCSSObject<StyledCSSObject> {
variant?: string;
}
/**
* Opinionated typing of the [@styled-system/css](https://styled-system.com/css) function.
*
* @param styleObject CSS-in-JS style object.
*
* For CSS properties with a corresponding Styled System scale, you are
* constrained to using the scale keys in your theme _if_ your theme
* has that scale defined.
*/
export function css<Props>(
styleObject: StyledCSSObject
): (props: Props) => CSSObject;
}
declare module "@styled-system/variant" {
import { CSSObject, StyledCSSObject, Theme } from "@styled-system/css";
export function variant<Props, Prop extends string = "variant">(args: {
scale?: keyof Theme;
prop?: Prop;
variants?: Prop extends keyof Props
? Record<Props[Prop], StyledCSSObject>
: never;
}): (props: Props) => CSSObject;
}
import { Theme } from "@emotion/react";
import styled from "@emotion/styled";
import { css } from "@styled-system/css";
import { variant } from "@styled-system/variant";
interface StyledButtonProps {
variant?: "solid" | "outlined" | "text";
color?: "gray" | "primary" | "secondary";
radius?: keyof Theme["radii"];
}
const defaultProps: StyledButtonProps = {
variant: "solid",
color: "gray",
radius: "circle",
};
const transparentBg = {
bg: "transparent",
"&:hover": {
bg: "muted",
},
} as const;
export const StyledButton = styled("button")<StyledButtonProps>(
(props) =>
css({
appearance: "none",
py: 1,
px: 2,
fontSize: 1,
border: "2px solid transparent",
borderRadius: props.radius,
lineHeight: "body",
transition: "opacity 0.3s",
":focus": {
outline: "none",
borderColor: "highlight",
},
":disabled": {
opacity: 0.2,
},
":not(:disabled)": {
cursor: "pointer",
":hover": {
opacity: 0.8,
},
},
}),
({ color }) =>
variant({
variants: {
solid: {
bg: color,
color: color === "gray" ? "text" : "background",
},
text: {
color,
...transparentBg,
},
outlined: {
color,
borderColor: color,
...transparentBg,
},
},
})
);
StyledButton.defaultProps = defaultProps;
export default StyledButton;
import "@emotion/react";
type AppTheme = typeof import("./theme").default;
declare module "@emotion/react" {
export interface Theme extends AppTheme {}
}
declare module "@styled-system/css" {
export interface Theme extends AppTheme {}
}
const theme = {
colors: {
text: "#000",
background: "#fff",
primary: "#07c",
secondary: "#30c",
gray: "#999",
muted: "#f6f6f6",
highlight: "#e0e",
transparent: "transparent",
},
fonts: {
body:
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
heading: "inherit",
monospace: "Menlo, monospace",
},
fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72],
fontWeights: {
body: 400,
heading: 700,
bold: 700,
},
lineHeights: {
body: 1.5,
heading: 1.125,
},
space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
radii: {
rounded: 4,
circle: 99999,
},
} as const;
export default theme;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment