Split Box props

parent 8d5e5f36
import React, { createElement, forwardRef, memo } from 'react'; import React, {
HTMLAttributes,
createElement,
forwardRef,
memo,
Ref,
DetailedHTMLProps,
SVGProps,
ComponentType,
} from 'react';
import { useStyleSheet } from '../../hooks/useStyleSheet'; import { useStyleSheet } from '../../hooks/useStyleSheet';
import { useBoxTransform, BoxTransforms } from './BoxTransforms'; import { useBoxTransform, BoxTransforms } from './BoxTransforms';
import { BoxProps, consumeBoxProps } from './props'; import { BoxProps, useBoxProps } from './props';
import { useArrayLikeClassNameProp } from './useArrayLikeClassNameProp';
export const Box = memo( export const Box = memo(
forwardRef(function Box({ is = 'div', children, ...props }: BoxProps, ref) { forwardRef(function Box<
E = HTMLOrSVGElement,
P = DetailedHTMLProps<HTMLAttributes<E>, E> | SVGProps<E>
>({ is, children, ...props }: BoxProps<P>, ref: Ref<E>) {
useStyleSheet(); useStyleSheet();
let consumedProps = props as Record<string, unknown>;
const transformFn = useBoxTransform(); const transformFn = useBoxTransform();
if (transformFn) { const mappedProps = useBoxProps<P>(
consumedProps = transformFn(consumedProps); transformFn ? transformFn(props) : props
} );
consumedProps = consumeBoxProps(consumedProps); const element = createElement(
consumedProps = useArrayLikeClassNameProp(consumedProps); is,
Object.assign(mappedProps, { ref }),
if (ref) { children
consumedProps.ref = ref; );
}
return transformFn ? (
const element = createElement(is, consumedProps, children); <BoxTransforms.Provider children={element} value={undefined} />
) : (
if (transformFn) { element
return <BoxTransforms.Provider children={element} value={undefined} />; );
}
return element;
}) })
); );
Box.displayName = 'Box'; Box.displayName = 'Box';
(Box as ComponentType).defaultProps = {
is: 'div',
};
export { default as AnimatedVisibility } from './AnimatedVisibility'; export { default as AnimatedVisibility } from './AnimatedVisibility';
export { default as Flex } from './Flex'; export { default as Flex } from './Flex';
export { default as Position, PositionAnimated } from './Position'; export { default as Position, PositionAnimated } from './Position';
export { default as Scrollable } from './Scrollable'; export { default as Scrollable } from './Scrollable';
export default memo(Box);
...@@ -39,17 +39,11 @@ export type PostAliasTypes = { ...@@ -39,17 +39,11 @@ export type PostAliasTypes = {
export const isPreAlias = (propName: string): propName is keyof PreAliasTypes => export const isPreAlias = (propName: string): propName is keyof PreAliasTypes =>
typeof preAliases[propName] !== 'undefined'; typeof preAliases[propName] !== 'undefined';
export const consumePreAlias = < export const consumePreAlias = <P>(
P extends {
className?:
| string
| ReturnType<typeof css>
| (string | ReturnType<typeof css>)[];
}
>(
alias: keyof PreAliasTypes, alias: keyof PreAliasTypes,
value: PreAliasTypes[keyof PreAliasTypes], value: PreAliasTypes[keyof PreAliasTypes],
props: P props: P,
classNames: (string | ReturnType<typeof css>)[]
): void => { ): void => {
const effectivePropName = preAliases[alias]; const effectivePropName = preAliases[alias];
...@@ -57,7 +51,7 @@ export const consumePreAlias = < ...@@ -57,7 +51,7 @@ export const consumePreAlias = <
return; return;
} }
consumeCssPropertyProp(effectivePropName, value, props); consumeCssPropertyProp(effectivePropName, value, props, classNames);
}; };
export const isPostAlias = ( export const isPostAlias = (
...@@ -65,17 +59,11 @@ export const isPostAlias = ( ...@@ -65,17 +59,11 @@ export const isPostAlias = (
): propName is keyof PostAliasTypes => ): propName is keyof PostAliasTypes =>
typeof postAliases[propName] !== 'undefined'; typeof postAliases[propName] !== 'undefined';
export const consumePostAlias = < export const consumePostAlias = <P>(
P extends {
className?:
| string
| ReturnType<typeof css>
| (string | ReturnType<typeof css>)[];
}
>(
alias: keyof PostAliasTypes, alias: keyof PostAliasTypes,
value: PostAliasTypes[keyof PostAliasTypes], value: PostAliasTypes[keyof PostAliasTypes & keyof P],
props: P props: P,
_classNames: (string | ReturnType<typeof css>)[]
): void => { ): void => {
const effectivePropName = postAliases[alias]; const effectivePropName = postAliases[alias];
......
import { css } from '@rocket.chat/css-in-js'; import { css } from '@rocket.chat/css-in-js';
import { appendClassName } from '../../../helpers/appendClassName';
import { fromCamelToKebab } from '../../../helpers/fromCamelToKebab'; import { fromCamelToKebab } from '../../../helpers/fromCamelToKebab';
import * as styleTokens from '../../../styleTokens'; import * as styleTokens from '../../../styleTokens';
...@@ -138,17 +137,11 @@ export const isCssPropertyProp = ( ...@@ -138,17 +137,11 @@ export const isCssPropertyProp = (
): propName is keyof CssPropertyPropTypes => ): propName is keyof CssPropertyPropTypes =>
typeof cssPropertiesProps[propName] !== 'undefined'; typeof cssPropertiesProps[propName] !== 'undefined';
export const consumeCssPropertyProp = < export const consumeCssPropertyProp = <P>(
P extends {
className?:
| string
| ReturnType<typeof css>
| (string | ReturnType<typeof css>)[];
}
>(
propName: keyof CssPropertyPropTypes, propName: keyof CssPropertyPropTypes,
propValue: CssPropertyPropTypes[keyof CssPropertyPropTypes], propValue: CssPropertyPropTypes[keyof CssPropertyPropTypes],
props: P _props: P,
classNames: (string | ReturnType<typeof css>)[]
): void => { ): void => {
const fn = cssPropertiesProps[propName]; const fn = cssPropertiesProps[propName];
const cssProperty = fromCamelToKebab(propName); const cssProperty = fromCamelToKebab(propName);
...@@ -161,5 +154,5 @@ export const consumeCssPropertyProp = < ...@@ -161,5 +154,5 @@ export const consumeCssPropertyProp = <
${`${cssProperty}: ${cssValue} !important;`} ${`${cssProperty}: ${cssValue} !important;`}
`; `;
props.className = appendClassName(props.className, style); classNames.push(style);
}; };
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
import { css } from '@rocket.chat/css-in-js'; import { css } from '@rocket.chat/css-in-js';
import { AllHTMLAttributes, ElementType, SVGAttributes } from 'react'; import { ElementType, PropsWithChildren } from 'react';
import { prependClassName } from '../../../helpers/prependClassName'; import { prependClassName } from '../../../helpers/prependClassName';
import { useStyle } from '../../../hooks/useStyle';
import { import {
consumePostAlias, consumePostAlias,
consumePreAlias, consumePreAlias,
...@@ -28,83 +29,127 @@ export type BoxStylingProps = SpecialStylingPropTypes & ...@@ -28,83 +29,127 @@ export type BoxStylingProps = SpecialStylingPropTypes &
PreAliasTypes & PreAliasTypes &
PostAliasTypes; PostAliasTypes;
type BoxOnlyProps = { type BoxOnlyProps<P extends { className?: string }> = {
animated?: boolean; animated?: boolean;
className?: className?:
| string | string
| ReturnType<typeof css> | ReturnType<typeof css>
| (string | ReturnType<typeof css>)[]; | (string | ReturnType<typeof css>)[];
is?: ElementType; is?: ElementType<P>;
}; };
export type BoxProps = BoxStylingProps & export const useBoxStylingProps = <P extends { className?: string }>(
BoxOnlyProps & props: BoxStylingProps & Omit<P, 'className' | keyof BoxStylingProps>
Omit<AllHTMLAttributes<HTMLOrSVGElement>, keyof BoxOnlyProps> & ): P => {
Omit<SVGAttributes<HTMLOrSVGElement>, keyof BoxOnlyProps>; const targetProps: Partial<P> = {};
const targetClassNames: (string | ReturnType<typeof css>)[] = [];
export const consumeBoxStylingProps = (
props: BoxProps
): Record<string, unknown> => {
for (const [propName, propValue] of Object.entries(props)) { for (const [propName, propValue] of Object.entries(props)) {
if (isPreAlias(propName)) { if (isPreAlias(propName)) {
delete props[propName]; consumePreAlias(propName, propValue, targetProps, targetClassNames);
consumePreAlias(propName, propValue, props);
continue; continue;
} }
if (isSpecialStylingProp(propName)) { if (isSpecialStylingProp(propName)) {
delete props[propName]; consumeSpecialStylingProp(
consumeSpecialStylingProp(propName, propValue, props); propName,
propValue,
targetProps,
targetClassNames
);
continue; continue;
} }
if (isCssPropertyProp(propName)) { if (isCssPropertyProp(propName)) {
delete props[propName]; consumeCssPropertyProp(
consumeCssPropertyProp(propName, propValue, props); propName,
propValue,
targetProps,
targetClassNames
);
continue; continue;
} }
if (isPostAlias(propName)) { if (isPostAlias(propName)) {
delete props[propName]; consumePostAlias(propName, propValue, targetProps, targetClassNames);
consumePostAlias(propName, propValue, props);
continue; continue;
} }
targetProps[propName] = propValue;
}
const styles = targetClassNames.filter(
(value): value is ReturnType<typeof css> => typeof value === 'function'
);
const stylesClassName = useStyle(
css`
${styles}
`,
props
);
const classNames = targetClassNames.filter(
(value): value is string => typeof value === 'string'
);
if (stylesClassName) {
classNames.push(stylesClassName);
} }
return props; return Object.assign(targetProps, {
className: classNames.join(' '),
}) as P;
}; };
export const consumeBoxProps = (props: BoxProps): Record<string, unknown> => { export type BoxProps<P> = PropsWithChildren<BoxStylingProps & BoxOnlyProps<P>> &
Omit<P, 'className' | keyof (BoxStylingProps & BoxOnlyProps<P>)>;
export const useBoxProps = <P extends { className?: string }>(
props: BoxProps<P>
): P => {
const targetProps: Partial<P> = {};
const targetClassNames: (string | ReturnType<typeof css>)[] = [
'rcx-box',
'rcx-box--full',
];
for (const [propName, propValue] of Object.entries(props)) { for (const [propName, propValue] of Object.entries(props)) {
if (isRcxProp(propName)) { if (isRcxProp(propName)) {
delete props[propName]; consumeRcxProp(propName, propValue, targetProps, targetClassNames);
consumeRcxProp(propName, propValue, props);
continue; continue;
} }
if (isPreAlias(propName)) { if (isPreAlias(propName)) {
delete props[propName]; consumePreAlias(propName, propValue, targetProps, targetClassNames);
consumePreAlias(propName, propValue, props);
continue; continue;
} }
if (isSpecialStylingProp(propName)) { if (isSpecialStylingProp(propName)) {
delete props[propName]; consumeSpecialStylingProp(
consumeSpecialStylingProp(propName, propValue, props); propName,
propValue,
targetProps,
targetClassNames
);
continue; continue;
} }
if (isCssPropertyProp(propName)) { if (isCssPropertyProp(propName)) {
delete props[propName]; consumeCssPropertyProp(
consumeCssPropertyProp(propName, propValue, props); propName,
propValue,
targetProps,
targetClassNames
);
continue; continue;
} }
if (isPostAlias(propName)) { if (isPostAlias(propName)) {
delete props[propName]; consumePostAlias(propName, propValue, targetProps, targetClassNames);
consumePostAlias(propName, propValue, props);
continue; continue;
} }
targetProps[propName] = propValue;
} }
if (props.animated) { if (props.animated) {
...@@ -114,5 +159,25 @@ export const consumeBoxProps = (props: BoxProps): Record<string, unknown> => { ...@@ -114,5 +159,25 @@ export const consumeBoxProps = (props: BoxProps): Record<string, unknown> => {
props.className = prependClassName(props.className, 'rcx-box--full'); props.className = prependClassName(props.className, 'rcx-box--full');
props.className = prependClassName(props.className, 'rcx-box'); props.className = prependClassName(props.className, 'rcx-box');
return props; const styles = targetClassNames.filter(
(value): value is ReturnType<typeof css> => typeof value === 'function'
);
const stylesClassName = useStyle(
css`
${styles}
`,
props
);
const classNames = targetClassNames.filter(
(value): value is string => typeof value === 'string'
);
if (stylesClassName) {
classNames.push(stylesClassName);
}
return Object.assign(targetProps, {
className: classNames.join(' '),
}) as P;
}; };
import { css } from '@rocket.chat/css-in-js'; import { css } from '@rocket.chat/css-in-js';
import { prependClassName } from '../../../helpers/prependClassName';
export type RcxPropTypes = { export type RcxPropTypes = {
[propName in string]?: boolean | number | string; [propName in string]?: boolean | number | string;
}; };
export const isRcxProp = (propName: string): propName is keyof RcxPropTypes => export const isRcxProp = (propName: string): boolean =>
propName.slice(0, 4) === 'rcx-'; propName.slice(0, 4) === 'rcx-';
export const consumeRcxProp = < export const consumeRcxProp = <
...@@ -19,7 +17,8 @@ export const consumeRcxProp = < ...@@ -19,7 +17,8 @@ export const consumeRcxProp = <
>( >(
rcxClassName: keyof RcxPropTypes, rcxClassName: keyof RcxPropTypes,
value: RcxPropTypes[keyof RcxPropTypes], value: RcxPropTypes[keyof RcxPropTypes],
targetProps: P _props: P,
classNames: (string | ReturnType<typeof css>)[]
): void => { ): void => {
if (!value) { if (!value) {
return; return;
...@@ -27,5 +26,5 @@ export const consumeRcxProp = < ...@@ -27,5 +26,5 @@ export const consumeRcxProp = <
const newClassName = const newClassName =
value === true ? rcxClassName : `${rcxClassName}-${value}`; value === true ? rcxClassName : `${rcxClassName}-${value}`;
targetProps.className = prependClassName(targetProps.className, newClassName); classNames.push(newClassName);
}; };
import { appendClassName } from '../../../helpers/appendClassName'; import { css } from '@rocket.chat/css-in-js';
import { elevation } from '../../../styles/runtime/elevation'; import { elevation } from '../../../styles/runtime/elevation';
import { fontScale } from '../../../styles/runtime/fontScales'; import { fontScale } from '../../../styles/runtime/fontScales';
import { invisible } from '../../../styles/runtime/invisible'; import { invisible } from '../../../styles/runtime/invisible';
...@@ -27,10 +28,11 @@ export const isSpecialStylingProp = ( ...@@ -27,10 +28,11 @@ export const isSpecialStylingProp = (
propName: string propName: string
): propName is keyof SpecialStylingPropTypes => propName in specialStylingProps; ): propName is keyof SpecialStylingPropTypes => propName in specialStylingProps;
export const consumeSpecialStylingProp = ( export const consumeSpecialStylingProp = <P>(
propName: keyof SpecialStylingPropTypes, propName: keyof SpecialStylingPropTypes,
propValue: SpecialStylingPropTypes[keyof SpecialStylingPropTypes], propValue: SpecialStylingPropTypes[keyof SpecialStylingPropTypes],
props: Record<string, unknown> _props: P,
classNames: (string | ReturnType<typeof css>)[]
): void => { ): void => {
const fn = specialStylingProps[propName]; const fn = specialStylingProps[propName];
const style = fn(propValue as never); const style = fn(propValue as never);
...@@ -38,5 +40,5 @@ export const consumeSpecialStylingProp = ( ...@@ -38,5 +40,5 @@ export const consumeSpecialStylingProp = (
return; return;
} }
props.className = appendClassName(props.className, style); classNames.push(style);
}; };
import { css } from '@rocket.chat/css-in-js';
import { appendClassName } from '../../helpers/appendClassName';
import { useStyle } from '../../hooks/useStyle';
import { BoxProps } from './props';
export const useArrayLikeClassNameProp = (
props: BoxProps
): Record<string, unknown> => {
const classNames = ([] as (
| undefined
| string
| ReturnType<typeof css>
)[]).concat(
props.className as
| undefined
| string
| ReturnType<typeof css>
| (undefined | string | ReturnType<typeof css>)[]
);
const cssFns = classNames.filter((value) => typeof value === 'function');
const stylesClassName = useStyle(
css`
${cssFns}
`,
props
);
const strings = classNames.filter((value) => typeof value === 'string');
props.className = strings.reduce(
(className, string) => appendClassName(className, string),
stylesClassName || ''
);
return props;
};
import { createElement } from 'react'; import { ComponentType, createElement, FC } from 'react';
import { consumeBoxStylingProps } from '../components/Box/props'; import { BoxStylingProps, useBoxStylingProps } from '../components/Box/props';
import { useArrayLikeClassNameProp } from '../components/Box/useArrayLikeClassNameProp';
import { useStyleSheet } from '../hooks/useStyleSheet'; import { useStyleSheet } from '../hooks/useStyleSheet';
export const withBoxStyling = (component) => { export const withBoxStyling = <P extends { className?: string }>(
const WithBoxStyling = ({ ...props }) => { component: ComponentType<P>
): ComponentType<
BoxStylingProps & Omit<P, 'className' | keyof BoxStylingProps>
> => {
const WithBoxStyling: FC<
BoxStylingProps & Omit<P, 'className' | keyof BoxStylingProps>
> = (props) => {
useStyleSheet(); useStyleSheet();