From 80b274235b652be328c1500402c8dfc6b0e0f4fa Mon Sep 17 00:00:00 2001 From: Tobias Date: Tue, 18 Oct 2022 08:53:50 +0200 Subject: [PATCH] fix(Space): rewrite Space component to TypeScript --- .../docs/uilib/components/space/properties.md | 2 +- .../__snapshots__/Button.test.js.snap | 4 +- .../__snapshots__/GlobalError.test.js.snap | 4 +- .../height-animation/HeightAnimation.tsx | 17 +- .../__tests__/HeightAnimation.test.tsx | 9 +- .../components/space/{Space.js => Space.tsx} | 113 +++++--- .../src/components/space/SpacingHelper.js | 265 +----------------- .../src/components/space/SpacingUtils.ts | 261 +++++++++++++++++ .../{Space.test.js => Space.test.tsx} | 0 ...ngHelper.test.js => SpacingUtils.test.tsx} | 5 +- ...Space.test.js.snap => Space.test.tsx.snap} | 0 .../{Space.stories.js => Space.stories.tsx} | 24 +- .../dnb-eufemia/src/components/space/types.ts | 41 +++ .../src/components/upload/Upload.tsx | 2 +- packages/dnb-eufemia/src/elements/Anchor.tsx | 12 +- packages/dnb-eufemia/src/elements/Dd.tsx | 2 +- packages/dnb-eufemia/src/elements/Dl.tsx | 6 +- packages/dnb-eufemia/src/elements/Dt.tsx | 7 +- packages/dnb-eufemia/src/elements/Element.tsx | 60 ++-- packages/dnb-eufemia/src/elements/Li.tsx | 2 +- packages/dnb-eufemia/src/elements/Ol.tsx | 2 +- packages/dnb-eufemia/src/elements/Ul.tsx | 2 +- .../src/elements/__tests__/Element.test.tsx | 18 +- .../__snapshots__/Element.test.tsx.snap | 10 +- packages/dnb-eufemia/src/shared/types.tsx | 31 +- 25 files changed, 504 insertions(+), 395 deletions(-) rename packages/dnb-eufemia/src/components/space/{Space.js => Space.tsx} (72%) create mode 100644 packages/dnb-eufemia/src/components/space/SpacingUtils.ts rename packages/dnb-eufemia/src/components/space/__tests__/{Space.test.js => Space.test.tsx} (100%) rename packages/dnb-eufemia/src/components/space/__tests__/{SpacingHelper.test.js => SpacingUtils.test.tsx} (98%) rename packages/dnb-eufemia/src/components/space/__tests__/__snapshots__/{Space.test.js.snap => Space.test.tsx.snap} (100%) rename packages/dnb-eufemia/src/components/space/stories/{Space.stories.js => Space.stories.tsx} (93%) create mode 100644 packages/dnb-eufemia/src/components/space/types.ts diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/space/properties.md b/packages/dnb-design-system-portal/src/docs/uilib/components/space/properties.md index 7dd807c6798..a410adbc7ab 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/space/properties.md +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/space/properties.md @@ -20,7 +20,7 @@ These properties are available in many other components and elements. | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `element` | _(optional)_ defines the HTML element used. Defaults to `div`. | | `stretch` | _(optional)_ if set to `true`, then the space element will be 100% in `width`. | -| `inline` | _(optional)_ if set to `true`, then `display: inline-block;` is used, so the HTML elements get aligned horizontally. Defaults to `false`. | +| `inline` | _(optional)_ if set to `true`, then `display: inline-block;` is used, so the HTML elements get aligned horizontally. Defaults to `false`. | | `no_collapse` | _(optional)_ if set to `true`, then a wrapper with `display: flow-root;` is used. This way you avoid **Margin Collapsing**. Defaults to `false`. _Note:_ You can't use `inline="true"` in combination. | ## Zero diff --git a/packages/dnb-eufemia/src/components/button/__tests__/__snapshots__/Button.test.js.snap b/packages/dnb-eufemia/src/components/button/__tests__/__snapshots__/Button.test.js.snap index 90e73b517cf..7e7cf9f27d8 100644 --- a/packages/dnb-eufemia/src/components/button/__tests__/__snapshots__/Button.test.js.snap +++ b/packages/dnb-eufemia/src/components/button/__tests__/__snapshots__/Button.test.js.snap @@ -518,6 +518,7 @@ exports[`Button component have to match href="..." snapshot 1`] = ` type="type" > , } } - is="a" onClick={[Function]} skeleton={null} title="This is a button title" type="type" > , } } - is="a" onClick={[Function]} skeleton={null} title="This is a button title" diff --git a/packages/dnb-eufemia/src/components/global-error/__tests__/__snapshots__/GlobalError.test.js.snap b/packages/dnb-eufemia/src/components/global-error/__tests__/__snapshots__/GlobalError.test.js.snap index 2e7e9ba490a..d9a201f5513 100644 --- a/packages/dnb-eufemia/src/components/global-error/__tests__/__snapshots__/GlobalError.test.js.snap +++ b/packages/dnb-eufemia/src/components/global-error/__tests__/__snapshots__/GlobalError.test.js.snap @@ -800,6 +800,7 @@ exports[`GlobalError snapshot have to match component snapshot 1`] = ` onClick={[Function]} > , } } - is="a" onClick={[Function]} skeleton={null} > , } } - is="a" onClick={[Function]} skeleton={null} > diff --git a/packages/dnb-eufemia/src/components/height-animation/HeightAnimation.tsx b/packages/dnb-eufemia/src/components/height-animation/HeightAnimation.tsx index a34de3f21a6..d082caa914f 100644 --- a/packages/dnb-eufemia/src/components/height-animation/HeightAnimation.tsx +++ b/packages/dnb-eufemia/src/components/height-animation/HeightAnimation.tsx @@ -1,11 +1,13 @@ import React from 'react' import classnames from 'classnames' -import { DynamicElement, SpacingProps } from '../../shared/types' +import { SpacingProps } from '../../shared/types' import { useHeightAnimation, useHeightAnimationOptions, } from './useHeightAnimation' -import Space from '../space/Space' +import Space, { SpaceProps } from '../space/Space' + +import type { DynamicElement } from '../../shared/types' export type HeightAnimationProps = { /** @@ -37,10 +39,12 @@ export type HeightAnimationProps = { * Default: null */ innerRef?: React.RefObject - - className?: React.ReactNode } & useHeightAnimationOptions +export type HeightAnimationAllProps = HeightAnimationProps & + SpacingProps & + React.HTMLProps + export default function HeightAnimation({ open = false, animate = true, @@ -54,9 +58,10 @@ export default function HeightAnimation({ onInit = null, onOpen = null, onAnimationEnd = null, - ...props -}: HeightAnimationProps & SpacingProps) { + ...rest +}: HeightAnimationAllProps) { const ref = React.useRef() + const props = rest as SpaceProps const { isInDOM, isVisible, isVisibleParallax, isAnimating } = useHeightAnimation(innerRef || ref, { diff --git a/packages/dnb-eufemia/src/components/height-animation/__tests__/HeightAnimation.test.tsx b/packages/dnb-eufemia/src/components/height-animation/__tests__/HeightAnimation.test.tsx index 553b40876d0..6cfcf5a1ec0 100644 --- a/packages/dnb-eufemia/src/components/height-animation/__tests__/HeightAnimation.test.tsx +++ b/packages/dnb-eufemia/src/components/height-animation/__tests__/HeightAnimation.test.tsx @@ -7,7 +7,10 @@ import React from 'react' import { render, act, fireEvent } from '@testing-library/react' import ToggleButton from '../../ToggleButton' import { wait } from '@testing-library/user-event/dist/utils' -import HeightAnimation, { HeightAnimationProps } from '../HeightAnimation' +import HeightAnimation, { + HeightAnimationAllProps, + HeightAnimationProps, +} from '../HeightAnimation' import HeightAnimationInstance from '../HeightAnimationInstance' beforeEach(() => { @@ -35,7 +38,7 @@ describe('HeightAnimation', () => { animate = true, element = 'div', children, - ...props + ...rest }: Partial) => { const [openState, setOpenState] = React.useState(open) @@ -47,6 +50,8 @@ describe('HeightAnimation', () => { setOpenState(open) }, [open]) + const props = rest as HeightAnimationAllProps + return ( <> diff --git a/packages/dnb-eufemia/src/components/space/Space.js b/packages/dnb-eufemia/src/components/space/Space.tsx similarity index 72% rename from packages/dnb-eufemia/src/components/space/Space.js rename to packages/dnb-eufemia/src/components/space/Space.tsx index 60897114434..d82f1009e55 100644 --- a/packages/dnb-eufemia/src/components/space/Space.js +++ b/packages/dnb-eufemia/src/components/space/Space.tsx @@ -14,20 +14,95 @@ import { validateDOMAttributes, } from '../../shared/component-helper' import Context from '../../shared/Context' +import { spacingPropTypes } from './SpacingHelper' import { createSpacingClasses, isInline, - spacingPropTypes, spacingDefaultProps, -} from './SpacingHelper' +} from './SpacingUtils' import { skeletonDOMAttributes, createSkeletonClass, } from '../skeleton/SkeletonHelper' +import type { DynamicElement, SpacingProps } from '../../shared/types' + export { spacingPropTypes } -export default class Space extends React.PureComponent { +function Element({ + element, + no_collapse, + children, + innerRef, + ...props +}: SpaceAllProps) { + const E = element as DynamicElement + const component = ( + + {children} + + ) + + if (isTrue(no_collapse)) { + const R = E === 'span' || isInline(Element) ? 'span' : 'div' + return ( + + {component} + + ) + } + + return component +} + +export type SpaceProps = { + /** + * Defines the HTML element used. + * Default: div + */ + element?: DynamicElement + + /** + * If set to `true`, then `display: inline-block;` is used, so the HTML elements get aligned horizontally. Defaults to `false`. + * Default: false + */ + inline?: boolean + + /** + * If set to `true`, then a wrapper with `display: flow-root;` is used. This way you avoid **Margin Collapsing**. Defaults to `false`. _Note:_ You can't use `inline="true"` in combination. + * Default: false + */ + no_collapse?: boolean + + /** + * If set to `true`, then the space element will be 100% in `width`. + * Default: false + */ + stretch?: boolean + + /** + * If set to `true`, a loading skeleton will be shown. + * Default: false + */ + skeleton?: boolean + + /** + * Send along a custom React Ref. + * Default: null + */ + innerRef?: React.RefObject +} & SpacingProps + +export type SpaceAllProps = SpaceProps & React.HTMLProps + +export default class Space extends React.PureComponent< + SpaceAllProps | React.HTMLProps +> { static tagName = 'dnb-space' static contextType = Context @@ -102,7 +177,7 @@ export default class Space extends React.PureComponent { className, ...attributes - } = props + } = props as SpaceAllProps // in case we have a label already, we split this out and use this one instead const children = Space.getContent(this.props) @@ -136,33 +211,3 @@ export default class Space extends React.PureComponent { ) } } - -const Element = ({ - element: E, - no_collapse, - children, - innerRef, - ...props -}) => { - const component = ( - - {children} - - ) - - if (isTrue(no_collapse)) { - const R = E === 'span' || isInline(Element) ? 'span' : 'div' - return ( - - {component} - - ) - } - - return component -} diff --git a/packages/dnb-eufemia/src/components/space/SpacingHelper.js b/packages/dnb-eufemia/src/components/space/SpacingHelper.js index 7e054cb4ea3..8e7739bfc14 100644 --- a/packages/dnb-eufemia/src/components/space/SpacingHelper.js +++ b/packages/dnb-eufemia/src/components/space/SpacingHelper.js @@ -1,10 +1,16 @@ /** * Space helper - * + * NB: Because it contains "PropTypes" we keep this file as a JavaScirpt file. */ import PropTypes from 'prop-types' -import { warn } from '../../shared/component-helper' +import { + createSpacingClasses, + removeSpaceProps, + createStyleObject, +} from './SpacingUtils' + +export { createSpacingClasses, removeSpaceProps, createStyleObject } // because of ESLint import plugin export const spacingPropTypes = { space: PropTypes.oneOfType([ @@ -55,258 +61,3 @@ export const spacingPropTypes = { PropTypes.bool, ]), } - -export const spacingDefaultProps = { - space: null, - top: null, - right: null, - bottom: null, - left: null, -} - -// IMPORTANT: Keep the shorthand after the long type names -export const spacePatterns = { - 'xx-small': 0.25, - 'x-small': 0.5, - small: 1, - medium: 1.5, - large: 2, - 'x-large': 3, - 'xx-large': 3.5, - 'xx-large-x2': 7, -} - -export const translateSpace = (type) => { - if (/-x2$/.test(type)) { - return spacePatterns[type.replace(/-x2$/, '')] * 2 - } - return spacePatterns[type] || 0 -} - -// Splits a string of: "large x-small" into an array of the same -export const splitTypes = (types) => { - if (typeof types === 'string') { - types = types.split(/ /g) - } else if (typeof types === 'boolean') { - return ['small'] - } else if (typeof types === 'number') { - return [types] - } - return types ? types.filter((r) => r && r.length > 0) : null -} - -// Sums e.g. "large" + "x-small" to be = 2.5rem -export const sumTypes = (types) => - splitTypes(types) - .map((type) => translateSpace(type)) - .reduce((acc, cur) => { - if (cur > 0) { - acc += cur - } else if (cur < 0) { - acc -= cur - } - return acc - }, 0) - -// Returns an array with modifiers e.g. ["--large" + "--x-small"] -export const createTypeModifiers = (types) => { - if (typeof types === 'number') { - types = String(types) - } - return (splitTypes(types) || []).reduce((acc, type) => { - if (type) { - const firstLetter = type[0] - if (parseFloat(firstLetter) > -1) { - // can be "2rem" or "32px" - but we want only a number - let num = parseFloat(type) - - // check if we got pixels - if (num >= 8 && /[0-9]px/.test(type)) { - num = num / 16 - } - - // check if the type exists in our extensions - const foundType = findType(num) - - // get the type - if (foundType) { - type = foundType - } else { - findNearestTypes(num).forEach((type) => { - if (type) { - acc.push(type) - } - }) - } - } - - if (!(parseFloat(type) > 0)) { - acc.push(type) - } - } - return acc - }, []) -} - -// Finds from "2.0" the equivalent type "large" -export const findType = (num, { returnObject = false } = {}) => { - const found = - Object.entries(spacePatterns).find(([k, v]) => k && v === num) || null - - if (returnObject) { - return found - } - - // get the type - if (found) { - return found[0] - } - - return found -} - -// Finds from e.g. a value of "2.5rem" the nearest type = ["large", "x-small"] -export const findNearestTypes = (num) => { - let res = [] - - const near = Object.entries(spacePatterns) - .reverse() - .find(([k, v]) => k && num >= v) - const nearNum = (near && near[1]) || num - - const typeObject = findType(nearNum, { returnObject: true }) - - if (typeObject) { - const nearType = typeObject[0] - res.push(nearType) - const leftOver = num - parseFloat(typeObject[1]) - const foundMoreTypes = findNearestTypes(leftOver) - - // if the value already exists, then replace it with an x2 - foundMoreTypes.forEach((type) => { - const index = res.indexOf(type) - if (index !== -1) { - res[index] = `${type}-x2` - } - }) - - res = [...res, ...foundMoreTypes] - } - - return res -} - -// Checks if a space prop is a valid string like "top" -export const isValidSpaceProp = (prop) => - prop && ['top', 'right', 'bottom', 'left'].includes(prop) - -export const removeSpaceProps = ({ ...props }) => { - for (let i in props) { - if (isValidSpaceProp(i)) { - delete props[i] - } - } - return props -} - -// Creates a valid space CSS class out from given space types -export const createSpacingClasses = (props, Element = null) => { - if (typeof props.space !== 'undefined') { - if ( - typeof props.space === 'string' || - typeof props.space === 'number' || - (typeof props.space === 'boolean' && props.space) - ) { - props.top = props.right = props.bottom = props.left = props.space - } - for (let i in props.space) { - if (!props[i] && isValidSpaceProp(i)) { - props[i] = props.space[i] - } - } - delete props.space - } - - return Object.entries(props).reduce((acc, [direction, cur]) => { - if (isValidSpaceProp(direction)) { - if (String(cur) === '0' || String(cur) === 'false') { - acc.push(`dnb-space__${direction}--zero`) - } else if (cur) { - const typeModifiers = createTypeModifiers(cur) - - // get the total sum - const sum = sumTypes(typeModifiers) - if (sum > 10) { - warn( - `Spacing of more than 10rem is not supported! You used ${sum} / (${typeModifiers.join( - ',' - )})` - ) - } else { - // auto combine classes - const nearestTypes = findNearestTypes(sum) - - acc = [ - ...acc, - ...nearestTypes.map( - (type) => `dnb-space__${direction}--${type}` - ), - ] - } - } - } else if (direction === 'no_collapse') { - acc.push('dnb-space--no-collapse') - if (Element && isInline(Element)) { - acc.push('dnb-space--inline') - } - } - - return acc - }, []) -} - -// Creates a CSS Style Object out from given space types -export const createStyleObject = (props) => { - if (props.top && !(parseFloat(props.top) > 0)) { - props.top = sumTypes(props.top) - } - if (props.bottom && !(parseFloat(props.bottom) > 0)) { - props.bottom = sumTypes(props.bottom) - } - if (props.left && !(parseFloat(props.left) > 0)) { - props.left = sumTypes(props.left) - } - if (props.right && !(parseFloat(props.right) > 0)) { - props.right = sumTypes(props.right) - } - return Object.entries({ - marginTop: props.top && `${props.top}rem`, - marginBottom: props.bottom && `${props.bottom}rem`, - maxWidth: props.maxWidth && `${props.maxWidth}rem`, - maxHeight: props.maxHeight && `${props.maxHeight}rem`, - width: props.width && `${props.width}rem`, - height: props.height && `${props.height}rem`, - }).reduce((acc, [key, val]) => { - if (typeof val !== 'undefined') { - acc[key] = val - } - return acc - }, {}) -} - -export const isInline = (Element) => { - let inline = false - switch (Element) { - case 'h1': - case 'h2': - case 'h3': - case 'h4': - case 'h5': - case 'h6': - case 'p': - inline = true - break - } - - return inline -} diff --git a/packages/dnb-eufemia/src/components/space/SpacingUtils.ts b/packages/dnb-eufemia/src/components/space/SpacingUtils.ts new file mode 100644 index 00000000000..6c5179f489b --- /dev/null +++ b/packages/dnb-eufemia/src/components/space/SpacingUtils.ts @@ -0,0 +1,261 @@ +/** + * Space helper + * + */ + +import { warn } from '../../shared/component-helper' + +export const spacingDefaultProps = { + space: null, + top: null, + right: null, + bottom: null, + left: null, +} + +// IMPORTANT: Keep the shorthand after the long type names +export const spacePatterns = { + 'xx-small': 0.25, + 'x-small': 0.5, + small: 1, + medium: 1.5, + large: 2, + 'x-large': 3, + 'xx-large': 3.5, + 'xx-large-x2': 7, +} + +export const translateSpace = (type) => { + if (/-x2$/.test(type)) { + return spacePatterns[type.replace(/-x2$/, '')] * 2 + } + return spacePatterns[type] || 0 +} + +// Splits a string of: "large x-small" into an array of the same +export const splitTypes = (types) => { + if (typeof types === 'string') { + types = types.split(/ /g) + } else if (typeof types === 'boolean') { + return ['small'] + } else if (typeof types === 'number') { + return [types] + } + return types ? types.filter((r) => r && r.length > 0) : null +} + +// Sums e.g. "large" + "x-small" to be = 2.5rem +export const sumTypes = (types) => + splitTypes(types) + .map((type) => translateSpace(type)) + .reduce((acc, cur) => { + if (cur > 0) { + acc += cur + } else if (cur < 0) { + acc -= cur + } + return acc + }, 0) + +// Returns an array with modifiers e.g. ["--large" + "--x-small"] +export const createTypeModifiers = (types) => { + if (typeof types === 'number') { + types = String(types) + } + return (splitTypes(types) || []).reduce((acc, type) => { + if (type) { + const firstLetter = type[0] + if (parseFloat(firstLetter) > -1) { + // can be "2rem" or "32px" - but we want only a number + let num = parseFloat(type) + + // check if we got pixels + if (num >= 8 && /[0-9]px/.test(type)) { + num = num / 16 + } + + // check if the type exists in our extensions + const foundType = findType(num) + + // get the type + if (foundType) { + type = foundType + } else { + findNearestTypes(num).forEach((type) => { + if (type) { + acc.push(type) + } + }) + } + } + + if (!(parseFloat(type) > 0)) { + acc.push(type) + } + } + return acc + }, []) +} + +// Finds from "2.0" the equivalent type "large" +export const findType = (num, { returnObject = false } = {}) => { + const found = + Object.entries(spacePatterns).find(([k, v]) => k && v === num) || null + + if (returnObject) { + return found + } + + // get the type + if (found) { + return found[0] + } + + return found +} + +// Finds from e.g. a value of "2.5rem" the nearest type = ["large", "x-small"] +export const findNearestTypes = (num) => { + let res = [] + + const near = Object.entries(spacePatterns) + .reverse() + .find(([k, v]) => k && num >= v) + const nearNum = (near && near[1]) || num + + const typeObject = findType(nearNum, { returnObject: true }) + + if (typeObject) { + const nearType = typeObject[0] + res.push(nearType) + const leftOver = num - parseFloat(String(typeObject[1])) + const foundMoreTypes = findNearestTypes(leftOver) + + // if the value already exists, then replace it with an x2 + foundMoreTypes.forEach((type) => { + const index = res.indexOf(type) + if (index !== -1) { + res[index] = `${type}-x2` + } + }) + + res = [...res, ...foundMoreTypes] + } + + return res +} + +// Checks if a space prop is a valid string like "top" +export const isValidSpaceProp = (prop) => + prop && ['top', 'right', 'bottom', 'left'].includes(prop) + +export const removeSpaceProps = ({ ...props }) => { + for (const i in props) { + if (isValidSpaceProp(i)) { + delete props[i] + } + } + return props +} + +// Creates a valid space CSS class out from given space types +export const createSpacingClasses = (props, Element = null) => { + if (typeof props.space !== 'undefined') { + if ( + typeof props.space === 'string' || + typeof props.space === 'number' || + (typeof props.space === 'boolean' && props.space) + ) { + props.top = props.right = props.bottom = props.left = props.space + } + for (const i in props.space) { + if (!props[i] && isValidSpaceProp(i)) { + props[i] = props.space[i] + } + } + delete props.space + } + + return Object.entries(props).reduce((acc, [direction, cur]) => { + if (isValidSpaceProp(direction)) { + if (String(cur) === '0' || String(cur) === 'false') { + acc.push(`dnb-space__${direction}--zero`) + } else if (cur) { + const typeModifiers = createTypeModifiers(cur) + + // get the total sum + const sum = sumTypes(typeModifiers) + if (sum > 10) { + warn( + `Spacing of more than 10rem is not supported! You used ${sum} / (${typeModifiers.join( + ',' + )})` + ) + } else { + // auto combine classes + const nearestTypes = findNearestTypes(sum) + + acc = [ + ...acc, + ...nearestTypes.map( + (type) => `dnb-space__${direction}--${type}` + ), + ] + } + } + } else if (direction === 'no_collapse') { + acc.push('dnb-space--no-collapse') + if (Element && isInline(Element)) { + acc.push('dnb-space--inline') + } + } + + return acc + }, []) +} + +// Creates a CSS Style Object out from given space types +export const createStyleObject = (props) => { + if (props.top && !(parseFloat(props.top) > 0)) { + props.top = sumTypes(props.top) + } + if (props.bottom && !(parseFloat(props.bottom) > 0)) { + props.bottom = sumTypes(props.bottom) + } + if (props.left && !(parseFloat(props.left) > 0)) { + props.left = sumTypes(props.left) + } + if (props.right && !(parseFloat(props.right) > 0)) { + props.right = sumTypes(props.right) + } + return Object.entries({ + marginTop: props.top && `${props.top}rem`, + marginBottom: props.bottom && `${props.bottom}rem`, + maxWidth: props.maxWidth && `${props.maxWidth}rem`, + maxHeight: props.maxHeight && `${props.maxHeight}rem`, + width: props.width && `${props.width}rem`, + height: props.height && `${props.height}rem`, + }).reduce((acc, [key, val]) => { + if (typeof val !== 'undefined') { + acc[key] = val + } + return acc + }, {}) +} + +export const isInline = (Element) => { + let inline = false + switch (Element) { + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + case 'p': + inline = true + break + } + + return inline +} diff --git a/packages/dnb-eufemia/src/components/space/__tests__/Space.test.js b/packages/dnb-eufemia/src/components/space/__tests__/Space.test.tsx similarity index 100% rename from packages/dnb-eufemia/src/components/space/__tests__/Space.test.js rename to packages/dnb-eufemia/src/components/space/__tests__/Space.test.tsx diff --git a/packages/dnb-eufemia/src/components/space/__tests__/SpacingHelper.test.js b/packages/dnb-eufemia/src/components/space/__tests__/SpacingUtils.test.tsx similarity index 98% rename from packages/dnb-eufemia/src/components/space/__tests__/SpacingHelper.test.js rename to packages/dnb-eufemia/src/components/space/__tests__/SpacingUtils.test.tsx index ac66ecdd006..eb4ded5ffa6 100644 --- a/packages/dnb-eufemia/src/components/space/__tests__/SpacingHelper.test.js +++ b/packages/dnb-eufemia/src/components/space/__tests__/SpacingUtils.test.tsx @@ -3,10 +3,8 @@ * */ -// import React from 'react' import '../../../core/jest/jestSetup' import { - spacingPropTypes, spacePatterns, translateSpace, splitTypes, @@ -17,7 +15,8 @@ import { isValidSpaceProp, createStyleObject, createSpacingClasses, -} from '../SpacingHelper' +} from '../SpacingUtils' +import { spacingPropTypes } from '../SpacingHelper' describe('spacePatterns', () => { it('should be an object with valid keys', () => { diff --git a/packages/dnb-eufemia/src/components/space/__tests__/__snapshots__/Space.test.js.snap b/packages/dnb-eufemia/src/components/space/__tests__/__snapshots__/Space.test.tsx.snap similarity index 100% rename from packages/dnb-eufemia/src/components/space/__tests__/__snapshots__/Space.test.js.snap rename to packages/dnb-eufemia/src/components/space/__tests__/__snapshots__/Space.test.tsx.snap diff --git a/packages/dnb-eufemia/src/components/space/stories/Space.stories.js b/packages/dnb-eufemia/src/components/space/stories/Space.stories.tsx similarity index 93% rename from packages/dnb-eufemia/src/components/space/stories/Space.stories.js rename to packages/dnb-eufemia/src/components/space/stories/Space.stories.tsx index 3eb5275c92a..7464e566be4 100644 --- a/packages/dnb-eufemia/src/components/space/stories/Space.stories.js +++ b/packages/dnb-eufemia/src/components/space/stories/Space.stories.tsx @@ -9,17 +9,9 @@ import { Wrapper, Box } from 'storybook-utils/helpers' import styled from '@emotion/styled' import { css, Global } from '@emotion/react' -import { - Space, - // Checkbox, - // Radio, - Input, - // FormLabel, - // FormRow -} from '../../' +import { Space, Input } from '../../' import { H1, H2 } from '../../../elements' import Provider from '../../../shared/Provider' -// import { spacingPropTypes, createSpacingClasses } from '../../space/SpacingHelper' export default { title: 'Eufemia/Components/Space', @@ -151,8 +143,8 @@ const Label = styled.label` color: var(--color-black-80); ` -const MagicBox = ({ label, ...rest }) => { - const ref = React.createRef() +const MagicBox = ({ label, ...rest }: { label?: string } = {}) => { + const ref = React.createRef() const [spaceInRem, setLabel] = React.useState(label) const [title, setTitle] = React.useState(null) @@ -188,8 +180,12 @@ MagicBox.defaultProps = { label: null, } -const VisualSpace = ({ label, children, ...rest }) => { - const ref = React.createRef() +const VisualSpace = ({ + label, + children, + ...rest +}: { label?: string; children?: React.ReactNode } = {}) => { + const ref = React.createRef() const [spaceInRem, setLabel] = React.useState(label) const [title, setTitle] = React.useState(null) @@ -231,7 +227,7 @@ VisualSpace.defaultProps = { children: null, } -const Collapsing = styled(Space)` +const Collapsing = styled(Space)` border: 1px solid; ` diff --git a/packages/dnb-eufemia/src/components/space/types.ts b/packages/dnb-eufemia/src/components/space/types.ts new file mode 100644 index 00000000000..afd7b5066c9 --- /dev/null +++ b/packages/dnb-eufemia/src/components/space/types.ts @@ -0,0 +1,41 @@ +export type SpacingElementProps = { + /** + * Use spacing values like: `small`, `1rem`, `1` or , `16px`. Will use `margin-top`. + * + */ + top?: SpaceTypes + + /** + * Use spacing values like: `small`, `1rem`, `1` or , `16px`. will use `margin-right`. + * + */ + right?: SpaceTypes + + /** + * Use spacing values like: `small`, `1rem`, `1` or , `16px`. will use `margin-bottom`. + * + */ + bottom?: SpaceTypes + + /** + * Use spacing values like: `small`, `1rem`, `1` or , `16px`. will use `margin-left`. + * + */ + left?: SpaceTypes +} + +export type SpaceTypes = string | boolean | number + +export type SpacingProps = SpacingElementProps & { + space?: SpaceTypes | SpacingElementProps +} + +/** + * @deprecated Use SpacingElementProps instead + */ +export type ISpacingElementProps = SpacingElementProps + +/** + * @deprecated Use SpacingProps instead + */ +export type ISpacingProps = SpacingProps diff --git a/packages/dnb-eufemia/src/components/upload/Upload.tsx b/packages/dnb-eufemia/src/components/upload/Upload.tsx index b99436a0e5e..9f14d777b9b 100644 --- a/packages/dnb-eufemia/src/components/upload/Upload.tsx +++ b/packages/dnb-eufemia/src/components/upload/Upload.tsx @@ -15,7 +15,7 @@ import Provider from '../../shared/Provider' import Context from '../../shared/Context' import { extendPropsWithContext } from '../../shared/component-helper' import { format } from '../number-format/NumberUtils' -import { LocaleProps, SpacingProps } from 'src/shared/types' +import { LocaleProps, SpacingProps } from '../../shared/types' // Internal import UploadFileInput from './UploadFileInput' diff --git a/packages/dnb-eufemia/src/elements/Anchor.tsx b/packages/dnb-eufemia/src/elements/Anchor.tsx index 2df3e8ef176..a4f0c5b8d48 100644 --- a/packages/dnb-eufemia/src/elements/Anchor.tsx +++ b/packages/dnb-eufemia/src/elements/Anchor.tsx @@ -5,7 +5,7 @@ import React from 'react' import classnames from 'classnames' -import E from './Element' +import E, { ElementProps, ElementIsType } from './Element' import Context from '../shared/Context' import { makeUniqueId, @@ -15,7 +15,7 @@ import Tooltip from '../components/tooltip/Tooltip' import { SpacingProps } from '../shared/types' export type AnchorProps = { - element?: React.ReactNode + element?: ElementIsType href?: string to?: string targetBlankTitle?: string @@ -76,18 +76,22 @@ function AnchorInstance(localProps: AnchorProps) { omitClass, innerRef, targetBlankTitle, - ...attributes + ...rest } = allProps + const attributes = rest as ElementProps + const internalId = id || 'id' + makeUniqueId() // WCAG guide: https://www.w3.org/TR/WCAG20-TECHS/G201.html const showTooltip = allProps.target === '_blank' && !allProps.title + const as = (element || 'a') as string + return ( <> ( - + )) export default Dd diff --git a/packages/dnb-eufemia/src/elements/Dl.tsx b/packages/dnb-eufemia/src/elements/Dl.tsx index b1f32b3b034..f93629da7fb 100644 --- a/packages/dnb-eufemia/src/elements/Dl.tsx +++ b/packages/dnb-eufemia/src/elements/Dl.tsx @@ -16,7 +16,7 @@ export type DlProps = { } export type DlAllProps = DlProps & - React.AllHTMLAttributes> & + React.AllHTMLAttributes & ElementProps const Dl = ({ direction, ...props }: DlAllProps) => { @@ -26,7 +26,7 @@ const Dl = ({ direction, ...props }: DlAllProps) => { `dnb-dl__direction--${direction}` ) } - return + return } Dl.Item = ({ @@ -38,7 +38,7 @@ Dl.Item = ({ <> {children} const Dt = React.forwardRef((props: DtProps & SpacingProps, ref) => ( - + )) export default Dt diff --git a/packages/dnb-eufemia/src/elements/Element.tsx b/packages/dnb-eufemia/src/elements/Element.tsx index 6c679f6fc86..7e2e2cee8d3 100644 --- a/packages/dnb-eufemia/src/elements/Element.tsx +++ b/packages/dnb-eufemia/src/elements/Element.tsx @@ -17,28 +17,44 @@ import { SkeletonMethods, } from '../components/skeleton/SkeletonHelper' import { includeValidProps } from '../components/form-row/FormRowHelpers' -import { SpacingProps } from '../shared/types' + +import type { DynamicElement, SpacingProps } from '../shared/types' + +export type ElementIsType = string | React.ReactNode // | DynamicElement export type ElementInternalProps = { - is: React.ReactNode + /** + * Defines the Element Type, like "div" + */ + as: ElementIsType + + /** @deprecated use as instead */ + is?: ElementIsType } + export type ElementProps = { skeleton?: boolean skeletonMethod?: SkeletonMethods - class?: string - className?: string internalClass?: string - css?: string + innerRef?: React.RefObject | React.ForwardedRef children?: React.ReactNode - innerRef?: React.ForwardedRef + + /** @deprecated use className instead */ + css?: string + + /** @deprecated use className instead */ + class?: string /** @deprecated use innerRef instead */ - inner_ref?: React.ForwardedRef + inner_ref?: React.RefObject | React.ForwardedRef /** @deprecated use skeletonMethod instead */ skeleton_method?: SkeletonMethods } & SpacingProps -type ElementAllProps = ElementInternalProps & ElementProps + +export type ElementAllProps = ElementProps & + ElementInternalProps & + React.HTMLProps type Attributes = Record @@ -71,20 +87,20 @@ function ElementInstance(localProps: ElementAllProps) { } const { - className, // eslint-disable-line - class: _className, // eslint-disable-line - internalClass, // eslint-disable-line - css, // eslint-disable-line - is, // eslint-disable-line - innerRef, // eslint-disable-line - skeleton, // eslint-disable-line - skeletonMethod, // eslint-disable-line - - ...attributes - }: ElementAllProps & Attributes = props - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const Tag = is as any + className, + class: _className, + internalClass, + css, + as, + is, // deprecated + innerRef, + skeleton, + skeletonMethod, + ...rest + } = props + + const Tag = (as || is) as DynamicElement + const attributes = rest as Attributes const tagClass = internalClass || (typeof Tag === 'string' ? `dnb-${Tag}` : '') diff --git a/packages/dnb-eufemia/src/elements/Li.tsx b/packages/dnb-eufemia/src/elements/Li.tsx index ba97817e623..65fd53387d8 100644 --- a/packages/dnb-eufemia/src/elements/Li.tsx +++ b/packages/dnb-eufemia/src/elements/Li.tsx @@ -27,7 +27,7 @@ const Li = ({ className, ...p }: LiAllProps = {}) => { }) } - return + return } export default Li diff --git a/packages/dnb-eufemia/src/elements/Ol.tsx b/packages/dnb-eufemia/src/elements/Ol.tsx index 0672b64b052..09cfff79f47 100644 --- a/packages/dnb-eufemia/src/elements/Ol.tsx +++ b/packages/dnb-eufemia/src/elements/Ol.tsx @@ -39,7 +39,7 @@ const Ol = ({ nested, inside, outside, ...p }: OlAllProps = {}) => { p.className = classnames(p.className, 'dnb-ol--outside') } - return + return } export default Ol diff --git a/packages/dnb-eufemia/src/elements/Ul.tsx b/packages/dnb-eufemia/src/elements/Ul.tsx index 85c5fc844d2..75c77984ef4 100644 --- a/packages/dnb-eufemia/src/elements/Ul.tsx +++ b/packages/dnb-eufemia/src/elements/Ul.tsx @@ -39,7 +39,7 @@ const Ul = ({ nested, inside, outside, ...p }: UlAllProps = {}) => { p.className = classnames(p.className, 'dnb-ul--outside') } - return + return } export default Ul diff --git a/packages/dnb-eufemia/src/elements/__tests__/Element.test.tsx b/packages/dnb-eufemia/src/elements/__tests__/Element.test.tsx index 1101775161e..98e327b5f87 100644 --- a/packages/dnb-eufemia/src/elements/__tests__/Element.test.tsx +++ b/packages/dnb-eufemia/src/elements/__tests__/Element.test.tsx @@ -16,7 +16,7 @@ import Element, { defaultProps } from '../Element' const props = fakeProps(require.resolve('../Element'), { optional: true, }) -props.is = 'p' +props.as = 'p' props.inner_ref = null props.internalClass = null props.skeleton_method = 'font' @@ -41,7 +41,7 @@ describe('Element', () => { it('should support spacing props', () => { render( - + text ) @@ -56,7 +56,7 @@ describe('Element', () => { it('have to support skeleton', () => { const { container, rerender } = render( - + text ) @@ -66,7 +66,7 @@ describe('Element', () => { ) rerender( - + text ) @@ -76,6 +76,16 @@ describe('Element', () => { ) }) + it('supports deprecated "is"', () => { + const { container } = render( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + text + ) + + expect(container.querySelector('p')).toBeTruthy() + }) + it('does not have inner_ref null inside default propes', () => { expect(defaultProps['inner_ref']).toBe(undefined) }) diff --git a/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/Element.test.tsx.snap b/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/Element.test.tsx.snap index 5391dc9009f..f06463d842c 100644 --- a/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/Element.test.tsx.snap +++ b/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/Element.test.tsx.snap @@ -2,25 +2,25 @@ exports[`Element have to match default Element snapshot 1`] = ` text diff --git a/packages/dnb-eufemia/src/shared/types.tsx b/packages/dnb-eufemia/src/shared/types.tsx index 7508ce0279d..feba420ec67 100644 --- a/packages/dnb-eufemia/src/shared/types.tsx +++ b/packages/dnb-eufemia/src/shared/types.tsx @@ -1,30 +1,9 @@ -import { GetTranslationProps } from './Context' +import React from 'react' +import type { GetTranslationProps } from './Context' +export * from '../components/space/types' export type LocaleProps = GetTranslationProps -export type SpacingElementProps = { - top?: SpaceTypes - right?: SpaceTypes - bottom?: SpaceTypes - left?: SpaceTypes -} - -/** - * @deprecated Use SpacingElementProps instead - */ -export type ISpacingElementProps = SpacingElementProps - -export type SpacingProps = SpacingElementProps & { - space?: SpaceTypes | SpacingElementProps -} - -/** - * @deprecated Use SpacingProps instead - */ -export type ISpacingProps = SpacingProps - -export type SpaceTypes = string | boolean | number - export type DataAttributeTypes = { /** * When using HTMLAttributes on object to define props, @@ -45,6 +24,6 @@ export type DataAttributeTypes = { // [property: `data-${string}`]: string } -export type DynamicElement = +export type DynamicElement

> = | keyof JSX.IntrinsicElements - | React.FunctionComponent + | React.FunctionComponent