diff --git a/packages/components/src/ui/context/index.js b/packages/components/src/ui/context/index.js index 502d0ca2769c7..6a9e824185db9 100644 --- a/packages/components/src/ui/context/index.js +++ b/packages/components/src/ui/context/index.js @@ -4,5 +4,10 @@ export { ContextSystemProvider, useComponentsContext, } from './context-system-provider'; -export { contextConnect } from './context-connect'; +export { + contextConnect, + hasConnectNamespace, + getConnectNamespace, +} from './context-connect'; export { useContextSystem } from './use-context-system'; +export * from './polymorphic-component'; diff --git a/packages/components/src/ui/context/polymorphic-component.ts b/packages/components/src/ui/context/polymorphic-component.ts index 2511de54a9189..af4791be0d7b6 100644 --- a/packages/components/src/ui/context/polymorphic-component.ts +++ b/packages/components/src/ui/context/polymorphic-component.ts @@ -4,16 +4,15 @@ // eslint-disable-next-line no-restricted-imports import type * as React from 'react'; import type { As, RenderProp, ExtractHTMLAttributes } from 'reakit-utils/types'; -import type { Interpolation, ObjectInterpolation } from 'create-emotion'; +import type { Interpolation } from 'create-emotion'; /** * Based on https://github.com/reakit/reakit/blob/master/packages/reakit-utils/src/types.ts */ export type ViewOwnProps< P, T extends As > = P & - Omit< React.ComponentPropsWithRef< T >, 'as' | 'css' | keyof P > & { + Omit< React.ComponentPropsWithRef< T >, 'as' | keyof P > & { as?: T | keyof JSX.IntrinsicElements; children?: React.ReactNode | RenderProp< ExtractHTMLAttributes< any > >; - css?: ObjectInterpolation< undefined > | string; }; export type ElementTypeFromViewOwnProps< P > = P extends ViewOwnProps< diff --git a/packages/components/src/ui/control-label/hook.js b/packages/components/src/ui/control-label/hook.js index 626fec07274bc..a0197ca41a78d 100644 --- a/packages/components/src/ui/control-label/hook.js +++ b/packages/components/src/ui/control-label/hook.js @@ -32,7 +32,7 @@ export function useControlLabel( props ) { const htmlFor = useFormGroupContextId( htmlForProp ); const classes = cx( styles.ControlLabel, - styles[ size ], + styles[ /** @type {'small' | 'medium' | 'large'} */ ( size ) ], className, isBlock ? styles.block : styles.inline ); diff --git a/packages/components/src/ui/flex/use-flex.js b/packages/components/src/ui/flex/use-flex.js index c16687f37939e..a8bad4348b189 100644 --- a/packages/components/src/ui/flex/use-flex.js +++ b/packages/components/src/ui/flex/use-flex.js @@ -1,8 +1,7 @@ /** * External dependencies */ -import { useContextSystem } from '@wp-g2/context'; -import { css, cx, ui, useResponsiveValue } from '@wp-g2/styles'; +import { css, cx } from 'emotion'; /** * WordPress dependencies @@ -12,6 +11,9 @@ import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ +import { useContextSystem } from '../context'; +import { useResponsiveValue } from '../utils/use-responsive-value'; +import { space } from '../utils/space'; import * as styles from './styles'; /** @@ -43,17 +45,13 @@ export function useFlex( props ) { const sx = {}; sx.Base = css( { - [ ui.createToken( 'flexGap' ) ]: ui.space( gap ), - [ ui.createToken( 'flexItemDisplay' ) ]: isColumn - ? 'block' - : undefined, alignItems: isColumn ? 'normal' : align, flexDirection: direction, flexWrap: wrap ? 'wrap' : undefined, justifyContent: justify, height: isColumn && expanded ? '100%' : undefined, width: ! isColumn && expanded ? '100%' : undefined, - marginBottom: wrap ? `calc(${ ui.space( gap ) } * -1)` : undefined, + marginBottom: wrap ? `calc(${ space( gap ) } * -1)` : undefined, } ); sx.Items = css( { @@ -65,21 +63,19 @@ export function useFlex( props ) { * Far less DOM less. However, UI rendering is not as reliable. */ '> * + *:not(marquee)': { - marginTop: isColumn ? ui.space( gap ) : undefined, - marginRight: - ! isColumn && isReverse ? ui.space( gap ) : undefined, + marginTop: isColumn ? space( gap ) : undefined, + marginRight: ! isColumn && isReverse ? space( gap ) : undefined, marginLeft: - ! isColumn && ! isReverse ? ui.space( gap ) : undefined, + ! isColumn && ! isReverse ? space( gap ) : undefined, }, } ); sx.WrapItems = css( { '> *:not(marquee)': { - marginBottom: ui.space( gap ), - marginLeft: - ! isColumn && isReverse ? ui.space( gap ) : undefined, + marginBottom: space( gap ), + marginLeft: ! isColumn && isReverse ? space( gap ) : undefined, marginRight: - ! isColumn && ! isReverse ? ui.space( gap ) : undefined, + ! isColumn && ! isReverse ? space( gap ) : undefined, }, '> *:last-child:not(marquee)': { marginLeft: ! isColumn && isReverse ? 0 : undefined, diff --git a/packages/components/src/ui/heading/hook.ts b/packages/components/src/ui/heading/hook.ts index 79d8977c4da1b..3c822d9810eaf 100644 --- a/packages/components/src/ui/heading/hook.ts +++ b/packages/components/src/ui/heading/hook.ts @@ -1,15 +1,13 @@ -/** - * External dependencies - */ -import { useContextSystem } from '@wp-g2/context'; -import { getHeadingFontSize, ui } from '@wp-g2/styles'; -import type { ViewOwnProps } from '@wp-g2/create-styles'; - /** * Internal dependencies */ +import { useContextSystem } from '../context'; +// eslint-disable-next-line no-duplicate-imports +import type { ViewOwnProps } from '../context'; import type { Props as TextProps } from '../text/types'; import { useText } from '../text'; +import { getHeadingFontSize } from '../utils/font-size'; +import { CONFIG, COLORS } from '../../utils'; export type HeadingSize = | 1 @@ -71,11 +69,10 @@ export function useHeading( props: ViewOwnProps< HeadingProps, 'h1' > ) { } const textProps = useText( { - color: ui.get( 'colorTextHeading' ), + color: COLORS.darkGray.heading, size: getHeadingFontSize( level ), isBlock: true, - // @ts-ignore We're passing a variable so `string` is safe - weight: ui.get( 'fontWeightHeading' ), + weight: CONFIG.fontWeightHeading as import('react').CSSProperties[ 'fontWeight' ], ...otherProps, } ); diff --git a/packages/components/src/ui/heading/stories/index.js b/packages/components/src/ui/heading/stories/index.js index 9254152b62817..ab4a1154f5797 100644 --- a/packages/components/src/ui/heading/stories/index.js +++ b/packages/components/src/ui/heading/stories/index.js @@ -11,7 +11,9 @@ export default { export const _default = () => { return ( <> - Heading + + Heading + Heading Heading Heading diff --git a/packages/components/src/ui/surface/hook.js b/packages/components/src/ui/surface/hook.js index 99cee69f0e68b..535569eeef754 100644 --- a/packages/components/src/ui/surface/hook.js +++ b/packages/components/src/ui/surface/hook.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { useContextSystem } from '@wp-g2/context'; import { css, cx, ui } from '@wp-g2/styles'; /** @@ -12,6 +11,7 @@ import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ +import { useContextSystem } from '../context'; import * as styles from './styles'; /** diff --git a/packages/components/src/ui/text/get-line-height.ts b/packages/components/src/ui/text/get-line-height.ts new file mode 100644 index 0000000000000..f298bf07d75e2 --- /dev/null +++ b/packages/components/src/ui/text/get-line-height.ts @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import type { CSSProperties } from 'react'; + +/** + * Internal dependencies + */ +import type { Props } from './types'; +import { space } from '../utils/space'; +import { CONFIG } from '../../utils'; + +export function getLineHeight( + adjustLineHeightForInnerControls: Props[ 'adjustLineHeightForInnerControls' ], + lineHeight: CSSProperties[ 'lineHeight' ] +) { + if ( lineHeight ) return lineHeight; + + if ( ! adjustLineHeightForInnerControls ) return; + + let value = `calc(${ CONFIG.controlHeight } + ${ space( 2 ) })`; + + switch ( adjustLineHeightForInnerControls ) { + case 'large': + value = `calc(${ CONFIG.controlHeightLarge } + ${ space( 2 ) })`; + break; + case 'small': + value = `calc(${ CONFIG.controlHeightSmall } + ${ space( 2 ) })`; + break; + case 'xSmall': + value = `calc(${ CONFIG.controlHeightXSmall } + ${ space( 2 ) })`; + break; + default: + break; + } + + return value; +} diff --git a/packages/components/src/ui/text/hook.js b/packages/components/src/ui/text/hook.js index f78eab0f81828..4b6eb65ea0a67 100644 --- a/packages/components/src/ui/text/hook.js +++ b/packages/components/src/ui/text/hook.js @@ -1,9 +1,8 @@ /** * External dependencies */ -import { hasNamespace, useContextSystem } from '@wp-g2/context'; -import { css, cx, getFontSize, ui } from '@wp-g2/styles'; -import { isPlainObject, isNil } from 'lodash'; +import { css, cx } from 'emotion'; +import { isPlainObject } from 'lodash'; /** * WordPress dependencies @@ -13,13 +12,17 @@ import { useMemo, Children, cloneElement } from '@wordpress/element'; /** * Internal dependencies */ +import { hasConnectNamespace, useContextSystem } from '../context'; import { useTruncate } from '../truncate'; import { getOptimalTextShade } from '../utils'; import * as styles from './styles'; import { createHighlighterText } from './utils'; +import { getFontSize } from '../utils/font-size'; +import { CONFIG, COLORS } from '../../utils'; +import { getLineHeight } from './get-line-height'; /** - * @param {import('@wp-g2/create-styles').ViewOwnProps} props + * @param {import('../context').ViewOwnProps} props */ export default function useText( props ) { const { @@ -43,7 +46,7 @@ export default function useText( props ) { truncate = false, upperCase = false, variant, - weight = ui.get( 'fontWeight' ), + weight = CONFIG.fontWeight, ...otherProps } = useContextSystem( props, 'Text' ); @@ -73,10 +76,10 @@ export default function useText( props ) { const classes = useMemo( () => { const sx = {}; - const lineHeight = getLineHeight( { - lineHeight: lineHeightProp, + const lineHeight = getLineHeight( adjustLineHeightForInnerControls, - } ); + lineHeightProp + ); sx.Base = css( { color, @@ -99,8 +102,8 @@ export default function useText( props ) { getOptimalTextShade( optimizeReadabilityFor ) === 'dark'; sx.optimalTextColor = isOptimalTextColorDark - ? css( { color: ui.get( 'black' ) } ) - : css( { color: ui.get( 'white' ) } ); + ? css( { color: COLORS.black } ) + : css( { color: COLORS.white } ); } return cx( @@ -162,7 +165,7 @@ export default function useText( props ) { return child; } - const isLink = hasNamespace( child, [ 'Link' ] ); + const isLink = hasConnectNamespace( child, [ 'Link' ] ); if ( isLink ) { return cloneElement( child, { size: child.props.size || 'inherit', @@ -178,40 +181,3 @@ export default function useText( props ) { children: truncate ? truncateProps.children : content, }; } - -/* eslint-disable jsdoc/valid-types */ -/** - * @param {Object} props - * @param {import('./types').Props['adjustLineHeightForInnerControls']} [props.adjustLineHeightForInnerControls] - * @param {import('react').CSSProperties['lineHeight']} [props.lineHeight] - */ -/* eslint-enable jsdoc/valid-types */ -function getLineHeight( { adjustLineHeightForInnerControls, lineHeight } ) { - if ( ! isNil( lineHeight ) ) return lineHeight; - - if ( ! adjustLineHeightForInnerControls ) return; - - let value = `calc(${ ui.get( 'controlHeight' ) } + ${ ui.space( 2 ) })`; - - switch ( adjustLineHeightForInnerControls ) { - case 'large': - value = `calc(${ ui.get( 'controlHeightLarge' ) } + ${ ui.space( - 2 - ) })`; - break; - case 'small': - value = `calc(${ ui.get( 'controlHeightSmall' ) } + ${ ui.space( - 2 - ) })`; - break; - case 'xSmall': - value = `calc(${ ui.get( 'controlHeightXSmall' ) } + ${ ui.space( - 2 - ) })`; - break; - default: - break; - } - - return value; -} diff --git a/packages/components/src/ui/text/styles.js b/packages/components/src/ui/text/styles.js index 947677a1b198b..6ded079d76d36 100644 --- a/packages/components/src/ui/text/styles.js +++ b/packages/components/src/ui/text/styles.js @@ -1,11 +1,17 @@ /** * External dependencies */ -import { css, ui } from '@wp-g2/styles'; +import { css } from 'emotion'; + +/** + * Internal dependencies + */ +import { COLORS, CONFIG } from '../../utils'; export const Text = css` - color: ${ ui.color.text }; - line-height: ${ ui.get( 'fontLineHeightBase' ) }; + color: ${ COLORS.black }; + line-height: ${ CONFIG.fontLineHeightBase }; + margin: 0; `; export const block = css` @@ -13,20 +19,20 @@ export const block = css` `; export const positive = css` - color: ${ ui.color.positive }; + color: ${ COLORS.alert.green }; `; export const destructive = css` - color: ${ ui.color.destructive }; + color: ${ COLORS.alert.red }; `; export const muted = css` - color: ${ ui.get( 'colorTextMuted' ) }; + color: ${ COLORS.mediumGray.text }; `; export const highlighterText = css` mark { - background: ${ ui.get( 'yellowRgba70' ) }; + background: ${ COLORS.alert.yellow }; border-radius: 2px; box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.05 ) inset, 0 -1px 0 rgba( 0, 0, 0, 0.1 ) inset; diff --git a/packages/components/src/ui/truncate/hook.js b/packages/components/src/ui/truncate/hook.js index 75ef75f4b8edd..f50707837536a 100644 --- a/packages/components/src/ui/truncate/hook.js +++ b/packages/components/src/ui/truncate/hook.js @@ -16,7 +16,7 @@ import * as styles from './styles'; import { TRUNCATE_ELLIPSIS, TRUNCATE_TYPE, truncateContent } from './utils'; /** - * @param {import('@wp-g2/create-styles').ViewOwnProps} props + * @param {import('../context').ViewOwnProps} props */ export default function useTruncate( props ) { const { diff --git a/packages/components/src/ui/utils/create-component.js b/packages/components/src/ui/utils/create-component.js index f1a640f0104ad..eb53b895b666d 100644 --- a/packages/components/src/ui/utils/create-component.js +++ b/packages/components/src/ui/utils/create-component.js @@ -1,21 +1,21 @@ /** * External dependencies */ -import { contextConnect } from '@wp-g2/context'; import { identity } from 'lodash'; /** * Internal dependencies */ +import { contextConnect } from '../context'; import { View } from '../view'; /** * Factory that creates a React component. * * @template {import('reakit-utils/types').As} T - * @template {import('@wp-g2/create-styles').ViewOwnProps<{}, T>} P + * @template {import('../context').ViewOwnProps<{}, T>} P * @param {import('./types').Options} options Options to customize the component. - * @return {import('@wp-g2/create-styles').PolymorphicComponent>} New React component. + * @return {import('../context').PolymorphicComponent>} New React component. */ /* eslint-disable jsdoc/no-undefined-types */ export const createComponent = ( { diff --git a/packages/components/src/ui/utils/font-size.ts b/packages/components/src/ui/utils/font-size.ts new file mode 100644 index 0000000000000..3ddcd9623eb1b --- /dev/null +++ b/packages/components/src/ui/utils/font-size.ts @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import type { CSSProperties, ReactText } from 'react'; + +/** + * Internal dependencies + */ +import { config } from '../../utils'; + +export type HeadingSize = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | '1' + | '2' + | '3' + | '4' + | '5' + | '6'; + +export const BASE_FONT_SIZE = 13; + +export const PRESET_FONT_SIZES = { + body: BASE_FONT_SIZE, + caption: 10, + footnote: 11, + largeTitle: 28, + subheadline: 12, + title: 20, +}; + +export const HEADING_FONT_SIZES = [ 1, 2, 3, 4, 5, 6 ].flatMap( ( n ) => [ + n as HeadingSize, + n.toString() as HeadingSize, +] ); + +export function getFontSize( + size: + | CSSProperties[ 'fontSize' ] + | keyof typeof PRESET_FONT_SIZES = BASE_FONT_SIZE +): string { + if ( size in PRESET_FONT_SIZES ) { + return getFontSize( + PRESET_FONT_SIZES[ size as keyof typeof PRESET_FONT_SIZES ] + ); + } + + if ( typeof size !== 'number' ) { + const parsed = parseFloat( size ); + if ( Number.isNaN( parsed ) ) return size; + size = parsed; + } + + const ratio = `(${ size } / ${ BASE_FONT_SIZE })`; + return `calc(${ ratio } * ${ config( 'fontSize' ) })`; +} + +export function getHeadingFontSize( size: ReactText = 3 ): string { + if ( ! HEADING_FONT_SIZES.includes( size as HeadingSize ) ) { + return getFontSize( size ); + } + + const headingSize = `fontSizeH${ size }` as `fontSizeH${ HeadingSize }`; + return config( headingSize ); +} diff --git a/packages/components/src/ui/utils/space.ts b/packages/components/src/ui/utils/space.ts new file mode 100644 index 0000000000000..9741ad2162b70 --- /dev/null +++ b/packages/components/src/ui/utils/space.ts @@ -0,0 +1,15 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import type { ReactText } from 'react'; +/** + * Internal dependencies + */ +import { CONFIG } from '../../utils'; + +export function space( value: ReactText ): string { + return typeof value === 'number' + ? `calc(${ CONFIG.gridBase } * ${ value })` + : value; +} diff --git a/packages/components/src/ui/utils/use-responsive-value.ts b/packages/components/src/ui/utils/use-responsive-value.ts new file mode 100644 index 0000000000000..af18aa432dbb0 --- /dev/null +++ b/packages/components/src/ui/utils/use-responsive-value.ts @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; + +const breakpoints = [ '40em', '52em', '64em' ]; + +export const useBreakpointIndex = ( + options: { defaultIndex?: number } = {} +) => { + const { defaultIndex = 0 } = options; + + if ( typeof defaultIndex !== 'number' ) { + throw new TypeError( + `Default breakpoint index should be a number. Got: ${ defaultIndex }, ${ typeof defaultIndex }` + ); + } else if ( defaultIndex < 0 || defaultIndex > breakpoints.length - 1 ) { + throw new RangeError( + `Default breakpoint index out of range. Theme has ${ breakpoints.length } breakpoints, got index ${ defaultIndex }` + ); + } + + const [ value, setValue ] = useState( defaultIndex ); + + useEffect( () => { + const getIndex = () => + breakpoints.filter( ( bp ) => { + return typeof window !== 'undefined' + ? window.matchMedia( `screen and (min-width: ${ bp })` ) + .matches + : false; + } ).length; + + const onResize = () => { + const newValue = getIndex(); + if ( value !== newValue ) { + setValue( newValue ); + } + }; + + onResize(); + + if ( typeof document !== 'undefined' ) { + // Disable reason: We don't really care about what document we listen to, we just want to know that we're resizing. + /* eslint-disable @wordpress/no-global-event-listener */ + document.addEventListener( 'resize', onResize ); + } + return () => { + if ( typeof document !== 'undefined' ) { + document.removeEventListener( 'resize', onResize ); + /* eslint-enable @wordpress/no-global-event-listener */ + } + }; + }, [ value ] ); + + return value; +}; + +export function useResponsiveValue< T >( + values: ( T | undefined )[], + options: Parameters< typeof useBreakpointIndex >[ 0 ] = {} +): T | undefined { + const index = useBreakpointIndex( options ); + + // Allow calling the function with a "normal" value without having to check on the outside. + if ( ! Array.isArray( values ) && typeof values !== 'function' ) + return values; + + const array = values || []; + + /* eslint-disable jsdoc/no-undefined-types */ + return /** @type {T[]} */ array[ + /* eslint-enable jsdoc/no-undefined-types */ + index >= array.length ? array.length - 1 : index + ]; +} diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index c22d2ece81397..2d54d4935d3a3 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -36,6 +36,7 @@ export const G2 = { }, darkGray: { primary: '#1e1e1e', + heading: '#050505', }, mediumGray: { text: '#757575', diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.js index 21311424087ae..f2e620e63c2f2 100644 --- a/packages/components/src/utils/config-values.js +++ b/packages/components/src/utils/config-values.js @@ -1,7 +1,28 @@ +const CONTROL_HEIGHT = '30px'; + export default { radiusBlockUi: '2px', borderWidth: '1px', borderWidthFocus: '1.5px', borderWidthTab: '4px', spinnerSize: '18px', + fontSize: '13px', + fontSizeH1: 'calc(2.44 * 13px)', + fontSizeH2: 'calc(1.95 * 13px)', + fontSizeH3: 'calc(1.56 * 13px)', + fontSizeH4: 'calc(1.25 * 13px)', + fontSizeH5: '13px', + fontSizeH6: 'calc(0.8 * 13px)', + fontSizeInputMobile: '16px', + fontSizeMobile: '15px', + fontSizeSmall: 'calc(0.92 * 13px)', + fontSizeXSmall: 'calc(0.75 * 13px)', + fontLineHeightBase: '1.2', + fontWeight: 'normal', + fontWeightHeading: '600', + gridBase: '4px', + controlHeight: CONTROL_HEIGHT, + controlHeightLarge: `calc( ${ CONTROL_HEIGHT } * 1.2 )`, + controlHeightSmall: `calc( ${ CONTROL_HEIGHT } * 0.8 )`, + controlHeightXSmall: `calc( ${ CONTROL_HEIGHT } * 0.6 )`, }; diff --git a/packages/components/src/utils/style-mixins.js b/packages/components/src/utils/style-mixins.js index 5b82fd9834354..be6347cf37b21 100644 --- a/packages/components/src/utils/style-mixins.js +++ b/packages/components/src/utils/style-mixins.js @@ -5,3 +5,5 @@ export { space } from './space'; export { font } from './font'; export { breakpoint } from './breakpoint'; export { config } from './config'; +export { default as CONFIG } from './config-values'; +export { COLORS } from './colors-values';