Skip to content

Instantly share code, notes, and snippets.

@tammyhart
Last active August 25, 2024 10:21
Show Gist options
  • Save tammyhart/c0fe335f30fb8d2d4806a83946b3f1ee to your computer and use it in GitHub Desktop.
Save tammyhart/c0fe335f30fb8d2d4806a83946b3f1ee to your computer and use it in GitHub Desktop.
Flexible flex-box component
import { HTMLProps } from "react"
import styled from "styled-components"
import {
calcBasis,
mapGap,
mapJustify,
mapPadding,
media,
} from "./utils"
import type { Basis, Gap, Justify, Padding } from "./styles"
export type FlexProps<T extends keyof JSX.IntrinsicElements = "div"> = {
$auto?: boolean
$baseline?: boolean
$basis?: Basis
$center?: boolean
$column?: boolean
$gap?: Gap
$grow?: boolean
$justify?: Justify
$padding?: Padding
$row?: boolean
$wrap?: boolean
as?: keyof JSX.IntrinsicElements
} & HTMLProps<T>
const Flex = styled.div<FlexProps>`
display: flex;
${({ $auto, $baseline, $center, $column, $grow, $wrap }) =>
($auto ? "justify-content: space-between; width: 100%;" : "") +
($baseline && !$center && !$column ? "align-items: baseline;" : "") +
($center && !$baseline && $column ? "align-items: center;" : "") +
($column ? "flex-direction: column;" : "") +
($grow ? "flex-grow: 1;" : "") +
($wrap ? "flex-wrap: wrap;" : "")}
${({ $gap }) => ($gap ? mapGap($gap) : "")}
${({ $padding }) => ($padding ? mapPadding($padding) : "")}
${({ $justify }) => ($justify ? mapJustify($justify) : "")}
${({ $basis }) => ($basis ? media.lap`${calcBasis($basis)}` : "")}
${({ $row }) => ($row ? media.desk`flex-direction: row;` : "")}
`
export default Flex
import { justifyOptions } from "./utils"
export type Size =
| 0
| 0.25
| 0.5
| 0.75
| 1
| 1.25
| 1.5
| 1.75
| 2
| 2.5
| 3
| 3.5
| 4
| 4.5
| 5
| 6
| 7
| 8
| 10
| 15
| 18
| 20
| 25
| 30
| 40
| 50
| 100
| 110
| 150
| 180
export type Sizes = (null | Size)[]
export type SizeOptions = null | Size | Sizes
export type SizesDeep = SizeOptions[]
export type Gap = SizeOptions
export type Padding = SizeOptions | SizesDeep
type ChildrenCount = number
type LapGap = Size
export type Basis = [ChildrenCount, LapGap]
export type JustifyOptions = (typeof justifyOptions)[number]
export type Justify = JustifyOptions | JustifyOptions[]
import { css } from "styled-components"
import type {
Basis,
FontSizePairing,
Gap,
Justify,
JustifyOptions,
Padding,
Size,
SizeOptions,
} from "./styles"
export const size = (_size: Size) => _size * 8 + "px"
const LAP = "lap"
const DESK = "desk"
const deviceSizes = [LAP, DESK] as const
type DeviceSizes = (typeof deviceSizes)[number]
const device = {
[LAP]: size(100),
[DESK]: size(150),
}
const mediaStyles =
(deviceSize: DeviceSizes) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(literals: TemplateStringsArray, ...rest: any[]) =>
css`
@media (min-width: ${device[deviceSize]}) {
${css(literals, ...rest)};
}
`
export const media = {
[LAP]: mediaStyles(LAP),
[DESK]: mediaStyles(DESK),
}
export const mapGap = (gap: Gap) => {
gap = Array.isArray(gap) ? gap : [gap]
const property = (g: Size) => `gap: ${size(g)};`
return css`
${gap.map(
(g, i) =>
g !== null &&
(i === 0 ? property(g) : media[deviceSizes[i - 1]]`${property(g)}`)
)}
`
}
export const mapPadding = (padding: Padding) => {
padding = Array.isArray(padding) ? padding : [padding]
const paddingSizes = (pad: SizeOptions) =>
pad &&
(Array.isArray(pad)
? pad.map(p => p !== null && size(p)).join(" ")
: size(pad))
const property = (pad: SizeOptions) => `padding: ${paddingSizes(pad)};`
return css`
${padding.map(
(p, i) =>
p !== null &&
(i === 0 ? property(p) : media[deviceSizes[i - 1]]`${property(p)}`)
)}
`
}
const CENTER = "center"
const END = "end"
const START = "start"
export const justifyOptions = [undefined, CENTER, END, START] as const
export const mapJustify = (justify: Justify) => {
justify = Array.isArray(justify) ? justify : [justify]
const maybePrefix = (j: JustifyOptions) =>
j && ([END, START].includes(j) ? "flex-" + j : j)
const property = (j: JustifyOptions) =>
j && `justify-content: ${maybePrefix(j)};`
return css`
${justify.map(
(j, i) =>
j && (i === 0 ? property(j) : media[deviceSizes[i - 1]]`${property(j)}`)
)}
`
}
export const calcBasis = (basis: Basis) => {
const [childrenCount, lapGap] = basis
const baseWidth = 100 / childrenCount + "%"
const adjustedGap = (lapGap / childrenCount) as Size
return `> * { flex-basis: calc(${baseWidth} - ${size(adjustedGap)}); }`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment