diff --git a/.changeset/lovely-socks-cheat.md b/.changeset/lovely-socks-cheat.md new file mode 100644 index 0000000000..7a5eab4d96 --- /dev/null +++ b/.changeset/lovely-socks-cheat.md @@ -0,0 +1,7 @@ +--- +'@toptal/picasso-provider': minor +'@toptal/picasso': minor +'@toptal/picasso-shared': minor +--- + +- add new spacing values to picasso's theme and to `picasso-shared`. See RFC: https://github.com/toptal/picasso/blob/master/docs/decisions/18-spacings.md diff --git a/packages/picasso-provider/src/Picasso/PicassoProvider.tsx b/packages/picasso-provider/src/Picasso/PicassoProvider.tsx index 0bec2534fa..ca7fe60074 100644 --- a/packages/picasso-provider/src/Picasso/PicassoProvider.tsx +++ b/packages/picasso-provider/src/Picasso/PicassoProvider.tsx @@ -12,6 +12,7 @@ import { typography, sizes, shadows, + spacings, } from './config' const picasso: ThemeOptions = { @@ -40,6 +41,7 @@ const picasso: ThemeOptions = { notched: false, }, }, + spacings, } class Provider { diff --git a/packages/picasso-provider/src/Picasso/config/index.ts b/packages/picasso-provider/src/Picasso/config/index.ts index cee832252c..ccfb7bddc5 100644 --- a/packages/picasso-provider/src/Picasso/config/index.ts +++ b/packages/picasso-provider/src/Picasso/config/index.ts @@ -16,3 +16,5 @@ export { } from './breakpoints' export { default as layout } from './layout' export { default as shadows } from './shadows' +export { default as spacings, SpacingEnum } from './spacings' +export type { Sizes, SizeType, SpacingType, PicassoSpacing } from './spacings' diff --git a/packages/picasso-provider/src/Picasso/config/spacings.ts b/packages/picasso-provider/src/Picasso/config/spacings.ts new file mode 100644 index 0000000000..7af750c486 --- /dev/null +++ b/packages/picasso-provider/src/Picasso/config/spacings.ts @@ -0,0 +1,75 @@ +// BASE-aligned spacing values in "rem" units +type PicassoSpacingValues = 0 | 0.25 | 0.5 | 0.75 | 1 | 1.5 | 2 | 2.5 | 3 + +export type Sizes = + | 'xxsmall' + | 'xsmall' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + +export type SizeType = T + +/** @deprecated **/ +type DeprecatedSpacingType = + | number + | SizeType<'xsmall' | 'small' | 'medium' | 'large' | 'xlarge'> + +export type SpacingType = PicassoSpacing | DeprecatedSpacingType + +export enum SpacingEnum { + xsmall = 0.5, + small = 1, + medium = 1.5, + large = 2, + xlarge = 2.5, +} + +class PicassoSpacing { + #value: PicassoSpacingValues + + private constructor(value: PicassoSpacingValues) { + this.#value = value + } + + static create(value: PicassoSpacingValues): PicassoSpacing { + return new PicassoSpacing(value) + } + + valueOf(): PicassoSpacingValues { + return this.#value + } + + toString(): string { + return this.#value.toString() + } +} + +export type { PicassoSpacing } + +export const isPicassoSpacing = ( + spacing: SpacingType +): spacing is PicassoSpacing => spacing instanceof PicassoSpacing + +export const SPACING_0 = PicassoSpacing.create(0) +export const SPACING_1 = PicassoSpacing.create(0.25) +export const SPACING_2 = PicassoSpacing.create(0.5) +export const SPACING_3 = PicassoSpacing.create(0.75) +export const SPACING_4 = PicassoSpacing.create(1) +export const SPACING_6 = PicassoSpacing.create(1.5) +export const SPACING_8 = PicassoSpacing.create(2) +export const SPACING_10 = PicassoSpacing.create(2.5) +export const SPACING_12 = PicassoSpacing.create(3) + +export default { + SPACING_0, + SPACING_1, + SPACING_2, + SPACING_3, + SPACING_4, + SPACING_6, + SPACING_8, + SPACING_10, + SPACING_12, +} diff --git a/packages/picasso-provider/src/Picasso/config/theme.ts b/packages/picasso-provider/src/Picasso/config/theme.ts index ef6d8d31dc..e3a3dccd91 100644 --- a/packages/picasso-provider/src/Picasso/config/theme.ts +++ b/packages/picasso-provider/src/Picasso/config/theme.ts @@ -1,12 +1,22 @@ import type { BreakpointKeys } from './breakpoints' import type { Layout } from './layout' import type { Sizes } from './sizes' +import type spacings from './spacings' +import type { PicassoSpacing } from './spacings' declare module '@material-ui/core/styles' { interface Theme { layout: Layout sizes: Sizes screens: (...sizes: BreakpointKeys[]) => string + spacings: Record + } + + interface ThemeOptions { + layout?: Layout + sizes?: Sizes + screens?: (...sizes: BreakpointKeys[]) => string + spacings?: Record } interface ThemeOptions { diff --git a/packages/picasso-provider/src/Picasso/utils/index.ts b/packages/picasso-provider/src/Picasso/utils/index.ts index be858e3bf6..6d30b7ea04 100644 --- a/packages/picasso-provider/src/Picasso/utils/index.ts +++ b/packages/picasso-provider/src/Picasso/utils/index.ts @@ -1,2 +1,3 @@ export * from './generate-random-string' export * from './get-serverside-stylesheets' +export * from './spacings' diff --git a/packages/picasso-provider/src/Picasso/utils/spacings.test.ts b/packages/picasso-provider/src/Picasso/utils/spacings.test.ts new file mode 100644 index 0000000000..108954f07f --- /dev/null +++ b/packages/picasso-provider/src/Picasso/utils/spacings.test.ts @@ -0,0 +1,74 @@ +import { isNumericSpacing, spacingToRem } from './spacings' +import { + SPACING_0, + SPACING_1, + SPACING_2, + SPACING_3, + SPACING_4, + SPACING_6, + SPACING_8, + SPACING_10, + SPACING_12, +} from '../config/spacings' + +describe('spacingUtils', () => { + describe('isNumericSpacing', () => { + describe('when spacing is undefined', () => { + it('returns false', () => { + expect(isNumericSpacing(undefined)).toBe(false) + }) + }) + + describe('when spacing is a number', () => { + it('returns true', () => { + expect(isNumericSpacing(1)).toBe(true) + expect(isNumericSpacing(2.5)).toBe(true) + }) + }) + + describe('when spacing is a new Picasso spacing', () => { + it('returns true', () => { + expect(isNumericSpacing(SPACING_0)).toBe(true) + }) + }) + + describe('when spacing is a string Picasso spacing', () => { + it('returns false', () => { + expect(isNumericSpacing('small')).toBe(false) + }) + }) + }) + + describe('spacingToRem', () => { + describe('when spacing is a number', () => { + it('converts to rem', () => { + expect(spacingToRem(1)).toBe('1rem') + expect(spacingToRem(2.5)).toBe('2.5rem') + }) + }) + + describe('when spacing is a valid Picasso spacing', () => { + it('converts to rem', () => { + expect(spacingToRem(SPACING_0)).toBe('0rem') + expect(spacingToRem(SPACING_1)).toBe('0.25rem') + expect(spacingToRem(SPACING_2)).toBe('0.5rem') + expect(spacingToRem(SPACING_3)).toBe('0.75rem') + expect(spacingToRem(SPACING_4)).toBe('1rem') + expect(spacingToRem(SPACING_6)).toBe('1.5rem') + expect(spacingToRem(SPACING_8)).toBe('2rem') + expect(spacingToRem(SPACING_10)).toBe('2.5rem') + expect(spacingToRem(SPACING_12)).toBe('3rem') + }) + }) + + describe('when spacing is a string Picasso spacing', () => { + it('converts to rem', () => { + expect(spacingToRem('xsmall')).toBe('0.5rem') + expect(spacingToRem('small')).toBe('1rem') + expect(spacingToRem('medium')).toBe('1.5rem') + expect(spacingToRem('large')).toBe('2rem') + expect(spacingToRem('xlarge')).toBe('2.5rem') + }) + }) + }) +}) diff --git a/packages/picasso-provider/src/Picasso/utils/spacings.ts b/packages/picasso-provider/src/Picasso/utils/spacings.ts new file mode 100644 index 0000000000..843b6714a7 --- /dev/null +++ b/packages/picasso-provider/src/Picasso/utils/spacings.ts @@ -0,0 +1,19 @@ +import type { SizeType, SpacingType } from '../config/spacings' +import { SpacingEnum, isPicassoSpacing } from '../config/spacings' + +const isNumericSpacing = ( + spacing: SpacingType | undefined +): spacing is number | SpacingType => { + const isNotNull = spacing != null + const isNumber = typeof spacing == 'number' + + return isNotNull && (isNumber || isPicassoSpacing(spacing)) +} + +const spacingToRem = (spacing: SpacingType): string => { + return isNumericSpacing(spacing) + ? `${spacing}rem` + : `${SpacingEnum[spacing as SizeType]}rem` +} + +export { isNumericSpacing, spacingToRem } diff --git a/packages/picasso-provider/src/index.ts b/packages/picasso-provider/src/index.ts index c900b5f445..68bb218cfa 100644 --- a/packages/picasso-provider/src/index.ts +++ b/packages/picasso-provider/src/index.ts @@ -25,6 +25,15 @@ export { shadows, PicassoBreakpoints, BreakpointKeys, + spacings, + SpacingEnum, +} from './Picasso/config' + +export type { + Sizes, + SizeType, + SpacingType, + PicassoSpacing, } from './Picasso/config' export { default as PicassoProvider } from './Picasso/PicassoProvider' @@ -44,6 +53,8 @@ export { generateRandomString, generateRandomStringOrGetEmptyInTest, getServersideStylesheets, + isNumericSpacing, + spacingToRem, } from './Picasso/utils' export { default as Favicon } from './Favicon' diff --git a/packages/picasso/src/Container/Container.tsx b/packages/picasso/src/Container/Container.tsx index cd51bc5dfd..b228673113 100644 --- a/packages/picasso/src/Container/Container.tsx +++ b/packages/picasso/src/Container/Container.tsx @@ -7,7 +7,7 @@ import { makeStyles } from '@material-ui/core/styles' import type { PropTypes } from '@material-ui/core' import cx from 'classnames' import type { StandardProps, SpacingType } from '@toptal/picasso-shared' -import { spacingToRem } from '@toptal/picasso-shared' +import { spacingToRem, isNumericSpacing } from '@toptal/picasso-shared' import type { AlignItemsType, JustifyContentType, VariantType } from './styles' import styles from './styles' @@ -101,12 +101,12 @@ export const Container = documentable( const classes = useStyles(props) const margins = { - ...(typeof top === 'number' && { marginTop: spacingToRem(top) }), - ...(typeof bottom === 'number' && { + ...(isNumericSpacing(top) && { marginTop: spacingToRem(top) }), + ...(isNumericSpacing(bottom) && { marginBottom: spacingToRem(bottom), }), - ...(typeof left === 'number' && { marginLeft: spacingToRem(left) }), - ...(typeof right === 'number' && { marginRight: spacingToRem(right) }), + ...(isNumericSpacing(left) && { marginLeft: spacingToRem(left) }), + ...(isNumericSpacing(right) && { marginRight: spacingToRem(right) }), } return ( @@ -144,10 +144,10 @@ export const Container = documentable( )} style={{ ...margins, - ...(typeof padded === 'number' && { + ...(isNumericSpacing(padded) && { padding: spacingToRem(padded), }), - ...(typeof gap === 'number' && { gap: spacingToRem(gap) }), + ...(isNumericSpacing(gap) && { gap: spacingToRem(gap) }), ...style, }} > diff --git a/packages/picasso/src/Container/story/Default.example.tsx b/packages/picasso/src/Container/story/Default.example.tsx index fe80625eb9..09653a5be7 100644 --- a/packages/picasso/src/Container/story/Default.example.tsx +++ b/packages/picasso/src/Container/story/Default.example.tsx @@ -1,10 +1,11 @@ import React from 'react' import { Container } from '@toptal/picasso' +import { SPACING_4, SPACING_8 } from '@toptal/picasso/utils' const Example = () => (
- Some text - Some more text with a small margin + Some text + Some more text with a small margin
) diff --git a/packages/picasso/src/utils/index.ts b/packages/picasso/src/utils/index.ts index 05bf1f4376..a27ccf9dda 100644 --- a/packages/picasso/src/utils/index.ts +++ b/packages/picasso/src/utils/index.ts @@ -1,4 +1,4 @@ -import { alpha, lighten, darken } from '@toptal/picasso-shared' +import { alpha, lighten, darken, spacings } from '@toptal/picasso-shared' import * as TransitionUtils from './Transitions' @@ -66,3 +66,15 @@ export { useDeprecationWarning, usePropDeprecationWarning, } from './use-deprecation-warnings' + +export const { + SPACING_0, + SPACING_1, + SPACING_2, + SPACING_3, + SPACING_4, + SPACING_6, + SPACING_8, + SPACING_10, + SPACING_12, +} = spacings diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 64e942bef4..620b4b6556 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -8,6 +8,20 @@ import type { import type { Classes } from './styles' +export { + spacings, + SpacingEnum, + isNumericSpacing, + spacingToRem, +} from '@toptal/picasso-provider' + +export type { + Sizes, + SizeType, + SpacingType, + PicassoSpacing, +} from '@toptal/picasso-provider' + export interface BaseProps { /** Classnames applied to root element */ className?: string @@ -52,8 +66,6 @@ export interface OverridableComponent

extends NamedComponent

{ ): JSX.Element | null } -type Sizes = 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' - type BaseEnvironments = 'development' | 'staging' | 'production' type Environments = BaseEnvironments | 'temploy' | 'test' @@ -62,23 +74,6 @@ export type EnvironmentType = | T | BaseEnvironments -export type SizeType = T - -export type SpacingType = - | number - | SizeType<'xsmall' | 'small' | 'medium' | 'large' | 'xlarge'> - -export enum SpacingEnum { - xsmall = 0.5, - small = 1, - medium = 1.5, - large = 2, - xlarge = 2.5, -} - -export const spacingToRem = (spacing: SpacingType) => - typeof spacing === 'number' ? `${spacing}rem` : `${SpacingEnum[spacing]}rem` - export type ButtonOrAnchorProps = AnchorHTMLAttributes & ButtonHTMLAttributes