From 66416d94e56168cba181376c6580228077950012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Mon, 13 Jun 2022 17:14:02 +0200 Subject: [PATCH] feat(Provider): rewrite to TypeScript (#1436) * feat(Provider): rewrite to TypeScript * Update packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx Co-authored-by: Anders * Update packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx Co-authored-by: Anders Co-authored-by: Anders --- .../scripts/prebuild/tasks/convertSvgToJsx.js | 2 +- .../src/components/form-row/FormRow.js | 9 - .../components/form-row/FormRowHelpers.tsx | 15 ++ .../dnb-eufemia/src/components/icon/Icon.js | 9 +- .../src/components/icon/IconHelpers.js | 7 + .../components/pagination/PaginationBar.tsx | 16 +- .../components/skeleton/SkeletonHelper.d.ts | 2 +- packages/dnb-eufemia/src/shared/Context.js | 121 --------- packages/dnb-eufemia/src/shared/Context.tsx | 229 ++++++++++++++++++ .../src/shared/{Provider.js => Provider.tsx} | 77 +++--- .../{Context.test.js => Context.test.tsx} | 57 +++-- .../{Provider.test.js => Provider.test.tsx} | 4 +- .../src/shared/helpers/withCamelCaseProps.tsx | 1 + 13 files changed, 358 insertions(+), 191 deletions(-) create mode 100644 packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx create mode 100644 packages/dnb-eufemia/src/components/icon/IconHelpers.js delete mode 100644 packages/dnb-eufemia/src/shared/Context.js create mode 100644 packages/dnb-eufemia/src/shared/Context.tsx rename packages/dnb-eufemia/src/shared/{Provider.js => Provider.tsx} (74%) rename packages/dnb-eufemia/src/shared/__tests__/{Context.test.js => Context.test.tsx} (65%) rename packages/dnb-eufemia/src/shared/__tests__/{Provider.test.js => Provider.test.tsx} (98%) diff --git a/packages/dnb-eufemia/scripts/prebuild/tasks/convertSvgToJsx.js b/packages/dnb-eufemia/scripts/prebuild/tasks/convertSvgToJsx.js index 16076d87964..a808161ed0e 100644 --- a/packages/dnb-eufemia/scripts/prebuild/tasks/convertSvgToJsx.js +++ b/packages/dnb-eufemia/scripts/prebuild/tasks/convertSvgToJsx.js @@ -12,7 +12,7 @@ import transform from 'gulp-transform' import { transform as svgr } from '@svgr/core' import prettier from 'prettier' import globby from 'globby' -import { iconCase } from '../../../src/components/icon' +import { iconCase } from '../../../src/components/icon/IconHelpers' import { asyncForEach } from '../../tools' import { log } from '../../lib' import { md5 } from '../../figma/helpers/docHelpers' diff --git a/packages/dnb-eufemia/src/components/form-row/FormRow.js b/packages/dnb-eufemia/src/components/form-row/FormRow.js index 312b3646b7b..e6a89fc23ba 100644 --- a/packages/dnb-eufemia/src/components/form-row/FormRow.js +++ b/packages/dnb-eufemia/src/components/form-row/FormRow.js @@ -338,12 +338,3 @@ Fieldset.defaultProps = { useFieldset: false, className: null, } - -export const prepareFormRowContext = (props) => { - if (typeof props.label_direction === 'undefined') { - props.label_direction = isTrue(props.vertical) - ? 'vertical' - : props.label_direction - } - return props -} diff --git a/packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx b/packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx new file mode 100644 index 00000000000..120b9f2d1f3 --- /dev/null +++ b/packages/dnb-eufemia/src/components/form-row/FormRowHelpers.tsx @@ -0,0 +1,15 @@ +import { isTrue } from '../../shared/component-helper' + +type FormRowProps = { + label_direction?: 'vertical' | 'horizontal' + vertical?: string | boolean +} + +export const prepareFormRowContext = (props: FormRowProps) => { + if (typeof props.label_direction === 'undefined') { + props.label_direction = isTrue(props.vertical) + ? 'vertical' + : props.label_direction + } + return props +} diff --git a/packages/dnb-eufemia/src/components/icon/Icon.js b/packages/dnb-eufemia/src/components/icon/Icon.js index 3ac43132e8b..67b368a43f8 100644 --- a/packages/dnb-eufemia/src/components/icon/Icon.js +++ b/packages/dnb-eufemia/src/components/icon/Icon.js @@ -20,6 +20,7 @@ import { } from '../space/SpacingHelper' import { createSkeletonClass } from '../skeleton/SkeletonHelper' import Context from '../../shared/Context' +import { iconCase } from './IconHelpers' export const DefaultIconSize = 16 export const DefaultIconSizes = { @@ -446,11 +447,3 @@ export const prerenderIcon = ({ return null } } - -// to replace icon names -export const iconCase = (name) => - name - .replace(/((?!^)[A-Z])/g, '_$1') - .toLowerCase() - .replace(/^[0-9]/g, '$1') - .replace(/[^a-z0-9_]/gi, '_') diff --git a/packages/dnb-eufemia/src/components/icon/IconHelpers.js b/packages/dnb-eufemia/src/components/icon/IconHelpers.js new file mode 100644 index 00000000000..013c0e78a99 --- /dev/null +++ b/packages/dnb-eufemia/src/components/icon/IconHelpers.js @@ -0,0 +1,7 @@ +// to replace icon names +export const iconCase = (name) => + name + .replace(/((?!^)[A-Z])/g, '_$1') + .toLowerCase() + .replace(/^[0-9]/g, '$1') + .replace(/[^a-z0-9_]/gi, '_') diff --git a/packages/dnb-eufemia/src/components/pagination/PaginationBar.tsx b/packages/dnb-eufemia/src/components/pagination/PaginationBar.tsx index dcc20222a6d..a55c9701b9b 100644 --- a/packages/dnb-eufemia/src/components/pagination/PaginationBar.tsx +++ b/packages/dnb-eufemia/src/components/pagination/PaginationBar.tsx @@ -24,27 +24,28 @@ interface PaginationBarProps { /** * The title used in every button shown in the bar. Defaults to Side %s. */ - button_title: string + button_title?: string /** * The title used in the previous page button. Defaults to Forrige side. */ - prev_title: string + prev_title?: string /** * The title used in the next page button. Defaults to Neste side. */ - next_title: string + next_title?: string /** * The title used in the dots. Relevant for screen-readers. Defaults to %s flere sider. */ - more_pages: string + more_pages?: string /** * Reference to the parent component. Used to contain height between updates. */ - contentRef: React.RefObject + contentRef?: React.RefObject /** * the given content can be either a function or a React node, depending on your needs. A function contains several helper functions. More details down below and have a look at the examples in the demos section. */ - children: React.ReactNode + children?: React.ReactNode + locale?: string } const defaultProps = { @@ -134,7 +135,8 @@ const PaginationBar = (innerProps: PaginationBarProps) => { extendPropsWithContext( { ...defaultProps, ...innerProps }, defaultProps, - getTranslation(innerProps).Pagination + getTranslation(innerProps as Pick) + .Pagination ) const prevIsDisabled = currentPage > -1 ? currentPage === 1 : true diff --git a/packages/dnb-eufemia/src/components/skeleton/SkeletonHelper.d.ts b/packages/dnb-eufemia/src/components/skeleton/SkeletonHelper.d.ts index d36dad3bc31..8ab0f071621 100644 --- a/packages/dnb-eufemia/src/components/skeleton/SkeletonHelper.d.ts +++ b/packages/dnb-eufemia/src/components/skeleton/SkeletonHelper.d.ts @@ -11,7 +11,7 @@ export class AutoSize extends React.Component { } export interface SkeletonContextProps { translation?: { - Skeleton: { + Skeleton?: { /** * Is used for screen reader text translation, defined in the translation files. You can set a custom text if needed. */ diff --git a/packages/dnb-eufemia/src/shared/Context.js b/packages/dnb-eufemia/src/shared/Context.js deleted file mode 100644 index 131bce67dcc..00000000000 --- a/packages/dnb-eufemia/src/shared/Context.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Web Context Context - * - */ - -import React from 'react' -import { LOCALE, CURRENCY, CURRENCY_DISPLAY } from './defaults' -import defaultLocales from './locales' -import { extend } from './component-helper' - -export const prepareContext = (props = {}) => { - const locales = props.locales - ? extend(defaultLocales, props.locales) - : defaultLocales - - if (props.__newContext) { - Object.assign(props, props.__newContext) - delete props.__newContext - } - - const key = handleLocaleFallbacks(props.locale || LOCALE, locales) - const translation = locales[key] || defaultLocales[LOCALE] || {} // here we could use Object.freeze - - /** - * The code above adds support for strings, defined like: - * { - * "Modal.close_title": "Steng", - * } - */ - if (locales[key]) { - locales[key] = destruct(locales[key], translation) - } - - const context = { - // We may use that in future - updateTranslation: (locale, translation) => { - context.translation = context.locales[locale] = translation - }, - getTranslation: (props) => { - if (props) { - const lang = props.lang || props.locale - if (lang && context.locales[lang] && lang !== key) { - return context.locales[lang] - } - } - return context.translation - }, - locale: null, - breakpoints: null, - locales, - skeleton: null, - // All eufemia components because of Typescript: - Button: {}, - Avatar: {}, - AvatarGroup: {}, - Breadcrumb: {}, - BreadcrumbItem: {}, - InfoCard: {}, - Tag: {}, - TagGroup: {}, - Timeline: {}, - TimelineItem: {}, - VisuallyHidden: {}, - Drawer: {}, - Dialog: {}, - NumberFormat: {}, - Table: {}, - - ...props, - translation, // make sure we set this after props, since we update this one! - } - - return context -} - -function handleLocaleFallbacks(locale, locales) { - if (!locales[locale]) { - if (locale === 'en' || locale.split('-')[0] === 'en') { - return 'en-GB' - } - } - return locale -} - -// If no provider is given, we use the default context from here -const Context = React.createContext( - prepareContext({ - locale: LOCALE, - currency: CURRENCY, - currency_display: CURRENCY_DISPLAY, - }) -) - -export default Context - -function destruct(source, validKeys) { - for (let k in source) { - if (String(k).includes('.')) { - const list = k.split('.') - - if (validKeys[list[0]]) { - const val = source[k] - const last = list.length - 1 - - list.forEach((k, i) => { - source[k] = i === last ? val : source[k] - source = source[k] || {} - }) - - // If the root object is frozen, then use this - // let lastObj = { ...source } - // list.forEach((k, i) => { - // lastObj[k] = i === last ? val : lastObj[k] // we may have to create a new object here instead? - // lastObj = lastObj[k] - // }) - } - } - } - - return source -} diff --git a/packages/dnb-eufemia/src/shared/Context.tsx b/packages/dnb-eufemia/src/shared/Context.tsx new file mode 100644 index 00000000000..3510d2ca456 --- /dev/null +++ b/packages/dnb-eufemia/src/shared/Context.tsx @@ -0,0 +1,229 @@ +/** + * Web Context Context + * + */ + +import React from 'react' +import { LOCALE, CURRENCY, CURRENCY_DISPLAY } from './defaults' +import defaultLocales from './locales' +import { extend } from './component-helper' + +// All TypeScript based Eufemia components +import type { ButtonProps } from '../components/button/Button' +import type { AvatarProps } from '../components/avatar/Avatar' +import type { AvatarGroupProps } from '../components/avatar/AvatarGroup' +import type { BreadcrumbProps } from '../components/breadcrumb/Breadcrumb' +import type { BreadcrumbItemProps } from '../components/breadcrumb/BreadcrumbItem' +import type { InfoCardProps } from '../components/info-card/InfoCard' +import type { TagProps } from '../components/tag/Tag' +import type { TagGroupProps } from '../components/tag/TagGroup' +import type { TimelineProps } from '../components/timeline/Timeline' +import type { TimelineItemProps } from '../components/timeline/TimelineItem' +import type { VisuallyHiddenProps } from '../components/visually-hidden/VisuallyHidden' +import type { DrawerProps } from '../components/drawer/types' +import type { DialogProps } from '../components/dialog/types' + +export type ContextProps = { + // -- All TypeScript based Eufemia components -- + + Button?: Partial + Avatar?: Partial + AvatarGroup?: Partial + Breadcrumb?: Partial + BreadcrumbItem?: Partial + InfoCard?: Partial + Tag?: Partial + TagGroup?: Partial + Timeline?: Partial + TimelineItem?: Partial + VisuallyHidden?: Partial + Drawer?: Partial + Dialog?: Partial + + // -- TODO: Not converted yet -- + + NumberFormat?: Record + Table?: Record + HelpButton?: Record + Pagination?: Record + FormRow?: Record + + // -- Global properties -- + + /** + * Will enable all skeletons inside this provider/context scope + */ + skeleton?: boolean | string // SkeletonShow + + /** + * Define what breakpoints should be used by the MediaQuery component and hook + */ + breakpoints?: Record + + /** + * Defines the locale (internal translation) used by some components + */ + locale?: Locale + + /** + * Provide your own translations. Use the same format as defined in the translation files + */ + translation?: Translation + + /** + * Defines the currency used by the NumberFormat component + */ + currency?: string + + /** + * Defines the currencyDisplay used by the NumberFormat component + */ + currency_display?: string + + /** + * Update any given provider/context properties + */ + update?: (props: ContextProps) => void + + /** + * Update any given provider/context properties, but only for the particular scope + */ + updateCurrent?: (props: ContextProps) => void + + /** + * Update the used locale from within the context + */ + setLocale?: (locale: Locale) => void + + /** + * Update the used locale from within the context, but only for the particular scope + */ + setCurrentLocale?: (locale: Locale) => void + + // -- For internal use -- + locales?: Locales + __newContext?: Record + updateTranslation?: (locale: Locale, translation: Translation) => void + getTranslation?: (props?: GetTranslationProps) => Translation +} + +export type GetTranslationProps = { + lang?: Locale + locale?: Locale +} + +export type Locale = string | 'nb-NO' | 'en-GB' | 'en-US' +export type ComponentTranslationsName = string +export type ComponentTranslations = Record +export type Locales = Record +export type Translation = Record< + ComponentTranslationsName, + ComponentTranslations +> + +export const prepareContext = (props: ContextProps = {}) => { + const locales: Locales = props.locales + ? extend(defaultLocales, props.locales) + : defaultLocales + + if (props.__newContext) { + Object.assign(props, props.__newContext) + delete props.__newContext + } + + const key = handleLocaleFallbacks(props.locale || LOCALE, locales) + const translation = locales[key] || defaultLocales[LOCALE] || {} // here we could use Object.freeze + + /** + * The code above adds support for strings, defined like: + * { + * "Modal.close_title": "Lukk", + * } + */ + if (locales[key]) { + locales[key] = destruct(locales[key], translation) + } + + const context = { + // We may use that in future + updateTranslation: (locale: string, translation: Translation) => { + context.translation = context.locales[locale] = translation + }, + getTranslation: (props: GetTranslationProps) => { + if (props) { + const lang = props.lang || props.locale + if (lang && context.locales[lang] && lang !== key) { + return context.locales[lang] + } + } + return context.translation + }, + + locales, + locale: null, + breakpoints: null, + skeleton: null, + + ...props, + + /** + * Make sure we set this after props, since we update this one! + */ + translation, + } as ContextProps + + return context +} + +function handleLocaleFallbacks(locale: Locale, locales: Locales) { + if (!locales[locale]) { + if (locale === 'en' || locale.split('-')[0] === 'en') { + return 'en-GB' + } + } + return locale +} + +// If no provider is given, we use the default context from here +const Context = React.createContext( + prepareContext({ + locale: LOCALE, + currency: CURRENCY, + currency_display: CURRENCY_DISPLAY, + }) +) + +export default Context + +function destruct( + source: Translation, + validKeys: Record +): Translation { + for (const k in source) { + if (String(k).includes('.')) { + const list = k.split('.') + + if (validKeys[list[0]]) { + const val = source[k] + const last = list.length - 1 + + list.forEach((k, i) => { + source[k] = i === last ? val : source[k] + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + source = source[k] || {} + }) + + // If the root object is frozen, then use this + // let lastObj = { ...source } + // list.forEach((k, i) => { + // lastObj[k] = i === last ? val : lastObj[k] // we may have to create a new object here instead? + // lastObj = lastObj[k] + // }) + } + } + } + + return source +} diff --git a/packages/dnb-eufemia/src/shared/Provider.js b/packages/dnb-eufemia/src/shared/Provider.tsx similarity index 74% rename from packages/dnb-eufemia/src/shared/Provider.js rename to packages/dnb-eufemia/src/shared/Provider.tsx index 72edc01aea6..914fa7d4c7e 100644 --- a/packages/dnb-eufemia/src/shared/Provider.js +++ b/packages/dnb-eufemia/src/shared/Provider.tsx @@ -4,27 +4,48 @@ */ import React from 'react' -import PropTypes from 'prop-types' import Context, { prepareContext } from './Context' +import type { ContextProps } from './Context' import { makeUniqueId } from './component-helper' - -// fill with data -import { prepareFormRowContext } from '../components/form-row/FormRow' - -export default class Provider extends React.PureComponent { +import { prepareFormRowContext } from '../components/form-row/FormRowHelpers' + +export type ProviderProps = { + /** + * Send in an object that gets spread as properties to the Provider + */ + value?: ContextProps + + /** + * Define the locale used for every Eufemia components inside this Provider. Defaults to nb-NO + */ + locale?: string + + /** + * Enable skeleton of every Eufemia component inside this Provider + */ + skeleton?: boolean | string // SkeletonShow + + /** + * The content + */ + children: React.ReactNode +} & ContextProps + +export type ProviderState = { + /** For internal use */ + isRoot?: boolean + _listenForPropChanges?: boolean + _startupProps?: ContextProps +} & ContextProps + +export default class Provider extends React.PureComponent< + ProviderProps, + ProviderState +> { static contextType = Context - static propTypes = { - /** Send in an object that gets spread as properties to the Provider */ - value: PropTypes.object, // eslint-disable-line - /** Define the locale used for every Eufemia components inside this Provider. Defaults to nb-NO */ - locale: PropTypes.string, // eslint-disable-line - /** Enable skeleton of every Eufemia component inside this Provider */ - skeleton: PropTypes.bool, // eslint-disable-line - children: PropTypes.node.isRequired, - } // NB! Do not provide any default props, because they would overwrite inherited values in nested provider - static defaultProps = {} + static defaultProps: Record = {} static getDerivedStateFromProps(props, state) { if (state._listenForPropChanges) { @@ -33,10 +54,7 @@ export default class Provider extends React.PureComponent { ...updatedProps } = props - // 1. Set default context to be overwritten by the provider props - // let newContext = state - - // No, it's not sure that props have been updated, so we check that here + // 1. It's not sure that props have been updated, so we check that here if (state._startupProps !== updatedProps) { let hasChanges = false for (const i in updatedProps) { @@ -78,7 +96,7 @@ export default class Provider extends React.PureComponent { const merge = { ...value, ...rest } // Merge our new values with an existing context - let mergedContext = { ...context, ...merge } + const mergedContext = { ...context, ...merge } // Because we don't want to deep merge, we merge FormRow additionally if (context.FormRow && merge.FormRow) { @@ -114,11 +132,12 @@ export default class Provider extends React.PureComponent { const pC = isRoot ? prepareContext(newContext) : newContext // change only current context - pC.updateCurrent = (props) => this.setNewContext(props) - pC.setCurrentLocale = (locale) => this.setNewContext({ locale }) + pC.updateCurrent = (props: ContextProps) => this.setNewContext(props) + pC.setCurrentLocale = (locale: string) => + this.setNewContext({ locale }) // change both the root and the current context - pC.update = (props) => { + pC.update = (props: ContextProps) => { // Update the root context if (typeof context.update === 'function') { context.update(props) @@ -126,7 +145,7 @@ export default class Provider extends React.PureComponent { this.setNewContext(props) } - pC.setLocale = (locale) => { + pC.setLocale = (locale: string) => { // Update the root context if (typeof context.update === 'function') { context.update({ locale }) @@ -135,10 +154,10 @@ export default class Provider extends React.PureComponent { this.setNewContext({ locale }) } + pC.isRoot = isRoot + pC._listenForPropChanges = true + pC._startupProps = startupProps this.state = pC - this.state.isRoot = isRoot - this.state._listenForPropChanges = true - this.state._startupProps = startupProps } setNewContext(__newContext) { @@ -169,6 +188,8 @@ export default class Provider extends React.PureComponent { this.context.updateTranslation(value.locale, value.translation) return ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore {this.props.children} diff --git a/packages/dnb-eufemia/src/shared/__tests__/Context.test.js b/packages/dnb-eufemia/src/shared/__tests__/Context.test.tsx similarity index 65% rename from packages/dnb-eufemia/src/shared/__tests__/Context.test.js rename to packages/dnb-eufemia/src/shared/__tests__/Context.test.tsx index 6f881268c5f..3a631f2cddf 100644 --- a/packages/dnb-eufemia/src/shared/__tests__/Context.test.js +++ b/packages/dnb-eufemia/src/shared/__tests__/Context.test.tsx @@ -12,19 +12,26 @@ import Context from '../Context' import nbNO from '../locales/nb-NO' import enGB from '../locales/en-GB' +import Provider from '../Provider' describe('Context', () => { const title_nb = nbNO['nb-NO'].HelpButton.title const title_gb = enGB['en-GB'].HelpButton.title const ChangeLocale = () => { - const { setLocaleByProps, locale } = React.useContext(Context) + const { setLocale, update, setCurrentLocale, updateCurrent, locale } = + React.useContext(Context) + + expect(typeof update).toBe('function') + expect(typeof setLocale).toBe('function') + expect(typeof setCurrentLocale).toBe('function') + expect(typeof updateCurrent).toBe('function') return ( { - setLocaleByProps({ locale: value }) + setLocale(value) }} > @@ -40,20 +47,22 @@ describe('Context', () => { ) } - const MagicContext = ({ children, ...props }) => { + const MagicContext = ({ children = null, ...props }) => { return ( - - {(context) => { - const title = context.getTranslation(props).HelpButton.title - return ( - <> -

{title}

- - {children} - - ) - }} -
+ + + {(context) => { + const title = context.getTranslation(props).HelpButton.title + return ( + <> +

{title}

+ + {children} + + ) + }} +
+
) } @@ -99,6 +108,24 @@ describe('Context', () => { expect(Comp.find('p').text()).toBe(title_gb) }) + it('translation (getTranslation) should react on new locale', () => { + const Comp = mount() + + expect(Comp.find('p').text()).toBe(title_nb) + + Comp.find('.en-GB').find('button').simulate('click') + + expect(Comp.find('p').text()).toBe(title_gb) + + Comp.find('.en-US').find('button').simulate('click') + + expect(Comp.find('p').text()).toBe(title_gb) + + Comp.find('.nb-NO').find('button').simulate('click') + + expect(Comp.find('p').text()).toBe(title_nb) + }) + it('translation should react on locale change', () => { const Comp = mount(content) diff --git a/packages/dnb-eufemia/src/shared/__tests__/Provider.test.js b/packages/dnb-eufemia/src/shared/__tests__/Provider.test.tsx similarity index 98% rename from packages/dnb-eufemia/src/shared/__tests__/Provider.test.js rename to packages/dnb-eufemia/src/shared/__tests__/Provider.test.tsx index 20af681602c..4be6693f897 100644 --- a/packages/dnb-eufemia/src/shared/__tests__/Provider.test.js +++ b/packages/dnb-eufemia/src/shared/__tests__/Provider.test.tsx @@ -37,6 +37,8 @@ describe('Provider', () => { const ChangeLocale = () => { const { setLocale, locale } = React.useContext(Context) + expect(typeof setLocale).toBe('function') + return ( { ) } - const MagicProvider = ({ children, ...props }) => { + const MagicProvider = ({ children = null, ...props }) => { return ( diff --git a/packages/dnb-eufemia/src/shared/helpers/withCamelCaseProps.tsx b/packages/dnb-eufemia/src/shared/helpers/withCamelCaseProps.tsx index d500e59460a..d60efe4e28c 100644 --- a/packages/dnb-eufemia/src/shared/helpers/withCamelCaseProps.tsx +++ b/packages/dnb-eufemia/src/shared/helpers/withCamelCaseProps.tsx @@ -57,6 +57,7 @@ export function classWithCamelCaseProps< if (this.props !== this._prevProps) { this._prevProps = this.props this._elem = ( + // @ts-ignore