Last active
August 25, 2024 10:21
-
-
Save tammyhart/c0fe335f30fb8d2d4806a83946b3f1ee to your computer and use it in GitHub Desktop.
Flexible flex-box component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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