From 7747e0639482a8541fe6013844b00375653cd1b9 Mon Sep 17 00:00:00 2001 From: Thayanan Tharmapalan Date: Wed, 12 Jun 2024 07:55:41 +0200 Subject: [PATCH 01/16] docs(Tabs): fix the title of Tabs breakout example (#3696) --- .../src/docs/uilib/components/tabs/demos.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/demos.mdx index d939b582f3e..bbbe1f2c5a9 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/demos.mdx @@ -48,7 +48,7 @@ Also, this is an example of how to define a different content background color, -### Tabs without breakout bottom border +### Tabs without breakout From 6386ef78431b28f14cf326654c51a3e334fc27de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Wed, 12 Jun 2024 09:45:02 +0200 Subject: [PATCH 02/16] chore(Visibility): move logic to hook (#3698) So we can use it in other contexts if needed. --- .../forms/Form/Visibility/Visibility.tsx | 117 ++++-------------- .../forms/Form/Visibility/useVisibility.tsx | 113 +++++++++++++++++ 2 files changed, 135 insertions(+), 95 deletions(-) create mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx index 8b2aae8735f..4d5abbe2102 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx @@ -1,15 +1,16 @@ -import React, { AriaAttributes, useCallback, useContext } from 'react' -import pointer from 'json-pointer' +import React, { AriaAttributes } from 'react' + import { warn } from '../../../../shared/helpers' import useMountEffect from '../../../../shared/helpers/useMountEffect' import HeightAnimation, { HeightAnimationProps, } from '../../../../components/HeightAnimation' -import DataContext, { FilterData } from '../../DataContext/Context' -import SectionContext from '../Section/SectionContext' import FieldProps from '../FieldProps' -import type { Path, UseFieldProps } from '../../types' +import useVisibility from './useVisibility' + +import type { UseFieldProps } from '../../types' import type { DataAttributes } from '../../hooks/useFieldProps' +import { FilterData } from '../../DataContext' type VisibleWhen = | { @@ -78,102 +79,28 @@ function Visibility({ children, ...rest }: Props) { - const dataContext = useContext(DataContext) - const sectionContext = useContext(SectionContext) - - const sectionPath = sectionContext?.path - const composePath = useCallback( - (path: Path) => { - return `${ - sectionPath && sectionPath !== '/' ? sectionPath : '' - }${path}` - }, - [sectionPath] - ) - useMountEffect(() => { if (fieldPropsWhenHidden && !keepInDOM) { warn('Using "fieldPropsWhenHidden" requires "keepInDOM" to be true.') } }) - const check = () => { - if (visible === false) { - return - } - - const data = - (filterData && - dataContext.filterDataHandler?.(dataContext.data, filterData)) || - dataContext.data - - if (visibleWhen || visibleWhenNot) { - if (visibleWhenNot) { - visibleWhen = visibleWhenNot - } - const hasPath = pointer.has(data, composePath(visibleWhen.path)) - if (hasPath) { - const value = pointer.get(data, composePath(visibleWhen.path)) - - const withValue = visibleWhen?.['withValue'] - const result = - (withValue && withValue?.(value) === false) || - (Object.prototype.hasOwnProperty.call(visibleWhen, 'hasValue') && - visibleWhen?.['hasValue'] !== value) - - if (visibleWhenNot) { - if (!result) { - return - } - } else if (result) { - return - } - } else { - return - } - } - - if (pathDefined && !pointer.has(data, composePath(pathDefined))) { - return - } - if (pathUndefined && pointer.has(data, composePath(pathUndefined))) { - return - } - - const getValue = (path: Path) => { - if (pointer.has(data, path)) { - return pointer.get(data, path) - } - } - - if (pathTrue && getValue(composePath(pathTrue)) !== true) { - return - } - if (pathFalse && getValue(composePath(pathFalse)) !== false) { - return - } - if ( - pathTruthy && - Boolean(getValue(composePath(pathTruthy))) === false - ) { - return - } - if (pathFalsy && Boolean(getValue(composePath(pathFalsy))) === true) { - return - } - if (inferData && !inferData(data)) { - return - } - - // Deprecated can be removed in v11 - if (pathValue && getValue(composePath(pathValue)) !== whenValue) { - return - } - - return true - } - - const open = Boolean(check()) + const { check } = useVisibility({ + visible, + pathDefined, + pathUndefined, + pathTruthy, + pathFalsy, + pathTrue, + pathFalse, + pathValue, + whenValue, + visibleWhen, + visibleWhenNot, + inferData, + filterData, + }) + const open = check() if (animate) { const props = !open ? fieldPropsWhenHidden : null diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx new file mode 100644 index 00000000000..6ad1823aff2 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx @@ -0,0 +1,113 @@ +import { useCallback, useContext } from 'react' +import pointer from 'json-pointer' +import DataContext from '../../DataContext/Context' +import SectionContext from '../Section/SectionContext' +import { Path } from '../../types' +import { Props } from './Visibility' + +export default function useVisibility({ + visible, + visibleWhen, + visibleWhenNot, + pathDefined, + pathUndefined, + pathTruthy, + pathFalsy, + pathTrue, + pathFalse, + pathValue, + whenValue, + inferData, + filterData, +}: Partial) { + const dataContext = useContext(DataContext) + const sectionContext = useContext(SectionContext) + + const sectionPath = sectionContext?.path + const composePath = useCallback( + (path: Path) => { + return `${ + sectionPath && sectionPath !== '/' ? sectionPath : '' + }${path}` + }, + [sectionPath] + ) + + const check = () => { + if (visible === false) { + return false + } + + const data = + (filterData && + dataContext.filterDataHandler?.(dataContext.data, filterData)) || + dataContext.data + + if (visibleWhen || visibleWhenNot) { + if (visibleWhenNot) { + visibleWhen = visibleWhenNot + } + const hasPath = pointer.has(data, composePath(visibleWhen.path)) + if (hasPath) { + const value = pointer.get(data, composePath(visibleWhen.path)) + + const withValue = visibleWhen?.['withValue'] + const result = + (withValue && withValue?.(value) === false) || + (Object.prototype.hasOwnProperty.call(visibleWhen, 'hasValue') && + visibleWhen?.['hasValue'] !== value) + + if (visibleWhenNot) { + if (!result) { + return false + } + } else if (result) { + return false + } + } else { + return false + } + } + + if (pathDefined && !pointer.has(data, composePath(pathDefined))) { + return false + } + if (pathUndefined && pointer.has(data, composePath(pathUndefined))) { + return false + } + + const getValue = (path: Path) => { + if (pointer.has(data, path)) { + return pointer.get(data, path) + } + } + + if (pathTrue && getValue(composePath(pathTrue)) !== true) { + return false + } + if (pathFalse && getValue(composePath(pathFalse)) !== false) { + return false + } + if ( + pathTruthy && + Boolean(getValue(composePath(pathTruthy))) === false + ) { + return false + } + if (pathFalsy && Boolean(getValue(composePath(pathFalsy))) === true) { + return false + } + if (inferData && !inferData(data)) { + return false + } + + // Deprecated can be removed in v11 + if (pathValue && getValue(composePath(pathValue)) !== whenValue) { + return false + } + + return true + } + + return { check } +} From d8d740cd129e4027bf94936f8898f7018c8f86f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Wed, 12 Jun 2024 14:19:05 +0200 Subject: [PATCH 03/16] fix(StepIndicator): avoid re-assigning functions that can cause titles to be not in sync (#3697) Fixes #3685 Here is the same example as mentioned in the issue: https://eufemia-git-fix-wizard-step-title-eufemia.vercel.app/uilib/extensions/forms/Wizard/#intro-example --- .../step-indicator/StepIndicatorContext.tsx | 192 ++++++++++-------- .../step-indicator/StepIndicatorItem.tsx | 115 +++++------ .../step-indicator/StepIndicatorModal.tsx | 18 +- .../step-indicator/StepIndicatorSidebar.tsx | 19 +- .../StepIndicatorTriggerButton.tsx | 5 +- .../__tests__/WizardContainer.test.tsx | 5 +- 6 files changed, 194 insertions(+), 160 deletions(-) diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx index 89cc7a7647b..f2fa1b5f380 100644 --- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx +++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorContext.tsx @@ -3,7 +3,15 @@ * */ -import React, { useContext, useEffect, useRef, useState } from 'react' +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useReducer, + useRef, + useState, +} from 'react' import Context, { ContextProps } from '../../shared/Context' import { stepIndicatorDefaultProps } from './StepIndicatorProps' import { extendPropsWithContext } from '../../shared/component-helper' @@ -95,90 +103,35 @@ export function StepIndicatorProvider({ isSidebar = false, ...restOfProps }: StepIndicatorProviderProps) { - const props = { isSidebar, ...restOfProps } + const props = useMemo(() => { + return { isSidebar, ...restOfProps } + }, [isSidebar, restOfProps]) - const data = getData(props) - const countSteps = data.length + const data = useMemo(() => { + if (typeof props.data === 'string') { + return props.data[0] === '[' ? JSON.parse(props.data) : [] + } + + return props.data || [] + }, [props]) const [hasSidebar, setHasSidebar] = useState(true) const [hideSidebar, setHideSidebar] = useState(false) - const [activeStep, setActiveStep] = useState( - getActiveStepFromProps() - ) const [openState, setOpenState] = useState(false) - const listOfReachedSteps = useRef([activeStep].filter(Boolean)).current - - const mediaQueryListener = useRef(null) - - const context = useContext(Context) - const contextValue = makeContextValue() as StepIndicatorContextValues - - // Mount and dismount - useEffect(() => { - const container = document?.getElementById( - 'sidebar__' + props.sidebar_id - ) - - setHasSidebar(Boolean(container)) - - mediaQueryListener.current = onMediaQueryChange( - { - min: '0', - max: 'medium', - }, - (hideSidebar) => { - setHideSidebar(hideSidebar) - }, - { runOnInit: true } - ) - - return () => { - if (mediaQueryListener.current) { - mediaQueryListener.current() - } - } - }, []) - - // Keeps the activeStep state updated with changes to the current_step and data props - useEffect(() => { - const currentStepFromProps = getActiveStepFromProps() - - if (currentStepFromProps !== activeStep) { - setActiveStep(currentStepFromProps) - } - }, [props.current_step, data]) - - // Keeps the listOfReachedSteps state up to date with the activeStep state - useEffect(() => { - if (!listOfReachedSteps.includes(activeStep)) { - listOfReachedSteps.push(activeStep) - } - }, [activeStep]) - - function onChangeState() { + const onChangeState = useCallback(() => { setOpenState(false) - } + }, []) - function openHandler() { + const openHandler = useCallback(() => { setOpenState(true) - } + }, []) - function closeHandler() { + const closeHandler = useCallback(() => { setOpenState(false) - } - - function getData( - props: StepIndicatorProviderProps - ): string[] | StepIndicatorDataItem[] { - if (typeof props.data === 'string') { - return props.data[0] === '[' ? JSON.parse(props.data) : [] - } - - return props.data || [] - } + }, []) - function getActiveStepFromProps() { + const getActiveStepFromProps = useCallback(() => { if (typeof data[0] === 'string') { return props.current_step } @@ -192,9 +145,31 @@ export function StepIndicatorProvider({ return itemWithCurrentStep ? dataWithItems.indexOf(itemWithCurrentStep) : props.current_step - } + }, [data, props.current_step]) + + const countSteps = data.length + const activeStepRef = useRef(getActiveStepFromProps()) + const [, forceUpdate] = useReducer(() => ({}), {}) + const setActiveStep = useCallback((step: number) => { + activeStepRef.current = step + forceUpdate() + }, []) + const listOfReachedSteps = useRef( + [activeStepRef.current].filter(Boolean) + ).current + const mediaQueryListener = useRef(null) + const context = useContext(Context) + + const updateStepTitle = useCallback( + (title: string) => { + return title + ?.replace('%step', String((activeStepRef.current || 0) + 1)) + .replace('%count', String(data?.length || 1)) + }, + [data?.length] + ) - function makeContextValue() { + const makeContextValue = useCallback(() => { const globalContext = extendPropsWithContext( props, stepIndicatorDefaultProps, @@ -215,7 +190,7 @@ export function StepIndicatorProvider({ { hasSidebar, hideSidebar, - activeStep, + activeStep: activeStepRef.current, openState, listOfReachedSteps, data, @@ -237,13 +212,66 @@ export function StepIndicatorProvider({ value.sidebarIsVisible = value.hasSidebar && !value.hideSidebar return value - } + }, [ + closeHandler, + context, + countSteps, + data, + hasSidebar, + hideSidebar, + listOfReachedSteps, + onChangeState, + openHandler, + openState, + props, + setActiveStep, + updateStepTitle, + ]) - function updateStepTitle(title: string) { - return title - ?.replace('%step', String((activeStep || 0) + 1)) - .replace('%count', String(data?.length || 1)) - } + const contextValue = makeContextValue() as StepIndicatorContextValues + + // Mount and dismount + useEffect(() => { + const container = document?.getElementById( + 'sidebar__' + props.sidebar_id + ) + + setHasSidebar(Boolean(container)) + + mediaQueryListener.current = onMediaQueryChange( + { + min: '0', + max: 'medium', + }, + (hideSidebar) => { + setHideSidebar(hideSidebar) + }, + { runOnInit: true } + ) + + return () => { + if (mediaQueryListener.current) { + mediaQueryListener.current() + } + } + }, [props.sidebar_id]) + + // Keeps the activeStep state updated with changes to the current_step and data props + useEffect(() => { + const currentStepFromProps = getActiveStepFromProps() + + if (currentStepFromProps !== activeStepRef.current) { + setActiveStep(currentStepFromProps) + } + }, [props.current_step, data, getActiveStepFromProps, setActiveStep]) + + // Keeps the listOfReachedSteps state up to date with the activeStep state + const activeStep = activeStepRef.current + useEffect(() => { + if (!listOfReachedSteps.includes(activeStep)) { + listOfReachedSteps.push(activeStep) + } + }, [activeStep, listOfReachedSteps]) if (typeof window !== 'undefined' && window['IS_TEST']) { contextValue['no_animation'] = true diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx index e4feb039c18..abd7add0384 100644 --- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx +++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorItem.tsx @@ -3,13 +3,7 @@ * */ -import React, { - HTMLProps, - useContext, - useEffect, - useRef, - useState, -} from 'react' +import React, { HTMLProps, useCallback, useContext, useMemo } from 'react' import classnames from 'classnames' import { @@ -84,67 +78,62 @@ function StepIndicatorItem({ disabled: disabled_default = false, ...restOfProps }: StepIndicatorItemProps) { - const props: StepIndicatorItemProps = { - status_state: status_state_default, - inactive: inactive_default, - disabled: disabled_default, - ...restOfProps, - } + const props: StepIndicatorItemProps = useMemo(() => { + return { + status_state: status_state_default, + inactive: inactive_default, + disabled: disabled_default, + ...restOfProps, + } + }, [ + disabled_default, + inactive_default, + restOfProps, + status_state_default, + ]) const context = useContext(StepIndicatorContext) - const [previousStep, setPreviousStep] = useState( - context.activeStep - ) - - const ref = useRef(null) - - const thisReference = { - context, - props, - onClickHandler, - } - - // Effect used to keep track of previous activeStep from context - useEffect(() => { - if (previousStep !== context.activeStep) { - setPreviousStep(context.activeStep) - } - }, [context.activeStep, previousStep]) - - function onClickHandler({ event, item, currentItemNum }) { - const params = { - event, - item, - current_step: currentItemNum, - currentStep: currentItemNum, - } - - const onClickItem = dispatchCustomElementEvent( - thisReference, - 'on_click', - params - ) + const onClickHandler = useCallback( + ({ event, item, currentItemNum }) => { + const params = { + event, + item, + current_step: currentItemNum, + currentStep: currentItemNum, + } - const onClickGlobal = dispatchCustomElementEvent( - context, - 'on_click', - params - ) + const onClickItem = dispatchCustomElementEvent( + { + context, + props, + onClickHandler, + }, + 'on_click', + params + ) + + const onClickGlobal = dispatchCustomElementEvent( + context, + 'on_click', + params + ) + + if (onClickItem === false || onClickGlobal === false) { + return // stop here + } - if (onClickItem === false || onClickGlobal === false) { - return // stop here - } + if (context.activeStep !== currentItemNum) { + context.setActiveStep(currentItemNum) + if (typeof context.onChangeState === 'function') { + context.onChangeState() + } - if (context.activeStep !== currentItemNum) { - context.setActiveStep(currentItemNum) - if (typeof context.onChangeState === 'function') { - context.onChangeState() + dispatchCustomElementEvent(context, 'on_change', params) } - - dispatchCustomElementEvent(context, 'on_change', params) - } - } + }, + [context, props] + ) const { mode, @@ -266,9 +255,7 @@ function StepIndicatorItem({ itemParams.className )} > - - {element} - + {element} {ariaLabel} diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx index a603ab4eba7..5b5d4abc340 100644 --- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx +++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorModal.tsx @@ -3,7 +3,13 @@ * */ -import React, { useContext, useEffect, useRef, useState } from 'react' +import React, { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react' import ReactDOM from 'react-dom' import Drawer from '../drawer/Drawer' import StepIndicatorTriggerButton from './StepIndicatorTriggerButton' @@ -23,22 +29,22 @@ function StepIndicatorModal() { ) setContainer(container) - }, []) + }, [context.sidebar_id]) - function closeHandler() { + const closeHandler = useCallback(() => { if (context.hasSidebar) { triggerRef.current?.focus() } context.closeHandler() - } + }, [context]) - function renderPortal() { + const renderPortal = useCallback(() => { if (!container) { return null } return ReactDOM.createPortal(, container) - } + }, [container]) if (context.sidebarIsVisible) { return renderPortal() diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx index d018754abfe..0293b0c698e 100644 --- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx +++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorSidebar.tsx @@ -3,7 +3,14 @@ * */ -import React, { useContext, useEffect, useRef, useState } from 'react' +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import classnames from 'classnames' import { extendPropsWithContext } from '../../shared/component-helper' @@ -45,7 +52,9 @@ function StepIndicatorSidebar({ data = stepIndicatorDefaultProps.data, ...restOfProps }: StepIndicatorSidebarProps) { - const props = { current_step, data, ...restOfProps } + const props = useMemo(() => { + return { current_step, data, ...restOfProps } + }, [current_step, data, restOfProps]) const context = useContext(Context) @@ -57,9 +66,9 @@ function StepIndicatorSidebar({ if (!props.showInitialData) { setShowInitialData(false) } - }, []) + }, [props.showInitialData]) - function getContextAndProps() { + const getContextAndProps = useCallback(() => { const providerProps = extendPropsWithContext( props, stepIndicatorDefaultProps, @@ -76,7 +85,7 @@ function StepIndicatorSidebar({ } return providerProps - } + }, [context, props]) const providerProps = showInitialData ? getContextAndProps() : null diff --git a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx index 5c80ac5880f..47c62ce3cba 100644 --- a/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx +++ b/packages/dnb-eufemia/src/components/step-indicator/StepIndicatorTriggerButton.tsx @@ -39,7 +39,10 @@ function StepIndicatorTriggerButton( const item = context.data[context.activeStep || 0] const label = context.stepsLabel - const { data, ...contextWithoutData } = context + const { + data, // eslint-disable-line + ...contextWithoutData + } = context const triggerParams = { ...contextWithoutData, diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx index 783955bb4c7..1833599ab82 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx @@ -653,9 +653,9 @@ describe('Wizard.Container', () => { expect(wizardList()).not.toBeInTheDocument() rerender( - + - Step 1 + ensure re-render @@ -664,6 +664,7 @@ describe('Wizard.Container', () => { ) + expect(output()).toHaveTextContent('ensure re-render') expect(stepTrigger()).toBeInTheDocument() expect(wizardList()).not.toBeInTheDocument() }) From 84605b70bd922bede930185aed7c981cf5ae2a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Thu, 13 Jun 2024 09:55:13 +0200 Subject: [PATCH 04/16] docs(forms): add translations table (#3693) --- .../forms/Form/Section/properties.mdx | 5 + .../forms/Form/SubmitButton/properties.mdx | 5 + .../Value/BankAccountNumber/properties.mdx | 5 + .../forms/Value/Date/properties.mdx | 5 + .../forms/Value/Email/properties.mdx | 5 + .../forms/Value/Name/properties.mdx | 7 ++ .../NationalIdentityNumber/properties.mdx | 5 + .../Value/OrganizationNumber/properties.mdx | 5 + .../forms/Value/PhoneNumber/properties.mdx | 5 + .../Value/PostalCodeAndCity/properties.mdx | 5 + .../forms/Wizard/Step/properties.mdx | 5 + .../forms/base-fields/Boolean/properties.mdx | 5 + .../forms/base-fields/Number/properties.mdx | 5 + .../forms/base-fields/String/properties.mdx | 5 + .../forms/base-fields/Toggle/properties.mdx | 5 + .../FieldBlock/properties.mdx | 5 + .../BankAccountNumber/properties.mdx | 5 + .../feature-fields/Currency/properties.mdx | 5 + .../forms/feature-fields/Date/properties.mdx | 5 + .../forms/feature-fields/Email/properties.mdx | 5 + .../feature-fields/Expiry/properties.mdx | 5 + .../forms/feature-fields/Name/properties.mdx | 7 ++ .../NationalIdentityNumber/properties.mdx | 5 + .../OrganizationNumber/properties.mdx | 5 + .../feature-fields/Password/properties.mdx | 5 + .../feature-fields/PhoneNumber/properties.mdx | 5 + .../PostalCodeAndCity/properties.mdx | 5 + .../SelectCountry/properties.mdx | 5 + .../feature-fields/Slider/properties.mdx | 5 + .../extensions/forms/getting-started.mdx | 4 +- .../src/shared/parts/PropertiesTable.tsx | 4 +- .../src/shared/parts/TranslationsTable.tsx | 109 ++++++++++++++++++ .../forms/DataContext/Provider/Provider.tsx | 2 +- .../Provider/__tests__/Provider.test.tsx | 4 +- .../forms/constants/locales/en-GB.ts | 16 ++- .../forms/constants/locales/nb-NO.ts | 16 ++- 36 files changed, 279 insertions(+), 25 deletions(-) create mode 100644 packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Section/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Section/properties.mdx index 13ffe9097d2..bc8429cd616 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Section/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Section/properties.mdx @@ -3,9 +3,14 @@ showTabs: true hideInMenu: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { SectionProperties } from '@dnb/eufemia/src/extensions/forms/Form/Section/SectionDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitButton/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitButton/properties.mdx index 0b871f70db7..94ff1205425 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitButton/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitButton/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { SubmitButtonProperties } from '@dnb/eufemia/src/extensions/forms/Form/SubmitButton/SubmitButtonDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/BankAccountNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/BankAccountNumber/properties.mdx index d6b6b09a600..b8d83a88498 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/BankAccountNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/BankAccountNumber/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Date/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Date/properties.mdx index d6b6b09a600..5c80d8e39bb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Date/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Date/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Email/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Email/properties.mdx index d6b6b09a600..527c55cf54e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Email/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Email/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Name/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Name/properties.mdx index d6b6b09a600..e1ba9b73050 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Name/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/Name/properties.mdx @@ -2,9 +2,16 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/NationalIdentityNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/NationalIdentityNumber/properties.mdx index d6b6b09a600..2b10cfc38a7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/NationalIdentityNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/NationalIdentityNumber/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/OrganizationNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/OrganizationNumber/properties.mdx index d6b6b09a600..789828ddbd7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/OrganizationNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/OrganizationNumber/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PhoneNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PhoneNumber/properties.mdx index d6b6b09a600..49bc4b2defb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PhoneNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PhoneNumber/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PostalCodeAndCity/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PostalCodeAndCity/properties.mdx index a33253cb1db..a715a3992cf 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PostalCodeAndCity/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Value/PostalCodeAndCity/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { ValueProperties } from '@dnb/eufemia/src/extensions/forms/Value/ValueDocs' import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/Value/PostalCodeAndCity/PostalCodeAndCityDocs' @@ -13,3 +14,7 @@ import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/V ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/properties.mdx index 5bd019ccb21..e2fc4238e1f 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { StepProperties } from '@dnb/eufemia/src/extensions/forms/Wizard/Step/StepDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Boolean/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Boolean/properties.mdx index 345fed453a8..f3e0daf6361 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Boolean/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Boolean/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' import { BooleanProperties } from '@dnb/eufemia/src/extensions/forms/Field/Boolean/BooleanDocs' @@ -15,3 +16,7 @@ import { BooleanProperties } from '@dnb/eufemia/src/extensions/forms/Field/Boole ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/properties.mdx index 33f4a114ad0..765b8bbbc02 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { numberProperties } from '@dnb/eufemia/src/extensions/forms/Field/Number/NumberDocs' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -15,3 +16,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx index c3a49160d8d..0c7c7b3eb18 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { stringProperties } from '@dnb/eufemia/src/extensions/forms/Field/String/StringDocs' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -15,3 +16,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Toggle/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Toggle/properties.mdx index 80790634ab0..1e6a6b20be4 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Toggle/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Toggle/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' import { ToggleProperties } from '@dnb/eufemia/src/extensions/forms/Field/Toggle/ToggleDocs' @@ -15,3 +16,7 @@ import { ToggleProperties } from '@dnb/eufemia/src/extensions/forms/Field/Toggle ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx index 62e8427b03d..ef1bb5b14b0 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/properties.mdx @@ -2,9 +2,14 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' ## Properties + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/properties.mdx index 0d57296d278..717adc261e5 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -17,3 +18,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Currency/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Currency/properties.mdx index d443a68a49f..03d4c387d07 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Currency/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Currency/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -19,3 +20,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/properties.mdx index 18df55326bb..04d0ff8901f 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -16,3 +17,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Email/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Email/properties.mdx index 76068e88577..ac1e9e26917 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Email/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Email/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -17,3 +18,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Expiry/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Expiry/properties.mdx index fff1b60f083..39b8334dd0a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Expiry/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Expiry/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -10,3 +11,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Name/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Name/properties.mdx index fff1b60f083..862ec1ef804 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Name/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Name/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -10,3 +11,9 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/properties.mdx index 0d57296d278..40cde507d48 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -17,3 +18,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/properties.mdx index 0d57296d278..6804501bab4 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -17,3 +18,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Password/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Password/properties.mdx index df5a20643cf..af5fbf8c342 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Password/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Password/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -23,3 +24,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo props={fieldProperties} omit={['layout', 'label', 'labelDescription']} /> + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/properties.mdx index 54d429922f5..8136e438e40 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -29,3 +30,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo props={fieldProperties} omit={['layout', 'label', 'labelDescription']} /> + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx index 33c0e7730c3..ef686d56f20 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldBlockProperties } from '@dnb/eufemia/src/extensions/forms/FieldBlock/FieldBlockDocs' import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCityDocs' @@ -15,3 +16,7 @@ import { PostalCodeAndCityProperties } from '@dnb/eufemia/src/extensions/forms/F ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/properties.mdx index d30f402a61d..38e82db1e8a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' @@ -15,3 +16,7 @@ import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDo ### General props + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Slider/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Slider/properties.mdx index 6eb3535a6f0..8d0c94b9625 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Slider/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Slider/properties.mdx @@ -2,6 +2,7 @@ showTabs: true --- +import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { fieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/FieldDocs' import { SliderFieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/Slider/SliderDocs' @@ -18,3 +19,7 @@ import { SliderFieldProperties } from '@dnb/eufemia/src/extensions/forms/Field/S props={fieldProperties} valueType={['number', 'Array']} /> + +## Translations + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx index 89d46921970..2b4f581c2cc 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx @@ -25,7 +25,7 @@ import AsyncChangeExample from './Form/Handler/parts/async-change-example.mdx' - [Value components](#value-components) - [Async form behavior](#async-form-behavior) - [Validation and error handling](#validation-and-error-handling) -- [Localization](#localization-locale-and-translation) +- [Localization and translation](#localization-and-translation) - [Layout](#layout) - [Best practices](#best-practices) - [Create your own component](#create-your-own-component) @@ -390,7 +390,7 @@ const validator = debounceAsync(async function myValidator(value) { render() ``` -### Localization +### Localization and translation You can set the locale for your form by using the `locale` property on the [Form.Handler](/uilib/extensions/forms/Form/Handler/) component. This will ensure that the correct language is used for all the fields in your form. diff --git a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx index a59eb0d91ae..14d81e7e591 100644 --- a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx +++ b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx @@ -21,7 +21,7 @@ const colorString = 'var(--color-fire-red)' const colorType = 'var(--color-violet)' const colorUndefined = 'var(--color-black-55)' -const FormattedCode = ({ +export const FormattedCode = ({ variant, strikethrough, children, @@ -178,7 +178,7 @@ function convertToCamelCase(doc: string, keys: string[]) { return doc } -function formatName(name: string): React.ReactNode | string { +export function formatName(name: string): React.ReactNode | string { if (name.includes('/')) { return {name} } diff --git a/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx b/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx new file mode 100644 index 00000000000..fe701ef1492 --- /dev/null +++ b/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx @@ -0,0 +1,109 @@ +import styled from '@emotion/styled' +import { Anchor, P, Table, Td, Th, Tr } from '@dnb/eufemia/src' +import { extendDeep } from '@dnb/eufemia/src/shared/component-helper' +import globalTranslations from '@dnb/eufemia/src/shared/locales' +import formsTranslations from '@dnb/eufemia/src/extensions/forms/constants/locales' +import { FormattedCode } from './PropertiesTable' + +const StyledTable = styled(Table)` + td { + white-space: nowrap; + } +` + +const allTranslations = extendDeep( + {}, + globalTranslations, + formsTranslations, +) + +export default function TranslationsTable({ + localeKey, +}: { + localeKey?: string | Array +}) { + const entries = {} + const allowList = {} + const localeKeys = ( + Array.isArray(localeKey) ? localeKey : [localeKey] + ).map((key) => { + if (key.includes('.')) { + const first = key.split('.')[0] + allowList[first] = allowList[first] || [] + allowList[first].push(key) + return first + } + + return key + }) + + Object.entries(allTranslations).forEach(([locale, translations]) => { + localeKeys.forEach((localeKey) => { + Object.entries(translations[localeKey]).forEach( + ([key, translation]) => { + key = `${localeKey}.${key}` + if ( + allowList[localeKey] && + !allowList[localeKey].includes(key) + ) { + return + } + entries[key] = Object.assign(entries[key] || {}, { + [locale]: translation, + }) + }, + ) + }) + }) + + const locales = Object.keys(allTranslations) + const tableRows = Object.entries(entries).map(([key, values]) => { + return ( + + + {key} + + {Object.entries(values).map(([locale, value], i) => { + return ( + + {typeof value === 'string' ? ( + value + ) : ( +
{JSON.stringify(value, null, 2)}
+ )} + + ) + })} + + ) + }) + + return ( + <> +

+ More info about translations can be found in the{' '} + + general localization + {' '} + and{' '} + + Eufemia Forms localization + {' '} + docs. +

+ + + + + Key + {locales.map((locale) => ( + {locale} + ))} + + + {tableRows} + + + + ) +} diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx index d016bf21aa7..211588abfe4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx @@ -213,7 +213,7 @@ export default function Provider( } // - Locale - const translation = useTranslation().Form + const translation = useTranslation().Field // - Ajv const ajvRef = useRef(makeAjvInstance(ajvInstance)) diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx index 57685c93b2b..d603f190232 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx @@ -2372,7 +2372,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect( document.querySelector('.dnb-global-status__title') - ).toHaveTextContent(nb.Form.errorSummaryTitle) + ).toHaveTextContent(nb.Field.errorSummaryTitle) }) await userEvent.type(input, 'foo') @@ -2434,7 +2434,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect( document.querySelector('.dnb-global-status__title') - ).toHaveTextContent(nb.Form.errorSummaryTitle) + ).toHaveTextContent(nb.Field.errorSummaryTitle) }) await userEvent.type(input, 'foo') diff --git a/packages/dnb-eufemia/src/extensions/forms/constants/locales/en-GB.ts b/packages/dnb-eufemia/src/extensions/forms/constants/locales/en-GB.ts index 25a077d0566..6d3d64795fb 100644 --- a/packages/dnb-eufemia/src/extensions/forms/constants/locales/en-GB.ts +++ b/packages/dnb-eufemia/src/extensions/forms/constants/locales/en-GB.ts @@ -3,6 +3,13 @@ export default { /** * General */ + Field: { + errorSummaryTitle: 'Please correct the following errors', + stateSummary: 'Summary:', + errorSummary: 'Please correct the following errors:', + errorRequired: 'This field is required.', + errorPattern: 'The value is invalid.', + }, SubmitButton: { text: 'Send', sendText: 'Send', @@ -13,9 +20,6 @@ export default { edit: 'Edit', summaryTitle: 'Summary', }, - Form: { - errorSummaryTitle: 'Please correct the following errors', - }, Section: { remove: 'Remove', done: 'Done', @@ -27,12 +31,6 @@ export default { /** * Base fields */ - Field: { - stateSummary: 'Summary:', - errorSummary: 'Please correct the following errors:', - errorRequired: 'This field is required.', - errorPattern: 'The value is invalid.', - }, StringField: { errorMinLength: 'The value can not be shorter than {minLength} characters.', diff --git a/packages/dnb-eufemia/src/extensions/forms/constants/locales/nb-NO.ts b/packages/dnb-eufemia/src/extensions/forms/constants/locales/nb-NO.ts index d6ea5f87429..eaeb36e8f73 100644 --- a/packages/dnb-eufemia/src/extensions/forms/constants/locales/nb-NO.ts +++ b/packages/dnb-eufemia/src/extensions/forms/constants/locales/nb-NO.ts @@ -3,6 +3,13 @@ export default { /** * General */ + Field: { + errorSummaryTitle: 'Feil som må rettes', + stateSummary: 'Oppsummering:', + errorSummary: 'Feil som må rettes:', + errorRequired: 'Dette feltet må fylles ut.', + errorPattern: 'Verdien er ugyldig.', + }, SubmitButton: { text: 'Send', sendText: 'Send inn', @@ -13,9 +20,6 @@ export default { edit: 'Endre', summaryTitle: 'Oppsummering', }, - Form: { - errorSummaryTitle: 'Feil som må rettes', - }, Section: { remove: 'Fjern', done: 'Ferdig', @@ -27,12 +31,6 @@ export default { /** * Base fields */ - Field: { - stateSummary: 'Oppsummering:', - errorSummary: 'Feil som må rettes:', - errorRequired: 'Dette feltet må fylles ut.', - errorPattern: 'Verdien er ugyldig.', - }, StringField: { errorMinLength: 'Verdien kan ikke være kortere enn {minLength} tegn.', From 62b23e8fbf626f795a129e4dcb2f21d5b65b14a1 Mon Sep 17 00:00:00 2001 From: Joakim Bjerknes Date: Thu, 13 Jun 2024 10:32:28 +0200 Subject: [PATCH 05/16] feat(Anchor): add helper classes as props (#3701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tobias Høegh --- .../docs/uilib/components/anchor/demos.mdx | 10 +-- .../uilib/components/anchor/properties.mdx | 16 +--- .../src/components/anchor/Anchor.tsx | 37 ++++++++- .../src/components/anchor/AnchorDocs.ts | 76 +++++++++++++++++++ .../anchor/__tests__/Anchor.test.tsx | 48 ++++++++++++ 5 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 packages/dnb-eufemia/src/components/anchor/AnchorDocs.ts diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/demos.mdx index 39440d5fff3..5c9da1c895c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/demos.mdx @@ -43,12 +43,12 @@ To force a specific state of style, use the following classes to do so: -### Anchor modifiers +### Anchor modifier props -- `.dnb-anchor--no-animation` No Animation -- `.dnb-anchor--no-style` No Style -- `.dnb-anchor--no-hover` No Hover -- `.dnb-anchor--no-underline` No Underline +- `noAnimation` - No Animation +- `noStyle` - No Style +- `noHover` - No Hover +- `noUnderline` - No Underline ### Anchor with `target="_blank"` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/properties.mdx index 5a164183d45..c294b5fbe4d 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/anchor/properties.mdx @@ -2,20 +2,12 @@ showTabs: true --- +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { AnchorProperties } from '@dnb/eufemia/src/components/anchor/AnchorDocs' + ## Properties -| Properties | Description | -| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| `element` | _(optional)_ define what HTML or React element should be used (e.g. `element={Link}`). Defaults to semantic `a` element. | -| `href` | _(optional)_ relative or absolute url. | -| `to` | _(optional)_ use this prop only if you are using a router Link component as the `element` that uses the `to` property to declare the navigation url. | -| `target` | _(optional)_ defines the opening method. Use `_blank` to open a new browser window/tab. | -| `targetBlankTitle` | _(optional)_ the title shown as a tooltip when target is set to `_blank`. | -| `tooltip` | _(optional)_ Provide a string or a React Element to be shown as the tooltip content. | -| `icon` | _(optional)_ [Primary Icons](/icons/primary) can be set as a string (e.g. icon="chevron_right"), other icons should be set as React elements. | -| `iconPosition` | _(optional)_ `left` (default) or `right`. Places icon to the left or to the right of the text. | -| `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. | -| [Space](/uilib/layout/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | + ### Router Link diff --git a/packages/dnb-eufemia/src/components/anchor/Anchor.tsx b/packages/dnb-eufemia/src/components/anchor/Anchor.tsx index a795dfb6e22..864f416e5a2 100644 --- a/packages/dnb-eufemia/src/components/anchor/Anchor.tsx +++ b/packages/dnb-eufemia/src/components/anchor/Anchor.tsx @@ -46,13 +46,38 @@ export type AnchorProps = { /** @deprecated use innerRef instead */ inner_ref?: React.RefObject + /** + * Removes default animation. + * Default: `false` + */ + noAnimation?: boolean + /** + * Removes default styling. + * Default: `false` + */ + noStyle?: boolean + /** + * Removes default hover style. + * Default: `false` + */ + noHover?: boolean + /** + * Removes underline. + * Default: `false` + */ + noUnderline?: boolean } export type AnchorAllProps = AnchorProps & Omit, 'ref'> & SpacingProps -const defaultProps = {} +const defaultProps = { + noAnimation: false, + noStyle: false, + noHover: false, + noUnderline: false, +} export function AnchorInstance(localProps: AnchorAllProps) { const context = React.useContext(Context) @@ -85,6 +110,10 @@ export function AnchorInstance(localProps: AnchorAllProps) { omitClass, innerRef, targetBlankTitle, + noAnimation, + noHover, + noStyle, + noUnderline, ...rest } = allProps @@ -121,7 +150,11 @@ export function AnchorInstance(localProps: AnchorAllProps) { 'dnb-anchor', prefix && 'dnb-anchor--icon-left', suffix && 'dnb-anchor--icon-right', - typeof children !== 'string' && 'dnb-anchor--was-node' + typeof children !== 'string' && 'dnb-anchor--was-node', + noAnimation && 'dnb-anchor--no-animation', + noHover && 'dnb-anchor--no-hover', + noStyle && 'dnb-anchor--no-style', + noUnderline && 'dnb-anchor--no-underline' ), className )} diff --git a/packages/dnb-eufemia/src/components/anchor/AnchorDocs.ts b/packages/dnb-eufemia/src/components/anchor/AnchorDocs.ts new file mode 100644 index 00000000000..43f397be98e --- /dev/null +++ b/packages/dnb-eufemia/src/components/anchor/AnchorDocs.ts @@ -0,0 +1,76 @@ +import { PropertiesTableProps } from '../../shared/types' + +export const AnchorProperties: PropertiesTableProps = { + element: { + doc: 'Define what HTML or React element should be used (e.g. `element={Link}`). Defaults to semantic `a` element.', + type: 'React.Element', + status: 'optional', + }, + href: { + doc: 'Relative or absolute url.', + type: 'string', + status: 'optional', + }, + to: { + doc: 'Use this prop only if you are using a router Link component as the `element` that uses the `to` property to declare the navigation url.', + type: 'string', + status: 'optional', + }, + target: { + doc: 'Defines the opening method. Use `_blank` to open a new browser window/tab.', + type: 'string', + status: 'optional', + }, + targetBlankTitle: { + doc: 'The title shown as a tooltip when target is set to `_blank`.', + type: 'string', + status: 'optional', + }, + tooltip: { + doc: 'Provide a string or a React Element to be shown as the tooltip content.', + type: 'string', + status: 'optional', + }, + icon: { + doc: '[Primary Icons](/icons/primary) can be set as a string (e.g. icon="chevron_right"), other icons should be set as React elements.', + type: 'React.Node', + status: 'optional', + }, + iconPosition: { + doc: '`left` (default) or `right`. Places icon to the left or to the right of the text.', + type: 'string', + status: 'optional', + }, + noAnimation: { + doc: 'Removes animations if set to `true`. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + noHover: { + doc: 'Removes hover effects if set to `true`. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + noStyle: { + doc: 'Removes styling if set to `true`. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + noUnderline: { + doc: 'Removes underline if set to `true`. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + skeleton: { + doc: 'If set to `true`, an overlaying skeleton with animation will be shown.', + type: 'boolean', + status: 'optional', + }, + '[Space](/uilib/layout/space/properties)': { + doc: 'Spacing properties like `top` or `bottom` are supported.', + type: ['string', 'object'], + status: 'optional', + }, +} + +export const AnchorEvents: PropertiesTableProps = {} diff --git a/packages/dnb-eufemia/src/components/anchor/__tests__/Anchor.test.tsx b/packages/dnb-eufemia/src/components/anchor/__tests__/Anchor.test.tsx index 027d8eab823..197b490dd72 100644 --- a/packages/dnb-eufemia/src/components/anchor/__tests__/Anchor.test.tsx +++ b/packages/dnb-eufemia/src/components/anchor/__tests__/Anchor.test.tsx @@ -417,6 +417,54 @@ describe('Anchor element', () => { expect(document.querySelector('a')).toHaveAttribute('href', '/url') }) + + it('should have no-animation class if "noAnimation" props is true', () => { + const { rerender } = render() + + const anchor = document.querySelector('.dnb-anchor') + + expect(anchor.className).not.toContain('dnb-anchor--no-animation') + + rerender() + + expect(anchor.className).toContain('dnb-anchor--no-animation') + }) + + it('shouldhave no-hover class if "noHover" props is true', () => { + const { rerender } = render() + + const anchor = document.querySelector('.dnb-anchor') + + expect(anchor.className).not.toContain('dnb-anchor--no-hover') + + rerender() + + expect(anchor.className).toContain('dnb-anchor--no-hover') + }) + + it('should have no-style class if "noStyle" props is true', () => { + const { rerender } = render() + + const anchor = document.querySelector('.dnb-anchor') + + expect(anchor.className).not.toContain('dnb-anchor--no-style') + + rerender() + + expect(anchor.className).toContain('dnb-anchor--no-style') + }) + + it('should have no-underline class if "noUnderline" props is true', () => { + const { rerender } = render() + + const anchor = document.querySelector('.dnb-anchor') + + expect(anchor.className).not.toContain('dnb-anchor--no-underline') + + rerender() + + expect(anchor.className).toContain('dnb-anchor--no-underline') + }) }) describe('Anchor scss', () => { From 0a481879b5d9fc13e48d651117b7888653ed5fff Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 13 Jun 2024 11:58:04 +0200 Subject: [PATCH 06/16] chore(TranslationsTable): fallbacks and warnings when no translations (#3704) --- .../src/shared/parts/TranslationsTable.tsx | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx b/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx index fe701ef1492..9b301d9bb06 100644 --- a/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx +++ b/packages/dnb-design-system-portal/src/shared/parts/TranslationsTable.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import { Anchor, P, Table, Td, Th, Tr } from '@dnb/eufemia/src' -import { extendDeep } from '@dnb/eufemia/src/shared/component-helper' +import { extendDeep, warn } from '@dnb/eufemia/src/shared/component-helper' import globalTranslations from '@dnb/eufemia/src/shared/locales' import formsTranslations from '@dnb/eufemia/src/extensions/forms/constants/locales' import { FormattedCode } from './PropertiesTable' @@ -39,20 +39,22 @@ export default function TranslationsTable({ Object.entries(allTranslations).forEach(([locale, translations]) => { localeKeys.forEach((localeKey) => { - Object.entries(translations[localeKey]).forEach( - ([key, translation]) => { - key = `${localeKey}.${key}` - if ( - allowList[localeKey] && - !allowList[localeKey].includes(key) - ) { - return - } - entries[key] = Object.assign(entries[key] || {}, { - [locale]: translation, - }) - }, - ) + const translationsObj = translations[localeKey] + if (!translationsObj) { + warn( + `TranslationsTable: Could not find any translations for key: "${localeKey}", perhaps you misspelled the key's name?`, + ) + return + } + Object.entries(translationsObj).forEach(([key, translation]) => { + key = `${localeKey}.${key}` + if (allowList[localeKey] && !allowList[localeKey].includes(key)) { + return + } + entries[key] = Object.assign(entries[key] || {}, { + [locale]: translation, + }) + }) }) }) @@ -78,6 +80,13 @@ export default function TranslationsTable({ ) }) + if (tableRows.length == 0) { + warn( + `TranslationsTable: Not able to find any translations for input : "${localeKey}", hence not rendering the translations table.`, + ) + return + } + return ( <>

From 5a2d95f32d8d2719c14eaa044ed1f1c9d9b347bb Mon Sep 17 00:00:00 2001 From: Joakim Bjerknes Date: Thu, 13 Jun 2024 12:02:20 +0200 Subject: [PATCH 07/16] chore(ToggleButton): replace margin spacing with gap (#3700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tobias Høegh --- .../__snapshots__/DatePicker.test.tsx.snap | 20 ++++++++++----- .../toggle-button/ToggleButtonGroup.js | 9 +++++-- .../__snapshots__/ToggleButton.test.tsx.snap | 20 ++++++++++----- .../style/dnb-toggle-button.scss | 25 ++++++++++++------- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/packages/dnb-eufemia/src/components/date-picker/__tests__/__snapshots__/DatePicker.test.tsx.snap b/packages/dnb-eufemia/src/components/date-picker/__tests__/__snapshots__/DatePicker.test.tsx.snap index 20ffef5373b..16f9e6729db 100644 --- a/packages/dnb-eufemia/src/components/date-picker/__tests__/__snapshots__/DatePicker.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/date-picker/__tests__/__snapshots__/DatePicker.test.tsx.snap @@ -3595,8 +3595,8 @@ button .dnb-form-status__text { margin-right: 1rem; } .dnb-toggle-button-group { - --toggle-button-group-margin-right: 1rem; - --toggle-button-group-margin-bottom: 1rem; + --toggle-button-group-column-gap: 1rem; + --toggle-button-group-row-gap: 1rem; display: inline-flex; } .dnb-toggle-button-group fieldset { @@ -3604,10 +3604,6 @@ button .dnb-form-status__text { padding: 0; border: none; } -.dnb-toggle-button-group .dnb-toggle-button { - margin-right: var(--toggle-button-group-margin-right); - margin-bottom: var(--toggle-button-group-margin-bottom); -} .dnb-toggle-button-group--column .dnb-toggle-button { display: flex; margin-right: 0; @@ -3618,10 +3614,22 @@ button .dnb-form-status__text { .dnb-toggle-button-group__shell { display: flex; flex-direction: column; + row-gap: var(--toggle-button-group-row-gap); } .dnb-toggle-button-group__shell__children { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + column-gap: var(--toggle-button-group-column-gap); + row-gap: var(--toggle-button-group-row-gap); order: 1; } +.dnb-toggle-button-group__shell__children--row { + flex-direction: row; +} +.dnb-toggle-button-group__shell__children--column { + flex-direction: column; +} .dnb-toggle-button-group__shell > .dnb-form-status { order: 2; transform: translateY(-0.5rem); diff --git a/packages/dnb-eufemia/src/components/toggle-button/ToggleButtonGroup.js b/packages/dnb-eufemia/src/components/toggle-button/ToggleButtonGroup.js index 45c5ce43fee..6632156a4ad 100644 --- a/packages/dnb-eufemia/src/components/toggle-button/ToggleButtonGroup.js +++ b/packages/dnb-eufemia/src/components/toggle-button/ToggleButtonGroup.js @@ -299,7 +299,7 @@ export default class ToggleButtonGroup extends React.PureComponent { ? 'vertical' : 'horizontal' } - spacing={vertical ? 'x-small' : 'small'} + gap={vertical ? 'x-small' : 'small'} > {label && ( - + {children} {suffix && ( diff --git a/packages/dnb-eufemia/src/components/toggle-button/__tests__/__snapshots__/ToggleButton.test.tsx.snap b/packages/dnb-eufemia/src/components/toggle-button/__tests__/__snapshots__/ToggleButton.test.tsx.snap index 0caafc17901..f88a001a6d7 100644 --- a/packages/dnb-eufemia/src/components/toggle-button/__tests__/__snapshots__/ToggleButton.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/toggle-button/__tests__/__snapshots__/ToggleButton.test.tsx.snap @@ -1716,8 +1716,8 @@ button .dnb-form-status__text { margin-right: 1rem; } .dnb-toggle-button-group { - --toggle-button-group-margin-right: 1rem; - --toggle-button-group-margin-bottom: 1rem; + --toggle-button-group-column-gap: 1rem; + --toggle-button-group-row-gap: 1rem; display: inline-flex; } .dnb-toggle-button-group fieldset { @@ -1725,10 +1725,6 @@ button .dnb-form-status__text { padding: 0; border: none; } -.dnb-toggle-button-group .dnb-toggle-button { - margin-right: var(--toggle-button-group-margin-right); - margin-bottom: var(--toggle-button-group-margin-bottom); -} .dnb-toggle-button-group--column .dnb-toggle-button { display: flex; margin-right: 0; @@ -1739,10 +1735,22 @@ button .dnb-form-status__text { .dnb-toggle-button-group__shell { display: flex; flex-direction: column; + row-gap: var(--toggle-button-group-row-gap); } .dnb-toggle-button-group__shell__children { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + column-gap: var(--toggle-button-group-column-gap); + row-gap: var(--toggle-button-group-row-gap); order: 1; } +.dnb-toggle-button-group__shell__children--row { + flex-direction: row; +} +.dnb-toggle-button-group__shell__children--column { + flex-direction: column; +} .dnb-toggle-button-group__shell > .dnb-form-status { order: 2; transform: translateY(-0.5rem); diff --git a/packages/dnb-eufemia/src/components/toggle-button/style/dnb-toggle-button.scss b/packages/dnb-eufemia/src/components/toggle-button/style/dnb-toggle-button.scss index 4fad9bdd991..e4db2e6eaca 100644 --- a/packages/dnb-eufemia/src/components/toggle-button/style/dnb-toggle-button.scss +++ b/packages/dnb-eufemia/src/components/toggle-button/style/dnb-toggle-button.scss @@ -115,21 +115,14 @@ } &-group { - --toggle-button-group-margin-right: 1rem; - --toggle-button-group-margin-bottom: 1rem; - + --toggle-button-group-column-gap: 1rem; + --toggle-button-group-row-gap: 1rem; display: inline-flex; fieldset { @include fieldsetReset(); } - // default spacing - .dnb-toggle-button { - margin-right: var(--toggle-button-group-margin-right); - margin-bottom: var(--toggle-button-group-margin-bottom); - } - &--column .dnb-toggle-button { display: flex; margin-right: 0; @@ -143,9 +136,23 @@ &__shell { display: flex; flex-direction: column; + row-gap: var(--toggle-button-group-row-gap); &__children { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + column-gap: var(--toggle-button-group-column-gap); + row-gap: var(--toggle-button-group-row-gap); + order: 1; + + &--row { + flex-direction: row; + } + &--column { + flex-direction: column; + } } & > .dnb-form-status { From 70b1f8ba5129bcdf9860ed967db3d0919fb9ccc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Thu, 13 Jun 2024 12:18:42 +0200 Subject: [PATCH 08/16] feat(Icon): rewrite to TypeScript and hooks (#3703) Will solve the issue described in this PR #3699 Even all the tests and types are running green, it needs extensive manual testing. --------- Co-authored-by: Anders --- .../src/shared/menu/StickyMenuBar.tsx | 3 +- .../src/components/accordion/Accordion.tsx | 4 +- .../components/accordion/AccordionHeader.tsx | 34 +- .../components/breadcrumb/BreadcrumbItem.tsx | 5 +- .../components/icon-primary/IconPrimary.d.ts | 12 - .../components/icon-primary/IconPrimary.js | 75 --- .../components/icon-primary/IconPrimary.tsx | 57 +++ .../__tests__/IconPrimary.test.tsx | 4 +- .../dnb-eufemia/src/components/icon/Icon.d.ts | 62 --- .../dnb-eufemia/src/components/icon/Icon.js | 441 ---------------- .../dnb-eufemia/src/components/icon/Icon.tsx | 469 ++++++++++++++++++ .../icon/{IconHelpers.js => IconHelpers.ts} | 6 +- .../icon/{IconPrimary.js => IconPrimary.ts} | 0 .../components/icon/__tests__/Icon.test.tsx | 6 +- .../tabs/__tests__/Tabs.screenshot.test.ts | 2 + packages/dnb-eufemia/src/shared/Context.tsx | 4 + 16 files changed, 560 insertions(+), 624 deletions(-) delete mode 100644 packages/dnb-eufemia/src/components/icon-primary/IconPrimary.d.ts delete mode 100644 packages/dnb-eufemia/src/components/icon-primary/IconPrimary.js create mode 100644 packages/dnb-eufemia/src/components/icon-primary/IconPrimary.tsx delete mode 100644 packages/dnb-eufemia/src/components/icon/Icon.d.ts delete mode 100644 packages/dnb-eufemia/src/components/icon/Icon.js create mode 100644 packages/dnb-eufemia/src/components/icon/Icon.tsx rename packages/dnb-eufemia/src/components/icon/{IconHelpers.js => IconHelpers.ts} (60%) rename packages/dnb-eufemia/src/components/icon/{IconPrimary.js => IconPrimary.ts} (100%) diff --git a/packages/dnb-design-system-portal/src/shared/menu/StickyMenuBar.tsx b/packages/dnb-design-system-portal/src/shared/menu/StickyMenuBar.tsx index 84c1459514d..d868153df5c 100644 --- a/packages/dnb-design-system-portal/src/shared/menu/StickyMenuBar.tsx +++ b/packages/dnb-design-system-portal/src/shared/menu/StickyMenuBar.tsx @@ -26,6 +26,7 @@ import { } from './StickyMenuBar.module.scss' import { Link } from '../tags/Anchor' import GithubLogo from '../../docs/contribute/assets/github-logo.js' +import type { IconSize } from '@dnb/eufemia/src/components/Icon' export default function StickyMenuBar({ hideSidebarToggleButton = false, @@ -75,7 +76,7 @@ export default function StickyMenuBar({ diff --git a/packages/dnb-eufemia/src/components/accordion/Accordion.tsx b/packages/dnb-eufemia/src/components/accordion/Accordion.tsx index e8de29f580b..032a09d4d4e 100644 --- a/packages/dnb-eufemia/src/components/accordion/Accordion.tsx +++ b/packages/dnb-eufemia/src/components/accordion/Accordion.tsx @@ -44,11 +44,11 @@ export type AccordionHeading = boolean | React.ReactNode export type AccordionIcon = | IconIcon | { - closed?: React.ReactNode | ((...args: any[]) => any) + closed?: IconIcon /** * If set to `true` the accordion will be expanded as its initial state. */ - expanded?: React.ReactNode | ((...args: any[]) => any) + expanded?: IconIcon } export type AccordionAttributes = string | Record diff --git a/packages/dnb-eufemia/src/components/accordion/AccordionHeader.tsx b/packages/dnb-eufemia/src/components/accordion/AccordionHeader.tsx index dc4e3fd10cb..335429d50c3 100644 --- a/packages/dnb-eufemia/src/components/accordion/AccordionHeader.tsx +++ b/packages/dnb-eufemia/src/components/accordion/AccordionHeader.tsx @@ -20,7 +20,7 @@ import { } from '../skeleton/SkeletonHelper' import type { HeadingLevel } from '../Heading' -import type { IconSize } from '../Icon' +import type { IconIcon, IconSize } from '../Icon' import type { SkeletonShow } from '../Skeleton' import type { AccordionIcon, @@ -89,11 +89,10 @@ function AccordionHeaderContainer({ } type AccordionHeaderIconIcon = - | React.ReactNode - | ((...args: any[]) => React.ReactNode) + | IconIcon | { - closed?: React.ReactNode | ((...args: any[]) => React.ReactNode) - expanded?: React.ReactNode | ((...args: any[]) => React.ReactNode) + closed?: IconIcon + expanded?: IconIcon } export type AccordionHeaderIconProps = { @@ -104,11 +103,19 @@ export type AccordionHeaderIconProps = { } function AccordionHeaderIcon({ - icon, + icon: iconProp, expanded, size = 'medium', icon_position, }: AccordionHeaderIconProps) { + const icon = ( + iconProp && + typeof iconProp === 'object' && + 'expanded' in iconProp && + typeof iconProp?.expanded !== 'undefined' + ? iconProp[expanded ? 'expanded' : 'closed'] + : iconProp || 'chevron-down' + ) as IconIcon return ( - any)) || - 'chevron-down' - } - aria-hidden - /> + ) } diff --git a/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx b/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx index 65c31f0369b..bd7eafd2136 100644 --- a/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx +++ b/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx @@ -77,10 +77,7 @@ const defaultProps = { skeleton: null, } -const determineIcon: IconIcon = ( - variant: string, - isSmallScreen: boolean -) => { +const determineIcon = (variant: string, isSmallScreen: boolean) => { switch (variant) { case 'home': return 'home-icon' diff --git a/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.d.ts b/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.d.ts deleted file mode 100644 index 589b8a29814..00000000000 --- a/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as React from 'react'; -import type { IconProps } from '../Icon'; -export interface IconPrimaryProps - extends IconProps, - Omit, 'size' | 'ref'> {} -export default class IconPrimary extends React.Component< - IconPrimaryProps, - any -> { - static defaultProps: object; - render(): JSX.Element; -} diff --git a/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.js b/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.js deleted file mode 100644 index eb98caa0fa7..00000000000 --- a/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Web Icon Component - * - * This is a legacy component. - * For referencing while developing new features, please use a Functional component. - */ - -import React from 'react' -// eslint-disable-next-line no-unused-vars -import PropTypes from 'prop-types' // Is needed because of ts types -import Context from '../../shared/Context' -import { extendPropsWithContextInClassComponent } from '../../shared/component-helper' -import DefaultIcon, { - iconPropTypes, - DefaultIconSize, - prerenderIcon, - prepareIcon, -} from '../icon/Icon' - -// NB: The path reflects the rollup.config.js -> external: '../../icons/dnb/primary_icons' -import * as primary_icons from '../../icons/dnb/primary_icons' -import * as primary_icons_medium from '../../icons/dnb/primary_icons_medium' - -const icons = { ...primary_icons, ...primary_icons_medium } - -export { DefaultIconSize } - -export default class IconPrimary extends React.PureComponent { - static contextType = Context - - static propTypes = { - /** - * Use spread, so generateTypes.js makes a valid copy to create the types - */ - ...iconPropTypes, - } - static defaultProps = { ...DefaultIcon.defaultProps } - - static getIcon(props) { - return DefaultIcon.getIcon(props) - } - - render() { - // use only the props from context, who are available here anyway - const props = extendPropsWithContextInClassComponent( - this.props, - IconPrimary.defaultProps, - { skeleton: this.context?.skeleton }, - this.context.Icon, - this.context.IconPrimary - ) - - const { icon, size, wrapperParams, iconParams, alt } = prepareIcon( - props, - this.context - ) - - const IconContainer = prerenderIcon({ - icon, - size, - alt, - listOfIcons: icons, - }) - - if (!IconContainer) return <> - - return ( - - - - ) - } -} - -IconPrimary._supportsSpacingProps = true diff --git a/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.tsx b/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.tsx new file mode 100644 index 00000000000..ba5efe6c269 --- /dev/null +++ b/packages/dnb-eufemia/src/components/icon-primary/IconPrimary.tsx @@ -0,0 +1,57 @@ +import React, { useContext } from 'react' +import Context from '../../shared/Context' +import { extendPropsWithContext } from '../../shared/component-helper' +import { + prerenderIcon, + prepareIcon, + IconAllProps, + IconProps, +} from '../icon/Icon' + +// NB: The path reflects the rollup.config.js -> external: '../../icons/dnb/primary_icons' +import * as primary_icons from '../../icons/dnb/primary_icons' +import * as primary_icons_medium from '../../icons/dnb/primary_icons_medium' + +export * from '../icon/Icon' + +export type IconPrimaryProps = IconProps +export type IconPrimaryAllProps = IconAllProps + +const icons = { ...primary_icons, ...primary_icons_medium } + +export default function IconPrimary(localProps: IconAllProps) { + const context = useContext(Context) + + // use only the props from context, who are available here anyway + const props = extendPropsWithContext( + localProps, + {}, + { skeleton: context?.skeleton }, + context.Icon, + context.IconPrimary + ) + + const { icon, size, wrapperParams, iconParams, alt } = prepareIcon( + props, + context + ) + + const IconContainer = prerenderIcon({ + icon, + size, + alt, + listOfIcons: icons, + }) + + if (!IconContainer) { + return <> + } + + return ( + + + + ) +} + +IconPrimary._supportsSpacingProps = true diff --git a/packages/dnb-eufemia/src/components/icon-primary/__tests__/IconPrimary.test.tsx b/packages/dnb-eufemia/src/components/icon-primary/__tests__/IconPrimary.test.tsx index 5a543843695..2328ff82370 100644 --- a/packages/dnb-eufemia/src/components/icon-primary/__tests__/IconPrimary.test.tsx +++ b/packages/dnb-eufemia/src/components/icon-primary/__tests__/IconPrimary.test.tsx @@ -5,10 +5,10 @@ import React from 'react' import { axeComponent } from '../../../core/jest/jestSetup' -import IconPrimary, { IconPrimaryProps } from '../IconPrimary' +import IconPrimary, { IconPrimaryAllProps } from '../IconPrimary' import { render } from '@testing-library/react' -const props: IconPrimaryProps = { +const props: IconPrimaryAllProps = { icon: 'question', } diff --git a/packages/dnb-eufemia/src/components/icon/Icon.d.ts b/packages/dnb-eufemia/src/components/icon/Icon.d.ts deleted file mode 100644 index e377ac2eb1a..00000000000 --- a/packages/dnb-eufemia/src/components/icon/Icon.d.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as React from 'react'; -import type { SkeletonShow } from '../Skeleton'; -import type { SpacingProps } from '../space/types'; -export type IconIcon = - | string - | React.ReactNode - | ((...args: any[]) => any); -export type IconSize = number | string | 'default' | 'medium' | 'large'; -export type IconWidth = string | number; -export type IconHeight = string | number; -export type IconAttributes = string | Record; -export type IconChildren = React.ReactNode | ((...args: any[]) => any); -export type IconColor = string; -export interface IconProps - extends Omit, 'ref'>, - SpacingProps { - /** - * (required) a React SVG Component or the icon name (in case we use `IconPrimary` or `dnb-icon-primary`). - */ - icon?: IconIcon; - /** - * Modifier class to define. Will result in: `dnb-icon--${modifier}`. - */ - modifier?: string; - /** - * The dimension of the icon. This will be the `viewBox` and represent `width` and `height`. Defaults to `16`. You can use `small`,`medium`, `large` or `auto`. Auto will enable that the icon size gets inherited by the parent HTML element if it provides a `font-size`. - */ - size?: IconSize; - width?: IconWidth; - height?: IconHeight; - /** - * Use `true` to display a rounded border with an inherited color. Keep in mind that the icon will have a larger total width and height of `+0.5em`. - */ - border?: boolean; - /** - * The color can be any valid color property, such as Hex, RGB or preferable – any CSS variable from the colors table, e.g. `var(--color-ocean-green)`. Default is no color, which means `--color-black-80`. - */ - color?: IconColor; - /** - * Defaults to `true`. Set to `false` if you do not want to inherit the color by `currentColor`. - */ - inherit_color?: boolean; - /** - * The alternative label (text version) of the icon. Defaults to the imported icon name. - */ - alt?: string; - /** - * Use a title to provide extra information about the icon used. - */ - title?: string; - /** - * If set to `true`, an overlaying skeleton with animation will be shown. - */ - skeleton?: SkeletonShow; - attributes?: IconAttributes; - className?: string; - children?: IconChildren; -} -export default class Icon extends React.Component { - static defaultProps: object; - render(): JSX.Element; -} diff --git a/packages/dnb-eufemia/src/components/icon/Icon.js b/packages/dnb-eufemia/src/components/icon/Icon.js deleted file mode 100644 index bc7f5215f87..00000000000 --- a/packages/dnb-eufemia/src/components/icon/Icon.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * Web Icon Component - * - * This is a legacy component. - * For referencing while developing new features, please use a Functional component. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import { ErrorHandler } from '../../shared/error-helper' -import { - isTrue, - validateDOMAttributes, - processChildren, - extendPropsWithContextInClassComponent, -} from '../../shared/component-helper' -import { - spacingPropTypes, - createSpacingClasses, -} from '../space/SpacingHelper' -import { createSkeletonClass } from '../skeleton/SkeletonHelper' -import Context from '../../shared/Context' -import { iconCase } from './IconHelpers' - -export const DefaultIconSize = 16 -export const DefaultIconSizes = { - // small: 8, // currently not in use - default: 16, - medium: 24, - // large: 32 // currently not in use -} -// instead of using Object.entries(DefaultIconSizes) -export const ListDefaultIconSizes = [ - ['default', 16], - ['medium', 24], -] -export const ValidIconSizes = [ - 'small', // 12px 0.75rem - 'default', // 16px 1rem - 'medium', // 24px 1.5rem - 'large', // 32px 2rem - 'x-large', // 40px 2.5rem - 'xx-large', // 48px 3rem -] - -export const iconPropTypes = { - icon: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.node, - PropTypes.func, - ]), - modifier: PropTypes.string, - size: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.oneOf(['default', 'medium', 'large']), - ]), - - ...spacingPropTypes, - - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - border: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - color: PropTypes.string, - inherit_color: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - alt: PropTypes.string, - title: PropTypes.string, - skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - attributes: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - className: PropTypes.string, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), -} - -/** - * The icon component is a span wrapping an inline svg. When using this component in your preferred framework. To load an svg file dynamically, you may need a "svg-loader". Feel free to use whatever tool you want (regarding the setup/tooling), as long as the output is the same markup as shown below. - */ -export default class Icon extends React.PureComponent { - static contextType = Context - - static propTypes = { - /** - * Use spread, so generateTypes.js makes a valid copy to create the types - */ - ...iconPropTypes, - } - - static defaultProps = { - icon: null, - modifier: null, - size: null, - width: null, - height: null, - border: null, - color: null, - inherit_color: true, - alt: null, - title: null, - skeleton: null, - attributes: null, - - className: null, - children: null, - } - - static getIcon(props) { - if (props.icon) { - return props.icon - } - return processChildren(props) - } - - render() { - // use only the props from context, who are available here anyway - const props = extendPropsWithContextInClassComponent( - this.props, - Icon.defaultProps, - { skeleton: this.context?.skeleton }, - this.context.Icon - ) - - const { icon, size, wrapperParams, iconParams, alt } = prepareIcon( - props, - this.context - ) - - if (!icon) { - return null - } - - const IconContainer = prerenderIcon({ icon, size, alt }) - - // make sure we return an empty span if we don't could get the icon - if (!IconContainer) return <> - - return ( - - - - ) - } -} - -export const getIconNameFromComponent = (icon) => { - const name = - typeof icon === 'string' - ? icon - : icon && (icon.displayName || icon.name) - if (/^data:image\//.test(name)) { - return null - } - return name -} - -export const calcSize = (props) => { - const { icon, size, width, height } = props - - let sizeAsInt = -1 - let sizeAsString = null - - // if there is no size, check if we can find the actual size in the name - if (!size || size === DefaultIconSize) { - // get the icon name - we use is for several things - const name = getIconNameFromComponent(icon) - - const nameParts = String(name || '').split('_') - - if (nameParts.length > 1) { - const lastPartOfIconName = nameParts.reverse()[0] - const potentialSize = ListDefaultIconSizes.filter( - ([key]) => key === lastPartOfIconName - ).reduce((acc, [key, value]) => { - return key && value - }, null) - if (potentialSize) { - sizeAsInt = potentialSize - } - if (ValidIconSizes.includes(lastPartOfIconName)) { - sizeAsString = lastPartOfIconName - } - } else { - if (typeof icon === 'function') { - const elem = icon() - if (elem.props) { - let potentialSize = -1 - if (elem.props.width) { - potentialSize = elem.props.width - } - if (!potentialSize && elem.props.viewBox) { - potentialSize = /[0-9]+ [0-9]+ ([0-9]+)/.exec( - elem.props.viewBox - )[1] // get the width - } - if (potentialSize) { - sizeAsInt = potentialSize - } - } - } - } - } - - // if size is defined as a string, find the size number - else if (typeof size === 'string' && !(parseFloat(size) > 0)) { - sizeAsInt = ListDefaultIconSizes.filter( - ([key]) => key === size - ).reduce((acc, [key, value]) => { - return key && value - }, -1) - - // of if the size is a default size defined as a string - if (ValidIconSizes.includes(size)) { - sizeAsString = size - } - } - - // check if the size is given as a number, and if is a default size - else if (parseFloat(size) > 0) { - sizeAsInt = ListDefaultIconSizes.filter( - ([key, value]) => key && value === parseFloat(size) - ).reduce((acc, [key, value]) => { - if (key && value) return value - return acc - }, -1) - - // has custom size - if (sizeAsInt === -1) { - sizeAsInt = parseFloat(size) - sizeAsString = 'custom-size' - } - } - - // check if the sizeAsInt is a default size - and no sizeAsString exists yet - if (!sizeAsString && sizeAsInt > 0) { - const potentialSizeAsString = ListDefaultIconSizes.reduce( - (acc, [key, value]) => { - if (key && value === sizeAsInt) { - return key - } - return acc - }, - null - ) - - if (potentialSizeAsString) { - sizeAsString = potentialSizeAsString - } - } - - // define all the svg parameters - const { sizeAsString: isCustomSize, params: iconParams } = - prepareIconParams({ - sizeAsString, - sizeAsInt, - size, - width, - height, - }) - if (isCustomSize) { - sizeAsString = isCustomSize - } - - if (!(sizeAsInt > 0)) { - sizeAsInt = DefaultIconSize - } - - if (size === 'auto') { - iconParams.width = '100%' - iconParams.height = '100%' - sizeAsString = 'auto' - } - - return { - iconParams, - sizeAsInt, - sizeAsString, - } -} - -const prepareIconParams = ({ sizeAsString, ...rest }) => { - const { size, width, height, sizeAsInt } = rest - const params = {} - - if (!sizeAsString && !(sizeAsInt > 0) && parseFloat(size) > -1) { - params.width = params.height = parseFloat(size) - } else if (sizeAsString === 'custom-size') { - params.width = params.height = parseFloat(sizeAsInt) - } - if (parseFloat(width) > -1) { - sizeAsString = 'custom-size' - params.width = parseFloat(width) - } - if (parseFloat(height) > -1) { - sizeAsString = 'custom-size' - params.height = parseFloat(height) - } - - validateDOMAttributes({}, params) - - return { params, sizeAsString } -} - -export const prepareIcon = (props, context) => { - const { - icon, - size, // eslint-disable-line - width, - height, - border, - color, - inherit_color, - modifier, - alt, - title, - skeleton, - className, - ...attributes - } = props - - const { sizeAsString, iconParams } = calcSize({ - icon, - size, - width, - height, - }) - - if (color) { - iconParams.color = color - } - - const label = icon ? getIconNameFromComponent(icon) : null - - // some wrapper params - // also used for code markup simulation - const wrapperParams = validateDOMAttributes(props, { - role: alt ? 'img' : 'presentation', - alt, // in case the image don't shows up (because we define the role to be img) - 'aria-label': - label && !label.includes('default') - ? label.replace(/_/g, ' ') + ' icon' - : null, // for screen readers only - title, // to show on hover, if defined - ...attributes, - }) - if (!alt && typeof wrapperParams['aria-hidden'] === 'undefined') { - wrapperParams['aria-hidden'] = true - } - if (wrapperParams['aria-hidden']) { - if ( - !wrapperParams['data-testid'] && - typeof process !== 'undefined' && - process.env.NODE_ENV === 'test' - ) { - wrapperParams['data-testid'] = wrapperParams['aria-label'] - } - delete wrapperParams['aria-label'] - } - - wrapperParams.className = classnames( - 'dnb-icon', - modifier && `dnb-icon--${modifier}`, - isTrue(border) && 'dnb-icon--border', - isTrue(inherit_color) && 'dnb-icon--inherit-color', - sizeAsString ? `dnb-icon--${sizeAsString}` : 'dnb-icon--default', - createSkeletonClass(null, skeleton, context), - createSpacingClasses(props), - className - ) - - let iconToRender = Icon.getIcon(props) - - if (iconToRender && typeof iconToRender.defaultProps !== 'undefined') { - iconToRender = React.createElement( - iconToRender, - validateDOMAttributes( - {}, - { - color, - icon, - size, - width, - height, - } - ) - ) - } - - return { - ...props, - icon: iconToRender, - alt, - iconParams, - wrapperParams, - } -} - -export const prerenderIcon = ({ - icon, - size = null, - listOfIcons = null, - alt = null, -} = {}) => { - if (typeof icon === 'string' && /^data:image\//.test(icon)) { - return () => {alt - } - - if (typeof icon === 'function') { - const elem = icon() - if (React.isValidElement(elem)) { - return icon - } - return elem - } - - if (React.isValidElement(icon) || Array.isArray(icon)) { - return () => icon - } - - // for importing react component - try { - icon = iconCase(icon) - if ( - size && - DefaultIconSizes[size] && - size !== 'default' && - !(parseFloat(size) > 0) && - !icon.includes(size) - ) { - icon = `${icon}_${size}` - } - const mod = ( - listOfIcons.dnbIcons ? listOfIcons.dnbIcons : listOfIcons - )[icon] - return mod && mod.default ? mod.default : mod - } catch (e) { - ErrorHandler(`Icon '${icon}' did not exist!`) - return null - } -} - -Icon._supportsSpacingProps = true diff --git a/packages/dnb-eufemia/src/components/icon/Icon.tsx b/packages/dnb-eufemia/src/components/icon/Icon.tsx new file mode 100644 index 00000000000..114389ffee5 --- /dev/null +++ b/packages/dnb-eufemia/src/components/icon/Icon.tsx @@ -0,0 +1,469 @@ +import React, { useContext } from 'react' +import classnames from 'classnames' +import { ErrorHandler } from '../../shared/error-helper' +import { + validateDOMAttributes, + processChildren, + extendPropsWithContext, +} from '../../shared/component-helper' +import Context, { ContextProps } from '../../shared/Context' +import { createSpacingClasses } from '../space/SpacingHelper' +import { createSkeletonClass } from '../skeleton/SkeletonHelper' +import { iconCase } from './IconHelpers' +import { SpacingProps } from '../../shared/types' +import { SkeletonShow } from '../Skeleton' + +export const DefaultIconSize = 16 +export const DefaultIconSizes = { + // small: 8, // currently not in use + default: 16, + medium: 24, + // large: 32 // currently not in use +} as const +export const ListDefaultIconSizes: Array< + [ValidIconType, ValidIconNumericSize] +> = [ + ['default', 16], + ['medium', 24], +] +export const ValidIconType = [ + 'small', // 12px 0.75rem + 'default', // 16px 1rem + 'medium', // 24px 1.5rem + 'large', // 32px 2rem + 'x-large', // 40px 2.5rem + 'xx-large', // 48px 3rem +] as const + +export type DefaultIconSizes = typeof DefaultIconSizes +export type ValidIconType = (typeof ValidIconType)[number] +export type ValidIconNumericSize = DefaultIconSizes[keyof DefaultIconSizes] + +/** For internal usage */ +type IconType = + | string + | React.ReactElement + | ((props?: unknown) => JSX.Element) + | false + +/** For external usage */ +export type IconIcon = IconType | React.FC + +export type IconSize = + | ValidIconNumericSize + | `${ValidIconNumericSize | number}` + | ValidIconType + | 'auto' + | 'basis' + +export type IconColor = + | string + | number + | { [key: string]: string | number } + +export type IconProps = { + /** + * A React SVG Component or the icon name (in case we use `IconPrimary` or `dnb-icon-primary`). + */ + icon?: IconIcon + + /** + * The dimension of the icon. This will be the `viewBox` and represent `width` and `height`. Defaults to `16`. You can use `small`,`medium`, `large` or `auto`. Auto will enable that the icon size gets inherited by the parent HTML element if it provides a `font-size`. + */ + size?: IconSize + + /** + * The color can be any valid color property, such as Hex, RGB or preferable – any CSS variable from the colors table, e.g. `var(--color-ocean-green)`. Default is no color, which means `--color-black-80`. + */ + color?: IconColor + + /** + * Defaults to `true`. Set to `false` if you do not want to inherit the color by `currentColor`. + */ + inheritColor?: boolean + + /** @deprecated Use `inheritColor` instead */ + inherit_color?: boolean + + /** + * The alternative label (text version) of the icon. Defaults to the imported icon name. + */ + alt?: string + + /** + * Use a title to provide extra information about the icon used. + */ + title?: string + + /** + * If set to `true`, an overlaying skeleton with animation will be shown. + */ + skeleton?: SkeletonShow + + /** + * Modifier class to define. Will result in: `dnb-icon--${modifier}`. + */ + modifier?: string + + border?: boolean + width?: `${IconSize}` | `${number}%` | number + height?: `${IconSize}` | `${number}%` | number + children?: IconIcon +} + +export type IconAllProps = IconProps & + SpacingProps & + Omit, 'size' | 'children'> + +export default function Icon(localProps: IconAllProps) { + const context = useContext(Context) + + // use only the props from context, who are available here anyway + const props = extendPropsWithContext( + localProps, + {}, + { skeleton: context?.skeleton }, + context.Icon + ) + + // Todo: rewrite prepareIcon to hook + const { + icon: iconProp, + size, + wrapperParams, + iconParams, + alt, + children, + } = prepareIcon(props, context) + const icon = iconProp ?? children + + if (!icon) { + return null + } + + const IconContainer = prerenderIcon({ icon, size, alt }) + + // make sure we return an empty span if we couldn't get the icon + if (!IconContainer) { + return <> + } + + return ( + + + + ) +} + +export function getIconNameFromComponent(icon: IconProps['icon']): string { + const name = typeof icon === 'function' ? icon.name : String(icon) + if (/^data:image\//.test(name)) { + return null + } + return name +} + +export function calcSize(props: IconProps) { + const { icon, size, width, height } = props as Omit< + IconProps, + 'icon' + > & { icon: IconType } + + let sizeAsInt: ValidIconNumericSize | -1 = null + let sizeAsString = null + + // if there is no size, check if we can find the actual size in the name + if (!size || size === DefaultIconSize) { + // get the icon name - we use it for several things + const name = getIconNameFromComponent(icon) + + const nameParts = String(name || '').split('_') + + if (nameParts.length > 1) { + const lastPartOfIconName = nameParts.reverse()[0] as ValidIconType + const potentialSize = ListDefaultIconSizes.filter( + ([key]) => key === lastPartOfIconName + )?.[0]?.[1] + if (potentialSize) { + sizeAsInt = potentialSize + } + if (ValidIconType.includes(lastPartOfIconName)) { + sizeAsString = lastPartOfIconName + } + } else { + if (typeof icon === 'function') { + const elem = icon() + if (elem.props) { + let potentialSize: ValidIconNumericSize | -1 = null + if (elem.props.width) { + potentialSize = elem.props.width + } + if (!potentialSize && elem.props.viewBox) { + potentialSize = parseFloat( + /[0-9]+ [0-9]+ ([0-9]+)/.exec(elem.props.viewBox)[1] + ) as ValidIconNumericSize // get the width + } + if (!isNaN(potentialSize)) { + sizeAsInt = potentialSize + } + } + } + } + } + + // if size is defined as a string, find the size number + else if (typeof size === 'string' && !(parseFloat(size) > 0)) { + sizeAsInt = + ListDefaultIconSizes.filter(([key]) => key === size)?.[0]?.[1] ?? -1 + + // or if the size is a default size defined as a string + if (ValidIconType.includes(size as ValidIconType)) { + sizeAsString = size + } + } + + // check if the size is given as a number, and if it's a default size + else if (parseFloat(String(size)) > 0) { + sizeAsInt = + ListDefaultIconSizes.filter( + ([key, value]) => key && value === parseFloat(String(size)) + )?.[0]?.[1] ?? -1 + + // has custom size + if (sizeAsInt === -1) { + sizeAsInt = parseFloat(String(size)) as ValidIconNumericSize + sizeAsString = 'custom-size' + } + } + + // check if the sizeAsInt is a default size - and no sizeAsString exists yet + if (!sizeAsString && sizeAsInt > 0) { + const potentialSizeAsString = ListDefaultIconSizes.reduce( + (acc, [key, value]) => { + if (key && value === sizeAsInt) { + return key + } + return acc + }, + null + ) + + if (potentialSizeAsString) { + sizeAsString = potentialSizeAsString + } + } + + // define all the svg parameters + const { sizeAsString: isCustomSize, params: iconParams } = + prepareIconParams({ + sizeAsString, + sizeAsInt, + size, + width, + height, + }) + + if (isCustomSize) { + sizeAsString = isCustomSize + } + + if (!(sizeAsInt > 0)) { + sizeAsInt = DefaultIconSize + } + + if (size === 'auto') { + iconParams.width = '100%' + iconParams.height = '100%' + sizeAsString = 'auto' + } + + return { + iconParams, + sizeAsInt, + sizeAsString, + } +} + +function prepareIconParams({ + sizeAsString, + ...rest +}: Omit & { + sizeAsString?: ValidIconType | 'custom-size' + sizeAsInt?: ValidIconNumericSize | -1 +}) { + const { size, width, height, sizeAsInt } = rest + const params: { + height?: IconProps['height'] + width?: IconProps['width'] + color?: IconProps['color'] + } = {} + + if (!sizeAsString && !(sizeAsInt > 0) && parseFloat(String(size)) > -1) { + params.width = params.height = parseFloat(String(size)) + } else if (sizeAsString === 'custom-size') { + params.width = params.height = parseFloat(String(sizeAsInt)) + } + if (parseFloat(String(width)) > -1) { + sizeAsString = 'custom-size' + params.width = parseFloat(String(width)) + } + if (parseFloat(String(height)) > -1) { + sizeAsString = 'custom-size' + params.height = parseFloat(String(height)) + } + + validateDOMAttributes({}, params) + + return { params, sizeAsString } +} + +export function prepareIcon(props: IconAllProps, context: ContextProps) { + const { + icon, + size, + width, + height, + border, + color, + inheritColor, + inherit_color, + modifier, + alt, + title, + skeleton, + className, + ...attributes + } = props + + const { sizeAsString, iconParams } = calcSize({ + icon, + size, + width, + height, + }) + + if (color) { + iconParams.color = color + } + + const label = icon ? getIconNameFromComponent(icon) : null + + // some wrapper params + // also used for code markup simulation + const wrapperParams = validateDOMAttributes(props, { + role: alt ? 'img' : 'presentation', + alt, // in case the image don't shows up (because we define the role to be img) + 'aria-label': + label && !label.includes('default') + ? label.replace(/_/g, ' ') + ' icon' + : null, // for screen readers only + title, // to show on hover, if defined + ...attributes, + }) + if (!alt && typeof wrapperParams['aria-hidden'] === 'undefined') { + wrapperParams['aria-hidden'] = true + } + if (wrapperParams['aria-hidden']) { + if ( + !wrapperParams['data-testid'] && + typeof process !== 'undefined' && + process.env.NODE_ENV === 'test' + ) { + wrapperParams['data-testid'] = wrapperParams['aria-label'] + } + delete wrapperParams['aria-label'] + } + + wrapperParams.className = classnames( + 'dnb-icon', + modifier && `dnb-icon--${modifier}`, + border && 'dnb-icon--border', + (inheritColor ?? inherit_color) !== false && 'dnb-icon--inherit-color', + sizeAsString ? `dnb-icon--${sizeAsString}` : 'dnb-icon--default', + createSkeletonClass(null, skeleton, context), + createSpacingClasses(props), + className + ) + + let iconToRender = getIcon(props) + + if (iconToRender && typeof iconToRender.defaultProps !== 'undefined') { + iconToRender = React.createElement( + iconToRender, + validateDOMAttributes( + {}, + { + color, + icon, + size, + width, + height, + } + ) + ) + } + + return { + ...props, + icon: iconToRender, + alt, + iconParams, + wrapperParams, + } +} + +export function prerenderIcon( + props: IconProps & { + listOfIcons?: Record + } +) { + const { size = null, listOfIcons = null, alt = null } = props + let { icon } = props as Omit & { icon: IconType } + + if (typeof icon === 'string' && /^data:image\//.test(icon)) { + return () => {alt + } + + if (typeof icon === 'function') { + const elem = icon() + if (React.isValidElement(elem)) { + return icon + } + return elem + } + + if (React.isValidElement(icon) || Array.isArray(icon)) { + return () => icon + } + + // For UMD/ dynamic import of icons + try { + icon = iconCase(icon) + if ( + size && + DefaultIconSizes[size] && + size !== 'basis' && + size !== 'default' && + !(parseFloat(String(size)) > 0) && + !icon.includes(size as ValidIconType) + ) { + icon = `${icon}_${size}` + } + const mod = ( + listOfIcons.dnbIcons ? listOfIcons.dnbIcons : listOfIcons + )[icon] + return mod && mod.default ? mod.default : mod + } catch (e) { + ErrorHandler(`Icon '${icon}' did not exist!`) + return null + } +} + +function getIcon(props) { + if (props.icon) { + return props.icon + } + return processChildren(props) +} + +Icon._supportsSpacingProps = true diff --git a/packages/dnb-eufemia/src/components/icon/IconHelpers.js b/packages/dnb-eufemia/src/components/icon/IconHelpers.ts similarity index 60% rename from packages/dnb-eufemia/src/components/icon/IconHelpers.js rename to packages/dnb-eufemia/src/components/icon/IconHelpers.ts index 013c0e78a99..5fcc920f472 100644 --- a/packages/dnb-eufemia/src/components/icon/IconHelpers.js +++ b/packages/dnb-eufemia/src/components/icon/IconHelpers.ts @@ -1,6 +1,8 @@ +import { IconIcon } from './Icon' + // to replace icon names -export const iconCase = (name) => - name +export const iconCase = (name: IconIcon) => + String(name) .replace(/((?!^)[A-Z])/g, '_$1') .toLowerCase() .replace(/^[0-9]/g, '$1') diff --git a/packages/dnb-eufemia/src/components/icon/IconPrimary.js b/packages/dnb-eufemia/src/components/icon/IconPrimary.ts similarity index 100% rename from packages/dnb-eufemia/src/components/icon/IconPrimary.js rename to packages/dnb-eufemia/src/components/icon/IconPrimary.ts diff --git a/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx b/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx index d5ff8a224d8..a5c4b29b0cc 100644 --- a/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx +++ b/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx @@ -6,10 +6,10 @@ import React from 'react' import { axeComponent, loadScss } from '../../../core/jest/jestSetup' import { render } from '@testing-library/react' -import Icon, { IconProps } from '../Icon' +import Icon, { IconAllProps } from '../Icon' import { question } from './test-files' -const props: IconProps = { +const props: IconAllProps = { icon: question, alt: 'question mark', 'aria-hidden': null, @@ -17,7 +17,7 @@ const props: IconProps = { describe('Icon component', () => { it('renders with props as an object', () => { - const props: IconProps = { icon: question } + const props: IconAllProps = { icon: question } render() expect(document.querySelector('.dnb-icon')).toBeInTheDocument() diff --git a/packages/dnb-eufemia/src/components/tabs/__tests__/Tabs.screenshot.test.ts b/packages/dnb-eufemia/src/components/tabs/__tests__/Tabs.screenshot.test.ts index 51f361e439d..6d77f05ced8 100644 --- a/packages/dnb-eufemia/src/components/tabs/__tests__/Tabs.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/tabs/__tests__/Tabs.screenshot.test.ts @@ -4,6 +4,7 @@ */ import { + isCI, makeScreenshot, setupPageScreenshot, } from '../../../core/jest/jestSetupScreenshots' @@ -95,6 +96,7 @@ describe.each(['ui', 'sbanken'])('Tabs for %s', (themeName) => { width: '80rem', padding: '0 2rem 4rem 2rem', }, + waitAfterSimulate: isCI ? 100 : 0, // ensure the buttons are "hidden", so give time for a slow CI selector: '[data-visual-test="tabs-tablist-scrollable"]', }) expect(screenshot).toMatchImageSnapshot() diff --git a/packages/dnb-eufemia/src/shared/Context.tsx b/packages/dnb-eufemia/src/shared/Context.tsx index 48fb37aa60a..47f592bcf69 100644 --- a/packages/dnb-eufemia/src/shared/Context.tsx +++ b/packages/dnb-eufemia/src/shared/Context.tsx @@ -47,6 +47,8 @@ import type { NumberFormatCurrency } from '../components/NumberFormat' import type { ProgressIndicatorProps } from '../components/progress-indicator/types' import type { FormStatusProps } from '../components/FormStatus' import type { LogoProps } from '../components/Logo' +import type { IconProps } from '../components/Icon' +import type { IconPrimaryProps } from '../components/IconPrimary' import type { FormElementProps } from './helpers/filterValidProps' import type { ThemeProps } from './Theme' @@ -87,6 +89,8 @@ export type ContextComponents = { ProgressIndicator?: Partial FormStatus?: Partial Logo?: Partial + Icon?: Partial + IconPrimary?: Partial // -- TODO: Not converted yet -- NumberFormat?: Record From bb4bccccec1a6a78ce86172840fb3e14ee81dbc3 Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 13 Jun 2024 17:10:35 +0200 Subject: [PATCH 09/16] chore(Icon): adds test for icon provided is FC using hooks (#3699) --- .../src/components/icon/__tests__/Icon.test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx b/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx index a5c4b29b0cc..55304c24f83 100644 --- a/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx +++ b/packages/dnb-eufemia/src/components/icon/__tests__/Icon.test.tsx @@ -122,6 +122,23 @@ describe('Icon component', () => { ).toBe('custom-data-testid-value') }) + it('should work when icon property is provided a functional component with a hook', () => { + const FunctionalComponentWithHookIcon = () => { + const [title] = React.useState('banana') + + return ( + + {title} + + + ) + } + render( + + ) + expect(document.querySelector('svg title').textContent).toBe('banana') + }) + it('should validate with ARIA rules', async () => { const Comp = render() expect(await axeComponent(Comp)).toHaveNoViolations() From 7849cbac776b14bf0250843f160b593367457692 Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 13 Jun 2024 17:26:17 +0200 Subject: [PATCH 10/16] docs(Tabs): improve property docs (#3665) --- .../docs/uilib/components/tabs/properties.mdx | 9 ++++- .../src/components/tabs/TabsDocs.ts | 37 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/properties.mdx index 9be43180930..3b44969533f 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/tabs/properties.mdx @@ -3,12 +3,19 @@ showTabs: true --- import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' -import { TabsProperties } from '@dnb/eufemia/src/components/tabs/TabsDocs' +import { + TabsProperties, + TabsDataObject, +} from '@dnb/eufemia/src/components/tabs/TabsDocs' ## Properties +## Data object + + + ## Key The key can be a string or a number. diff --git a/packages/dnb-eufemia/src/components/tabs/TabsDocs.ts b/packages/dnb-eufemia/src/components/tabs/TabsDocs.ts index 87970d3a674..7cbfd36b318 100644 --- a/packages/dnb-eufemia/src/components/tabs/TabsDocs.ts +++ b/packages/dnb-eufemia/src/components/tabs/TabsDocs.ts @@ -44,12 +44,9 @@ export const TabsProperties: PropertiesTableProps = { type: 'React.ReactNode', status: 'optional', }, - data: { - doc: "defines the data structure to load as a JSON. e.g. `[{title: '...', content: 'Current tab', key: '...', hash: '...'}]`", - type: [ - 'string', - '{title: string | React.ReactNode, key: string | number, selected?: boolean, disabled?: boolean}', - ], + '[data](/uilib/components/tabs/properties/#data-object)': { + doc: 'defines the data structure to load as an object.', + type: 'object', status: 'required', }, children: { @@ -104,6 +101,34 @@ export const TabsProperties: PropertiesTableProps = { }, } +export const TabsDataObject: PropertiesTableProps = { + title: { + doc: 'The title of the tab.', + type: ['string', 'React.ReactNode'], + status: 'required', + }, + key: { + doc: 'The unique key of the tab.', + type: ['string', 'number'], + status: 'required', + }, + content: { + doc: 'The content of the tab.', + type: 'React.ReactNode', + status: 'optional', + }, + selected: { + doc: 'If set to `true`, the tab will be selected.', + type: 'boolean', + status: 'optional', + }, + disabled: { + doc: 'If set to `true`, the tab will be disabled.', + type: 'boolean', + status: 'optional', + }, +} + export const TabsEvents: PropertiesTableProps = { on_change: { doc: '(preferred) this event gets triggered once the tab changes its selected key. Returns `{ key, selected_key, focus_key, event }`.', From edd6214e79c153f5557b9c6c7554c0d5afb03056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Fri, 14 Jun 2024 10:11:04 +0200 Subject: [PATCH 11/16] feat(forms): add `activeWhen` prop to Wizard.Step (#3705) Co-authored-by: Joakim Bjerknes --- .../extensions/forms/Wizard/Step/Examples.tsx | 63 +++- .../extensions/forms/Wizard/Step/demos.mdx | 8 +- .../uilib/extensions/forms/Wizard/info.mdx | 4 + .../docs/uilib/extensions/forms/changelog.mdx | 8 + .../__tests__/EditAndViewContainer.test.tsx | 6 +- .../forms/Form/Visibility/Visibility.tsx | 2 +- .../Visibility/__tests__/Visibility.test.tsx | 2 +- .../__tests__/useVisibility.test.tsx | 298 ++++++++++++++++++ .../forms/Form/Visibility/useVisibility.tsx | 168 +++++----- .../Wizard/Container/WizardContainer.tsx | 25 +- .../__tests__/WizardContainer.test.tsx | 165 +++++++++- .../forms/Wizard/Context/WizardContext.ts | 2 + .../src/extensions/forms/Wizard/Step/Step.tsx | 15 +- .../extensions/forms/Wizard/Step/StepDocs.ts | 5 + .../forms/Wizard/stories/Wizard.stories.tsx | 63 +++- 15 files changed, 719 insertions(+), 115 deletions(-) create mode 100644 packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/useVisibility.test.tsx diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx index ab9e12fd5b7..b8a26a094eb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx @@ -1,6 +1,67 @@ import { Card, P } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Form, Value, Wizard } from '@dnb/eufemia/src/extensions/forms' +import { + Field, + Form, + Value, + Wizard, +} from '@dnb/eufemia/src/extensions/forms' + +export const DynamicSteps = () => { + return ( + + + + + Step 1 + + + + + Step 2 + + + + + ['group-1', 'group-2'].includes(value), + }} + > + Step 3 + + + + + Step 4 + + + + + + + + + + + ) +} export const EditButton = () => { return ( diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/demos.mdx index ef14e046347..7c896fc9f43 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/demos.mdx @@ -2,6 +2,12 @@ showTabs: true --- +import * as Examples from './Examples' + ## Demos -See [WizardContainer demo section](/uilib/extensions/forms/Wizard/Container/demos) for examples. +See [WizardContainer demo section](/uilib/extensions/forms/Wizard/Container/demos) for more examples. + +### Dynamic steps + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/info.mdx index 6c1c2b5ebd3..c7a1df2ab8b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/info.mdx @@ -24,6 +24,10 @@ render( +## Dynamic steps support + +You can use the `Wizard.Step` component to create dynamic steps. The `active` and `activeWhen` props can be used to enable or disable a step based on the current data. Here is a [demo of a dynamic step](/uilib/extensions/forms/Wizard/Step/). + ## Summary step A Wizard needs a summary step at the end. You can use the `Wizard.Step` component for that, including the [Value.SummaryList](/uilib/extensions/forms/Value/SummaryList/) component: diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx index e3e2111b59b..aef17149056 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx @@ -13,6 +13,14 @@ breadcrumb: Change log for the Eufemia Forms extension. +## v10.36 + +- Added support for dynamic Wizard steps with the `active` and `activeWhen` prop ([Wizard.Step](/uilib/extensions/forms/Wizard/Step/)). + +## v10.35 + +- Added view and edit containers to [Form.Section](/uilib/extensions/forms/Form/Section/). + ## v10.34 - Added a first block (ChildrenWithAge) to the [list of blocks](/uilib/extensions/forms/blocks/). diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/__tests__/EditAndViewContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/__tests__/EditAndViewContainer.test.tsx index fa7d80f245d..1a5c0e75dd5 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/__tests__/EditAndViewContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/__tests__/EditAndViewContainer.test.tsx @@ -11,11 +11,11 @@ describe('EditContainer and ViewContainer', () => { render( - - Edit Content - View Content + + Edit Content + ) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx index 4d5abbe2102..2f9bc784dd8 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/Visibility.tsx @@ -12,7 +12,7 @@ import type { UseFieldProps } from '../../types' import type { DataAttributes } from '../../hooks/useFieldProps' import { FilterData } from '../../DataContext' -type VisibleWhen = +export type VisibleWhen = | { path: string hasValue: unknown diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/Visibility.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/Visibility.test.tsx index 6ddd0312a88..6c9014c7451 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/Visibility.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/__tests__/Visibility.test.tsx @@ -257,7 +257,7 @@ describe('Visibility', () => { expect(screen.getByText('Child')).toBeInTheDocument() }) - it('should not render children when withValue not matches', () => { + it('should not render children when withValue does not match', () => { render( { + describe('visibility', () => { + it('renders children when visible is true', () => { + const { result } = renderHook(() => + useVisibility({ + visible: true, + }) + ) + expect(result.current.check()).toBe(true) + }) + + it('does not render children when visible is false', () => { + const { result } = renderHook(() => + useVisibility({ + visible: false, + }) + ) + expect(result.current.check()).toBe(false) + }) + }) + + describe('pathDefined', () => { + it('renders children when target path is defined', () => { + const { result } = renderHook( + () => + useVisibility({ + pathDefined: '/isDefined', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + + it('does not render children when target path is not defined', () => { + const { result } = renderHook( + () => + useVisibility({ + pathDefined: '/notDefined', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(false) + }) + }) + + describe('pathUndefined', () => { + it('renders children when target path is defined', () => { + const { result } = renderHook( + () => + useVisibility({ + pathUndefined: '/isDefined', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(false) + }) + + it('does not render children when target path is not defined', () => { + const { result } = renderHook( + () => + useVisibility({ + pathUndefined: '/notDefined', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + }) + + describe('pathTruthy', () => { + it('renders children when target path is truthy', () => { + const { result } = renderHook( + () => + useVisibility({ + pathTruthy: '/isTruthy', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + + it('does not render children when target path is not truthy', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + pathTruthy: '/isFalsy', + }) + ).toBe(false) + }) + + it('does not render children when target path is not defined', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + pathTruthy: '/isNotDefined', + }) + ).toBe(false) + }) + }) + + describe('pathFalsy', () => { + it('renders children when target path is falsy', () => { + const { result } = renderHook( + () => + useVisibility({ + pathFalsy: '/isFalsy', + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + + it('renders children when target path is not defined', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + pathFalsy: '/isNotDefined', + }) + ).toBe(true) + }) + + it('does not render children when target path is not falsy', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + pathFalsy: '/isTruthy', + }) + ).toBe(false) + }) + }) + + describe('visibleWhen', () => { + it('should render children when hasValue matches', () => { + const { result } = renderHook( + () => + useVisibility({ + visibleWhen: { + path: '/myPath', + hasValue: 'foo', + }, + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + + it('should not render children when hasValue does not match', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + visibleWhen: { + path: '/myPath', + hasValue: 'bar', + }, + }) + ).toBe(false) + }) + + it('should not render children when path does not match', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + visibleWhen: { + path: '/nonExistingPath', + hasValue: 'foo', + }, + }) + ).toBe(false) + }) + + it('should render children when withValue matches', () => { + const { result } = renderHook( + () => + useVisibility({ + visibleWhen: { + path: '/myPath', + withValue: (value) => value === 'foo', + }, + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(true) + }) + + it('should not render children when withValue does not match', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + visibleWhen: { + path: '/myPath', + withValue: (value) => value === 'bar', + }, + }) + ).toBe(false) + }) + }) + + describe('visibleWhenNot', () => { + it('should render children when hasValue matches', () => { + const { result } = renderHook( + () => + useVisibility({ + visibleWhenNot: { + path: '/myPath', + hasValue: 'foo', + }, + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ) + expect(result.current.check()).toBe(false) + }) + + it('should not render children when hasValue does not match', () => { + const { result } = renderHook(() => useVisibility(), { + wrapper: ({ children }) => ( + {children} + ), + }) + expect( + result.current.check({ + visibleWhenNot: { + path: '/myPath', + hasValue: 'bar', + }, + }) + ).toBe(true) + }) + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx index 6ad1823aff2..0b290b3a80c 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/useVisibility.tsx @@ -1,26 +1,14 @@ -import { useCallback, useContext } from 'react' +import { useCallback, useContext, useRef } from 'react' import pointer from 'json-pointer' import DataContext from '../../DataContext/Context' import SectionContext from '../Section/SectionContext' import { Path } from '../../types' import { Props } from './Visibility' -export default function useVisibility({ - visible, - visibleWhen, - visibleWhenNot, - pathDefined, - pathUndefined, - pathTruthy, - pathFalsy, - pathTrue, - pathFalse, - pathValue, - whenValue, - inferData, - filterData, -}: Partial) { - const dataContext = useContext(DataContext) +export type { Props } + +export default function useVisibility(props?: Partial) { + const { filterDataHandler, data: originalData } = useContext(DataContext) const sectionContext = useContext(SectionContext) const sectionPath = sectionContext?.path @@ -33,81 +21,109 @@ export default function useVisibility({ [sectionPath] ) - const check = () => { - if (visible === false) { - return false - } - - const data = - (filterData && - dataContext.filterDataHandler?.(dataContext.data, filterData)) || - dataContext.data + // Forward props to the "check" method with ref to avoid infinite loop + const propsRef = useRef(props) + propsRef.current = props - if (visibleWhen || visibleWhenNot) { - if (visibleWhenNot) { - visibleWhen = visibleWhenNot + const check = useCallback( + ( + { + visible, + visibleWhen, + visibleWhenNot, + pathDefined, + pathUndefined, + pathTruthy, + pathFalsy, + pathTrue, + pathFalse, + pathValue, + whenValue, + inferData, + filterData, + }: Partial = propsRef.current + ) => { + if (visible === false) { + return false } - const hasPath = pointer.has(data, composePath(visibleWhen.path)) - if (hasPath) { - const value = pointer.get(data, composePath(visibleWhen.path)) - const withValue = visibleWhen?.['withValue'] - const result = - (withValue && withValue?.(value) === false) || - (Object.prototype.hasOwnProperty.call(visibleWhen, 'hasValue') && - visibleWhen?.['hasValue'] !== value) + const data = + (filterData && filterDataHandler?.(originalData, filterData)) || + originalData + if (visibleWhen || visibleWhenNot) { if (visibleWhenNot) { - if (!result) { + visibleWhen = visibleWhenNot + } + const hasPath = pointer.has(data, composePath(visibleWhen.path)) + if (hasPath) { + const value = pointer.get(data, composePath(visibleWhen.path)) + + const withValue = visibleWhen?.['withValue'] + const result = + (withValue && withValue?.(value) === false) || + (Object.prototype.hasOwnProperty.call( + visibleWhen, + 'hasValue' + ) && + visibleWhen?.['hasValue'] !== value) + + if (visibleWhenNot) { + if (!result) { + return false + } + } else if (result) { return false } - } else if (result) { + } else { return false } - } else { - return false } - } - if (pathDefined && !pointer.has(data, composePath(pathDefined))) { - return false - } - if (pathUndefined && pointer.has(data, composePath(pathUndefined))) { - return false - } + if (pathDefined && !pointer.has(data, composePath(pathDefined))) { + return false + } + if (pathUndefined && pointer.has(data, composePath(pathUndefined))) { + return false + } - const getValue = (path: Path) => { - if (pointer.has(data, path)) { - return pointer.get(data, path) + const getValue = (path: Path) => { + if (pointer.has(data, path)) { + return pointer.get(data, path) + } } - } - if (pathTrue && getValue(composePath(pathTrue)) !== true) { - return false - } - if (pathFalse && getValue(composePath(pathFalse)) !== false) { - return false - } - if ( - pathTruthy && - Boolean(getValue(composePath(pathTruthy))) === false - ) { - return false - } - if (pathFalsy && Boolean(getValue(composePath(pathFalsy))) === true) { - return false - } - if (inferData && !inferData(data)) { - return false - } + if (pathTrue && getValue(composePath(pathTrue)) !== true) { + return false + } + if (pathFalse && getValue(composePath(pathFalse)) !== false) { + return false + } + if ( + pathTruthy && + Boolean(getValue(composePath(pathTruthy))) === false + ) { + return false + } + if ( + pathFalsy && + Boolean(getValue(composePath(pathFalsy))) === true + ) { + return false + } + if (inferData && !inferData(data)) { + return false + } - // Deprecated can be removed in v11 - if (pathValue && getValue(composePath(pathValue)) !== whenValue) { - return false - } + // Deprecated can be removed in v11 + if (pathValue && getValue(composePath(pathValue)) !== whenValue) { + return false + } - return true - } + return true + }, + [composePath, filterDataHandler, originalData] + ) return { check } } diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/WizardContainer.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/WizardContainer.tsx index 2204e207db3..f8b42272c75 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/WizardContainer.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/WizardContainer.tsx @@ -31,6 +31,7 @@ import { } from '../../../../shared/helpers/useSharedState' import useHandleLayoutEffect from './useHandleLayoutEffect' import { ComponentProps } from '../../types' +import useVisibility from '../../Form/Visibility/useVisibility' // SSR warning fix: https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 const useLayoutEffect = @@ -264,6 +265,8 @@ function WizardContainer(props: Props) { Record React.ReactElement> >({}) + const { check } = useVisibility() + const activeIndex = activeIndexRef.current const providerValue = useMemo(() => { return { @@ -275,6 +278,7 @@ function WizardContainer(props: Props) { activeIndexRef, prerenderFieldProps, prerenderFieldPropsRef, + check, setActiveIndex, handlePrevious, handleNext, @@ -286,6 +290,7 @@ function WizardContainer(props: Props) { handlePrevious, id, prerenderFieldProps, + check, setActiveIndex, setFormError, ]) @@ -378,6 +383,7 @@ function DisplaySteps({ function IterateOverSteps({ children }) { const { + check, titlesRef, activeIndexRef, prerenderFieldProps, @@ -386,7 +392,6 @@ function IterateOverSteps({ children }) { titlesRef.current = {} let incrementIndex = -1 - let decrementIndex = -1 const childrenArray = React.Children.map(children, (child) => { if (React.isValidElement(child)) { @@ -407,13 +412,15 @@ function IterateOverSteps({ children }) { return null } - if (child.props.active === false) { - decrementIndex-- - } else { - incrementIndex++ + if ( + child.props.activeWhen && + !check({ visibleWhen: child.props.activeWhen }) + ) { + return null } - const index = - child.props.active === false ? decrementIndex : incrementIndex + + incrementIndex++ + const index = incrementIndex titlesRef.current[index] = child.props.title !== undefined @@ -450,9 +457,9 @@ function IterateOverSteps({ children }) { // Ensure we never have a higher index than the available children // else we get a white screen - if (childrenArray.length === 0) { + if (childrenArray?.length === 0) { activeIndexRef.current = 0 - } else if (childrenArray.length < activeIndexRef.current + 1) { + } else if (childrenArray?.length < activeIndexRef.current + 1) { activeIndexRef.current = childrenArray.length - 1 } diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx index 1833599ab82..a1d15236a7b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx @@ -601,9 +601,8 @@ describe('Wizard.Container', () => { expect( document.querySelectorAll('.dnb-step-indicator__item') ).toHaveLength(2) - expect( - document.querySelector('.dnb-forms-next-button') - ).toBeInTheDocument() + expect(previousButton()).toBeNull() + expect(nextButton()).toBeInTheDocument() await userEvent.click(document.querySelector('#toggleStep2')) @@ -611,18 +610,168 @@ describe('Wizard.Container', () => { expect( document.querySelectorAll('.dnb-step-indicator__item') ).toHaveLength(1) - expect( - document.querySelector('.dnb-forms-next-button') - ).not.toBeInTheDocument() + expect(previousButton()).toBeNull() + expect(nextButton()).not.toBeInTheDocument() await userEvent.click(document.querySelector('#toggleStep2')) expect( document.querySelectorAll('.dnb-step-indicator__item') ).toHaveLength(2) + expect(previousButton()).toBeNull() + expect(nextButton()).toBeInTheDocument() + }) + + it('should not render inactive steps based on paths and activeWhen', () => { + render( + + + + Step 1 + + + + Step 2 + + + + ) + + expect(output()).toHaveTextContent('Step 2') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(1) + }) + + it('should render inactive steps based on paths and activeWhen with withValue', () => { + render( + + + { + return value === 'group-1' + }, + }} + > + Step 1 + + + + Step 2 + + + + ) + + expect(output()).toHaveTextContent('Step 1') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(2) + }) + + it('should render dynamically enabled steps based on paths and activeWhen', async () => { + render( + + + + + + + + + + Step 1 + + + + + Step 2 + + + + { + return value === 'group-1' + }, + }} + > + Step 3 + + + + + ) + + expect(output()).toHaveTextContent('Step 2') + expect( + document.querySelector('.dnb-step-indicator') + ).toHaveTextContent('Steg 1 av 1') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(1) + expect(previousButton()).toBeNull() + expect(nextButton()).toBeNull() + + await userEvent.click( + document.querySelectorAll('.dnb-toggle-button button')[0] + ) + + expect(output()).toHaveTextContent('Step 1') + expect( + document.querySelector('.dnb-step-indicator') + ).toHaveTextContent('Steg 1 av 2') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(2) + expect(previousButton()).toBeNull() + expect(nextButton()).toBeInTheDocument() + + await userEvent.click( + document.querySelectorAll('.dnb-toggle-button button')[1] + ) + + expect(output()).toHaveTextContent('Step 2') + expect( + document.querySelector('.dnb-step-indicator') + ).toHaveTextContent('Steg 1 av 1') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(1) + expect(previousButton()).toBeNull() + expect(nextButton()).toBeNull() + + await userEvent.click( + document.querySelectorAll('.dnb-toggle-button button')[2] + ) + + expect(output()).toBeNull() expect( - document.querySelector('.dnb-forms-next-button') - ).toBeInTheDocument() + document.querySelector('.dnb-step-indicator') + ).toHaveTextContent('') + expect( + document.querySelectorAll('.dnb-step-indicator__item') + ).toHaveLength(0) + expect(previousButton()).toBeNull() + expect(nextButton()).toBeNull() }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Context/WizardContext.ts b/packages/dnb-eufemia/src/extensions/forms/Wizard/Context/WizardContext.ts index 850f12967c3..9df1075c648 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Context/WizardContext.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Context/WizardContext.ts @@ -1,5 +1,6 @@ import React from 'react' import { EventReturnWithStateObject } from '../../types' +import { VisibleWhen } from '../../Form/Visibility' export type OnStepChange = ( index: StepIndex, @@ -39,6 +40,7 @@ export interface WizardContextState { }?: SetActiveIndexOptions ) => void setFormError?: (error: Error) => void + check?: ({ visibleWhen }: { visibleWhen: VisibleWhen }) => boolean } const WizardContext = React.createContext( diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/Step.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/Step.tsx index 9046dce024d..5b8728354c5 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/Step.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/Step.tsx @@ -6,6 +6,7 @@ import WizardContext from '../Context/WizardContext' import Flex from '../../../../components/flex/Flex' import { convertJsxToString } from '../../../../shared/component-helper' import FieldProps from '../../Form/FieldProps' +import type { VisibleWhen } from '../../Form/Visibility' // SSR warning fix: https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 const useLayoutEffect = @@ -34,6 +35,11 @@ export type Props = ComponentProps & */ active?: boolean + /** + * Provide a `path` and a `hasValue` property with the excepted value in order to enable the step. You can alternatively provide a `withValue` function that returns a boolean. The first parameter is the value of the path. + */ + activeWhen?: VisibleWhen + /** * If set to `true`, the step will always be rendered. * For internal use only. @@ -47,12 +53,13 @@ function Step(props: Props) { title, index, active = true, + activeWhen, required, prerenderFieldProps, children, ...restProps } = props - const { activeIndex, titlesRef, stepElementRef } = + const { check, activeIndex, titlesRef, stepElementRef } = useContext(WizardContext) || {} const ariaLabel = useMemo(() => { @@ -78,7 +85,11 @@ function Step(props: Props) { return children } - if (activeIndex !== index || active === false) { + if ( + activeIndex !== index || + active === false || + (activeWhen && !check({ visibleWhen: activeWhen })) + ) { // Another step is active return <> } diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/StepDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/StepDocs.ts index bc47dfd9e47..28978394f51 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/StepDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Step/StepDocs.ts @@ -16,6 +16,11 @@ export const StepProperties: PropertiesTableProps = { type: 'boolean', status: 'optional', }, + activeWhen: { + doc: 'Provide a `path` and a `hasValue` property with the excepted value in order to enable the step. You can alternatively provide a `withValue` function that returns a boolean. The first parameter is the value of the path.', + type: 'object', + status: 'optional', + }, children: { doc: 'Contents.', type: 'React.Node', diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx index 8d92fc97fa1..913a850fc66 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx @@ -44,7 +44,16 @@ export const Basic = () => { const Step1 = () => { const { data } = Form.useData() return ( - + { + return value === '1' || value === undefined + }, + }} + > Heading Step 1 { const Step2 = () => { const { data } = Form.useData() return ( - + Heading Step 2

Contents

@@ -89,7 +102,11 @@ const Step3 = () => { const { summaryTitle } = Form.useLocale().Step return ( - + Summary

Contents

@@ -109,18 +126,38 @@ const initialData = { export const WizardDynamicSteps = () => { return ( - <> - - - - - - {/* {data?.step1 && } + + + + + + {/* {data?.step1 && } {data?.step2 && } {data?.step3 && } */} - - - + + + ) +} + +export const WizardDynamicStepsActiveWhen = () => { + return ( + + console.log('onChange', value)} + > + + + + + + + + + + + ) } From 641e5e79f22e20dc647b250be3f8ce98e7e9751f Mon Sep 17 00:00:00 2001 From: Joakim Bjerknes Date: Fri, 14 Jun 2024 10:39:31 +0200 Subject: [PATCH 12/16] fix(Autocomplete): make `input_value` react to prop change (#3706) --- .../components/autocomplete/Autocomplete.js | 1 - .../__tests__/Autocomplete.test.tsx | 12 ++++++++++++ ...to-match-autocomplete-opened-list.snap.png | Bin 43551 -> 41002 bytes ...to-match-autocomplete-opened-list.snap.png | Bin 43947 -> 41785 bytes 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js index da976ba557a..3d930d57d4c 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js @@ -403,7 +403,6 @@ class AutocompleteInstance extends React.PureComponent { if ( props.input_value !== 'initval' && - typeof state.inputValue === 'undefined' && props.input_value?.length > 0 ) { state.inputValue = props.input_value diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx index a613581594f..bea3b9cf6d0 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx @@ -3227,6 +3227,18 @@ describe('Autocomplete component', () => { expect(ref.current.getAttribute('id')).toBe('unique') expect(ref.current.tagName).toBe('INPUT') }) + + it('should change input value when prop changes', () => { + const { rerender } = render() + + const input = document.querySelector('input') + + expect(input.value).toBe('first value') + + rerender() + + expect(input.value).toBe('second value') + }) }) describe('Autocomplete markup', () => { diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png index 8360d8443e3224ff86c6c25e6397aed17450187a..98836c29dc4896016e8d7372643e221627d64b7f 100644 GIT binary patch literal 41002 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 literal 43551 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 diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-ui-have-to-match-autocomplete-opened-list.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-ui-have-to-match-autocomplete-opened-list.snap.png index 25d1d964157fa05b9fe08815061a739dfb504833..c615ffaa563e60d36d463901cd8f0517e80d90c2 100644 GIT binary patch literal 41785 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 VoVPMaIPnnp=w30@EY+~T^AFRXj_v>e literal 43947 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 Date: Fri, 14 Jun 2024 11:49:44 +0200 Subject: [PATCH 13/16] fix(Autocomplete): make inputValue update on data prop changes (#3581) --- .../components/autocomplete/Autocomplete.js | 28 ++++++++++++++++- .../__tests__/Autocomplete.test.tsx | 31 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js index 3d930d57d4c..e3d941ee4b5 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js @@ -54,6 +54,7 @@ import { parseContentTitle, getCurrentData, getCurrentIndex, + normalizeData, } from '../../fragments/drawer-list/DrawerListHelpers' export default class Autocomplete extends React.PureComponent { @@ -345,7 +346,6 @@ export default class Autocomplete extends React.PureComponent { this._id = props.id || makeUniqueId() } - render() { return ( 0 && state?.prevData?.length === 0) { + let selectedItem = state.selected_item + + if (props.default_value) { + selectedItem = props.default_value + } + + if ( + !props.default_value && + props.value && + props.value !== 'initval' + ) { + selectedItem = props.value + } + + const currentData = getCurrentData( + selectedItem, + normalizeData(props.data) + ) + + state.inputValue = parseContentTitle(currentData, { + separator: ' ', + preferSelectedValue: true, + }) + } + if (props.data !== state.prevData) { state.updateData(props.data) state.prevData = props.data diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx index bea3b9cf6d0..2a3f72d2819 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx @@ -1508,6 +1508,37 @@ describe('Autocomplete component', () => { expect(input.value).toBe('Kontonummer: 987654321') }) + it('should update input value when data prop goes from emtpy to unempty and value is given', async () => { + const { rerender } = render() + + const input = document.querySelector('.dnb-input__input') + + expect(input).not.toHaveValue() + + rerender( + + ) + + expect(input).toHaveValue('Sparekonto') + }) + describe('should have correct values on input blur', () => { it('when no selection is made and "keep_value" and "keep_value_and_selection" is false', async () => { const on_change = jest.fn() From 6045e42ee4c61059da8159ec1bc7a94dae6f8971 Mon Sep 17 00:00:00 2001 From: Snorre Kim Date: Fri, 14 Jun 2024 12:39:36 +0200 Subject: [PATCH 14/16] feat(Table): refactoring of types, documentation, and accordion file structure (#3683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Table, TableTr, TableTd * added `TableDocs` properties * removed unused `variant` prop * fixed some prop types * added typing to `TableContext` ### Accordion * moved internal accordion files to separate folder `./table-accordion` * combined `TableAccordionTd` and `TableAccordionTr` files in to `TableAccordionContent` to share identical logic * renamed `TableAccordionTd` to `TableAccordionContentSingle` * renamed `TableAccordionTr` to `TableAccordionContentRow` * added typing to `TableAccordionContext` ### Other * `PropertiesTable` now treats values `null` the same as `undefined` --------- Co-authored-by: Tobias Høegh --- .../docs/uilib/components/table/events.mdx | 16 +- .../uilib/components/table/properties.mdx | 42 ++-- .../src/shared/parts/PropertiesTable.tsx | 2 +- .../HeightAnimationInstance.ts | 2 +- .../height-animation/useHeightAnimation.tsx | 13 +- .../src/components/table/Table.tsx | 2 +- .../src/components/table/TableAccordionTd.tsx | 95 --------- .../src/components/table/TableAccordionTr.tsx | 89 -------- .../src/components/table/TableContext.tsx | 13 +- .../src/components/table/TableDocs.ts | 199 ++++++++++++++++++ .../src/components/table/TableTd.tsx | 6 +- .../src/components/table/TableTr.tsx | 26 +-- .../table/__tests__/TableAccordion.test.tsx | 4 +- .../table/__tests__/TableTd.test.tsx | 4 +- .../table-accordion/TableAccordionContent.tsx | 157 ++++++++++++++ .../table-accordion/TableAccordionContext.tsx | 22 ++ .../TableAccordionHead.tsx} | 62 +++--- .../useTableAnimationHandler.tsx | 10 +- 18 files changed, 480 insertions(+), 284 deletions(-) delete mode 100644 packages/dnb-eufemia/src/components/table/TableAccordionTd.tsx delete mode 100644 packages/dnb-eufemia/src/components/table/TableAccordionTr.tsx create mode 100644 packages/dnb-eufemia/src/components/table/TableDocs.ts create mode 100644 packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContent.tsx create mode 100644 packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContext.tsx rename packages/dnb-eufemia/src/components/table/{TableAccordion.tsx => table-accordion/TableAccordionHead.tsx} (82%) rename packages/dnb-eufemia/src/components/table/{ => table-accordion}/useTableAnimationHandler.tsx (84%) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/table/events.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/table/events.mdx index 8a9af4070e3..48c8a450392 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/table/events.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/table/events.mdx @@ -2,12 +2,18 @@ showTabs: true --- +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { + TableEventProperties, + TrEventProperties, +} from '@dnb/eufemia/src/components/table/TableDocs' + +## Table events + + + ## Table Row `` events Table Row `` events are a part of the accordion feature and needs to be enabled with the `accordion` property on the main Table. -| Events | Description | -| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `onClick` | _(optional)_ will emit when user clicks/expands the table row. Returns a native click. | -| `onOpened` | _(optional)_ Will emit when table row is expanded. Returns an object with the table row as the target: `{ target }`. | -| `onClosed` | _(optional)_ Will emit when table row is closed (after it was open). Returns an object with the table row as the target: `{ target }`. | + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx index 02ab1450ac9..876313584e5 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/table/properties.mdx @@ -2,46 +2,28 @@ showTabs: true --- +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { + TableProperties, + TrProperties, + ThProperties, + TdProperties, +} from '@dnb/eufemia/src/components/table/TableDocs' + ## Properties ### `` -| Properties | Description | -| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `accordion` | _(optional)_ set to `true` if you have one or more rows that contains an accordion content. Defaults to `false`. | -| `collapseAllHandleRef` | _(optional)_ ref handle to collapse all expanded accordion rows. Send in a ref and use `.current()` to collapse all rows. Defaults to `undefined`. | -| `border` | _(optional)_ use `true` to show borders between table data cells. Defaults to `false`. | -| `outline` | _(optional)_ use `true` to show a outline border around the table. Defaults to `false`. | -| `sticky` | _(optional)_ use `true` to enable a sticky Table header. Or use `css-position` to enable the CSS based scroll behavior. Defaults to `false`. | -| `stickyOffset` | _(optional)_ defines the offset (top) in `rem` from where the header should start to stick. You may define your app header height here, if you have a sticky header on your page. Defaults to `0`. | -| `size` | _(optional)_ spacing size inside the table header and data. Options: `small` \| `medium` \| `large` \. Defaults to `large`. | -| `fixed` | _(optional)_ if set to `true`, the table will behave with a fixed table layout, using: `table-layout: fixed;`. Use e.g. CSS `width: 40%` on a table column to define the width. Defaults to `false`. | -| `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. | -| ~~`variant`~~ (not implemented yet) | _(coming)_ defines the style variant of the table. Options: `basis` . Defaults to `generic`. | -| [Space](/uilib/layout/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | + ### Table Row `` -| Properties | Description | -| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `variant` | _(optional)_ defines the variant of the current row. If set to either `odd` or `even`, the next row one will continue with the opposite. Defaults to automatic. | -| `noWrap` | _(optional)_ if set to `true`, the inherited header text will not wrap to new lines. Defaults to `false`. | -| `expanded` | _(optional)_ use `true` to render the `` initially as expanded. Defaults to `false`. | -| `disabled` | _(optional)_ use `true` to disable the `` to be accessible as an interactive element. Defaults to `false`. | -| `noAnimation` | _(optional)_ use `true` to disable the expand/collapse animation. Defaults to `false`. | + ### Table Header ` - - - ) -} diff --git a/packages/dnb-eufemia/src/components/table/TableAccordionTr.tsx b/packages/dnb-eufemia/src/components/table/TableAccordionTr.tsx deleted file mode 100644 index 3ff56d199ed..00000000000 --- a/packages/dnb-eufemia/src/components/table/TableAccordionTr.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react' -import classnames from 'classnames' -import useTableAnimationHandler from './useTableAnimationHandler' -import { TableContext } from './TableContext' -import Td from './TableTd' - -export type TableAccordionTrProps = { - /** - * Set to true to expanded the content on initial render - */ - expanded?: boolean - - /** - * Set to true to skip animation - * Default: false - */ - noAnimation?: boolean -} - -export default function TableAccordionTr( - componentProps: TableAccordionTrProps & - React.TableHTMLAttributes -) { - const { - expanded = null, - noAnimation = null, - className, - children, - style, - ...props - } = componentProps - - const allProps = React.useContext(TableContext)?.allProps - const innerRef = React.useRef(null) - const trRef = React.useRef(null) - - const { - ariaLive, - isInDOM, - isAnimating, - isVisibleParallax, - firstPaintStyle, - } = useTableAnimationHandler({ - innerRef, - trRef, - expanded, - noAnimation, - }) - - const expandColumn = ( - - ) - - return ( - - {allProps.accordionChevronPlacement !== 'end' && expandColumn} - {isInDOM && children} - {allProps.accordionChevronPlacement === 'end' && expandColumn} - - ) -} diff --git a/packages/dnb-eufemia/src/components/table/TableContext.tsx b/packages/dnb-eufemia/src/components/table/TableContext.tsx index 59c6c84ab87..1e7f9cf38aa 100644 --- a/packages/dnb-eufemia/src/components/table/TableContext.tsx +++ b/packages/dnb-eufemia/src/components/table/TableContext.tsx @@ -4,7 +4,16 @@ */ import React from 'react' +import type { Translation } from '../../shared/Context' +import type { TableAllProps } from './Table' -export const TableContext = React.createContext(null) +type TableContextProps = { + trCountRef: React.MutableRefObject<{ + count: number + }> + rerenderAlias: Record + collapseTrCallbacks: React.MutableRefObject<(() => void)[]> + allProps: TableAllProps & Translation['Table'] +} -export const TableAccordionContext = React.createContext(null) +export const TableContext = React.createContext(null) diff --git a/packages/dnb-eufemia/src/components/table/TableDocs.ts b/packages/dnb-eufemia/src/components/table/TableDocs.ts new file mode 100644 index 00000000000..c70d849bb03 --- /dev/null +++ b/packages/dnb-eufemia/src/components/table/TableDocs.ts @@ -0,0 +1,199 @@ +import { PropertiesTableProps } from '../../shared/types' + +export const TableProperties: PropertiesTableProps = { + accordion: { + doc: 'Set to true if you have one or more rows that contains an accordion content.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + accordionChevronPlacement: { + doc: 'Defines where the chevron will be placed.', + type: [`'start'`, `'end'`], + defaultValue: `'start'`, + status: 'optional', + }, + border: { + doc: 'Use `true` to show borders between table data cells.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + outline: { + doc: 'Use `true` to show an outline border around the table', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + sticky: { + doc: "Use `true` to enable a sticky Table header. Or use `'css-position'` to enable the CSS based scroll behavior.", + type: ['boolean', `'css-position'`], + defaultValue: 'false', + status: 'optional', + }, + stickyOffset: { + doc: 'Defines the offset (top) in `rem` from where the header should start to stick. You may define your app header height here, if you have a sticky header on your page.', + type: ['string', 'number'], + defaultValue: 'false', + status: 'optional', + }, + size: { + doc: 'Spacing size inside the table header and data.', + type: [`'large'`, `'medium'`, `'small'`], + defaultValue: `'large'`, + status: 'optional', + }, + fixed: { + doc: 'If set to `true`, the table will behave with a fixed table layout, using: `table-layout: fixed;`. Use e.g. CSS `width: 40%` on a table column to define the width.', + type: 'boolean', + defaultValue: 'null', + status: 'optional', + }, + children: { + doc: 'The content of the component.', + type: 'React.ReactNode', + status: 'required', + }, + className: { + doc: 'Custom className on the component root', + type: 'string', + defaultValue: 'undefined', + status: 'optional', + }, + skeleton: { + doc: 'If set to `true`, an overlaying skeleton with animation will be shown.', + type: 'boolean', + defaultValue: 'undefined', + status: 'optional', + }, + '[Space](/uilib/layout/space/properties)': { + doc: 'Spacing properties like `top` or `bottom` are supported.', + type: ['string', 'object'], + status: 'optional', + }, +} + +export const TableEventProperties: PropertiesTableProps = { + collapseAllHandleRef: { + doc: 'ref handle to collapse all expanded accordion rows. Send in a ref and use `.current()` to collapse all rows.', + type: 'React.MutableRefObject<() => void>', + defaultValue: 'undefined', + status: 'optional', + }, +} + +export const TrProperties: PropertiesTableProps = { + /** + * The variant of the tr + */ + variant: { + doc: 'Override the automatic variant of the current row. The next row one will continue with the opposite.', + type: [`'even'`, `'odd'`], + defaultValue: 'undefined', + status: 'optional', + }, + noWrap: { + doc: 'if set to `true`, the inherited header text will not wrap to new lines.', + type: 'boolean', + defaultValue: 'true', + status: 'optional', + }, + expanded: { + doc: 'use `true` to render the `` initially as expanded.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + disabled: { + doc: 'use `true` to disable the `` to be accessible as an interactive element.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + noAnimation: { + doc: 'use `true` to disable the expand/collapse animation.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + children: { + doc: 'The content of the component.', + type: 'React.ReactNode', + status: 'required', + }, +} + +export const TrEventProperties: PropertiesTableProps = { + onClick: { + doc: 'will emit when user clicks/expands the table row. Returns a native click.', + type: '(event) => void', + defaultValue: 'undefined', + status: 'optional', + }, + onOpened: { + doc: 'Will emit when table row is expanded. Returns an object with the table row as the target: `{ target }`.', + type: '({ target }) => void', + defaultValue: 'undefined', + status: 'optional', + }, + onClosed: { + doc: 'Will emit when table row is closed (after it was open). Returns an object with the table row as the target: `{ target }`.', + type: '({ target }) => void', + defaultValue: 'undefined', + status: 'optional', + }, +} + +export const ThProperties: PropertiesTableProps = { + sortable: { + doc: 'Defines the table header as sortable if set to `true` (ascending).', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + active: { + doc: 'Defines the sortable column as the current active (ascending).', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + reversed: { + doc: 'Defines the sortable column as in reversed order (descending).', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + noWrap: { + doc: 'If set to `true`, the header text will not wrap to new lines.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + children: { + doc: 'The content of the component.', + type: 'React.ReactNode', + defaultValue: 'undefined', + status: 'optional', + }, +} + +export const TdProperties: PropertiesTableProps = { + noSpacing: { + doc: 'If set to `true`, no padding will be added.', + type: 'boolean', + defaultValue: 'false', + status: 'optional', + }, + spacing: { + doc: 'Set to `horizontal` for padding on left and right side.', + type: `'horizontal'`, + defaultValue: 'undefined', + status: 'optional', + }, + children: { + doc: 'The content of the component.', + type: 'React.ReactNode', + defaultValue: 'undefined', + status: 'optional', + }, +} diff --git a/packages/dnb-eufemia/src/components/table/TableTd.tsx b/packages/dnb-eufemia/src/components/table/TableTd.tsx index 115fbba705d..ba454a81ab8 100644 --- a/packages/dnb-eufemia/src/components/table/TableTd.tsx +++ b/packages/dnb-eufemia/src/components/table/TableTd.tsx @@ -1,6 +1,6 @@ import React from 'react' import classnames from 'classnames' -import TableAccordionTd from './TableAccordionTd' +import { TableAccordionContentSingle } from './table-accordion/TableAccordionContent' export type TableTdProps = { /** @@ -11,7 +11,7 @@ export type TableTdProps = { /** * Set to `horizontal` for padding on left and right side - * Default: false + * Default: undefined */ spacing?: 'horizontal' @@ -45,4 +45,4 @@ export default function Td( ) } -Td.AccordionContent = TableAccordionTd +Td.AccordionContent = TableAccordionContentSingle diff --git a/packages/dnb-eufemia/src/components/table/TableTr.tsx b/packages/dnb-eufemia/src/components/table/TableTr.tsx index b0ee80136d7..803535b3c25 100644 --- a/packages/dnb-eufemia/src/components/table/TableTr.tsx +++ b/packages/dnb-eufemia/src/components/table/TableTr.tsx @@ -1,8 +1,8 @@ import React from 'react' import classnames from 'classnames' -import { TableAccordion } from './TableAccordion' +import { TableAccordionHead } from './table-accordion/TableAccordionHead' +import { TableAccordionContentRow } from './table-accordion/TableAccordionContent' import { TableContext } from './TableContext' -import TableAccordionTr from './TableAccordionTr' export type TableTrProps = { /** @@ -41,19 +41,19 @@ export type TableTrProps = { * Will emit when user clicks/expands the table row * Is part of the accordion feature and needs to be enabled with `accordion` prop in main Table */ - onClick?: (event?: React.SyntheticEvent) => void + onClick?: (event: React.SyntheticEvent) => void /** * Will emit when table row is expanded * Is part of the accordion feature and needs to be enabled with `accordion` prop in main Table */ - onOpened?: (event?: React.SyntheticEvent) => void + onOpened?: ({ target }: { target: HTMLTableRowElement }) => void /** * Will emit when table row is closed (after it was open) * Is part of the accordion feature and needs to be enabled with `accordion` prop in main Table */ - onClosed?: (event?: React.SyntheticEvent) => void + onClosed?: ({ target }: { target: HTMLTableRowElement }) => void /** * The content of the component. @@ -87,7 +87,7 @@ export default function Tr( const tableContext = React.useContext(TableContext) if (tableContext?.allProps?.accordion) { return ( - { (attr) => attr.name ) - expect(attributes).toEqual(['colspan', 'class']) + expect(attributes).toEqual(['class', 'colspan']) expect(Array.from(element.classList)).toContain('dnb-table__td') expect(element.getAttribute('colspan')).toBe('2') }) @@ -180,7 +180,7 @@ describe('TableAccordion', () => { (attr) => attr.name ) - expect(attributes).toEqual(['role', 'colspan', 'class']) + expect(attributes).toEqual(['role', 'class', 'colspan']) expect(Array.from(element.classList)).toContain('dnb-table__td') expect(element.getAttribute('colspan')).toBe('3') expect(element.getAttribute('role')).toBe('cell') diff --git a/packages/dnb-eufemia/src/components/table/__tests__/TableTd.test.tsx b/packages/dnb-eufemia/src/components/table/__tests__/TableTd.test.tsx index 699509affc3..d64d198cb03 100644 --- a/packages/dnb-eufemia/src/components/table/__tests__/TableTd.test.tsx +++ b/packages/dnb-eufemia/src/components/table/__tests__/TableTd.test.tsx @@ -1,7 +1,7 @@ import React from 'react' import { render } from '@testing-library/react' import TableTd, { TableTdProps } from '../TableTd' -import TableAccordionTd from '../TableAccordionTd' +import { TableAccordionContentSingle } from '../table-accordion/TableAccordionContent' describe('TableTd', () => { it('renders with props as an object', () => { @@ -123,6 +123,6 @@ describe('TableTd', () => { }) it('should have Td.AccordionContent', () => { - expect(TableTd.AccordionContent).toBe(TableAccordionTd) + expect(TableTd.AccordionContent).toBe(TableAccordionContentSingle) }) }) diff --git a/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContent.tsx b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContent.tsx new file mode 100644 index 00000000000..5454d687264 --- /dev/null +++ b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContent.tsx @@ -0,0 +1,157 @@ +import React from 'react' +import classnames from 'classnames' +import useTableAnimationHandler from './useTableAnimationHandler' +import { TableContext } from '../TableContext' +import { TableAccordionContext } from './TableAccordionContext' + +type TableAccordionContentProps = { + /** Set to true to expanded the content on initial render */ + expanded?: boolean + /** Set to true to skip animation */ + noAnimation?: boolean + /** Overwrite the internal collected colSpan (add +1) */ + colSpan?: number + variant: 'row' | 'single' +} & React.TableHTMLAttributes + +export type TableAccordionContentRowProps = Omit< + TableAccordionContentProps, + 'variant' | 'colSpan' +> + +export type TableAccordionContentSingleProps = Omit< + TableAccordionContentProps, + 'variant' +> + +function TableAccordionContent( + componentProps: TableAccordionContentProps +) { + const { + variant, + expanded = null, + noAnimation = null, + className, + children, + colSpan, + style, + ...props + } = componentProps + + const tableContextAllProps = React.useContext(TableContext)?.allProps + const innerRef = React.useRef(null) + const trRef = React.useRef(null) + const { + ariaLive, + isInDOM, + isAnimating, + isVisibleParallax, + firstPaintStyle, + } = useTableAnimationHandler({ + innerRef, + trRef, + expanded, + noAnimation, + }) + + const chevronTdProps = { + ariaLive, + isInDOM, + accordionMoreContentSR: tableContextAllProps?.accordionMoreContentSR, + } + + return ( + + {variant === 'row' && ( + <> + {tableContextAllProps?.accordionChevronPlacement !== 'end' && ( + + )} + {isInDOM && children} + {tableContextAllProps?.accordionChevronPlacement === 'end' && ( + + )} + + )} + {variant === 'single' && ( + + {isInDOM && ( +
+
+ {children} +
+
+ )} +
+ )} +
+ ) +} + +const ChevronTd = ({ + children = undefined, + colSpan = undefined, + ariaLive, + isInDOM, + accordionMoreContentSR, +}) => ( + +) + +export function TableAccordionContentRow( + props: TableAccordionContentRowProps +) { + return +} + +export function TableAccordionContentSingle({ + colSpan = 100, + ...props +}: TableAccordionContentSingleProps) { + const tableAccordionContext = React.useContext(TableAccordionContext) + + return ( + + ) +} diff --git a/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContext.tsx b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContext.tsx new file mode 100644 index 00000000000..cae515ae8b0 --- /dev/null +++ b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionContext.tsx @@ -0,0 +1,22 @@ +/** + * Web TableContext Context + * + */ + +import React from 'react' +import type { TableTrProps } from '../TableTr' + +type TableAccordionContextProps = { + toggleOpenTr: ( + event: React.SyntheticEvent, + allowInteractiveElement?: boolean + ) => void + trIsOpen: boolean + countTds: number + noAnimation: TableTrProps['noAnimation'] + onOpened: TableTrProps['onOpened'] + onClosed: TableTrProps['onClosed'] +} + +export const TableAccordionContext = + React.createContext(null) diff --git a/packages/dnb-eufemia/src/components/table/TableAccordion.tsx b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionHead.tsx similarity index 82% rename from packages/dnb-eufemia/src/components/table/TableAccordion.tsx rename to packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionHead.tsx index 7c8c9e9dae3..15300aa2647 100644 --- a/packages/dnb-eufemia/src/components/table/TableAccordion.tsx +++ b/packages/dnb-eufemia/src/components/table/table-accordion/TableAccordionHead.tsx @@ -1,32 +1,31 @@ import React, { useEffect } from 'react' import classnames from 'classnames' -import Button from '../button/Button' -import IconPrimary from '../icon/IconPrimary' -import Th from './TableTh' -import Td from './TableTd' -import { TableAccordionContext, TableContext } from './TableContext' import keycode from 'keycode' -import { hasSelectedText } from '../../shared/helpers' - -import TableAccordionTd from './TableAccordionTd' -import TableAccordionTr from './TableAccordionTr' -import type { TableAccordionTdProps } from './TableAccordionTd' -import type { TableAccordionTrProps } from './TableAccordionTr' -import type { TableTrProps } from './TableTr' - -type TableAccordionContentProps = - | TableAccordionTdProps - | TableAccordionTrProps - -type TableAccordionProps = { +import { hasSelectedText } from '../../../shared/helpers' +import Button from '../../button/Button' +import IconPrimary from '../../icon/IconPrimary' +import Th from '../TableTh' +import Td from '../TableTd' +import { TableContext } from '../TableContext' +import { TableAccordionContext } from './TableAccordionContext' +import { + TableAccordionContentSingle, + TableAccordionContentRow, +} from './TableAccordionContent' + +import type { + TableAccordionContentSingleProps, + TableAccordionContentRowProps, +} from './TableAccordionContent' +import type { TableTrProps } from '../TableTr' + +export type TableAccordionHeadProps = { + /** The row number */ count: number -} +} & TableTrProps & + React.TableHTMLAttributes -export function TableAccordion( - allProps: TableAccordionProps & - TableTrProps & - React.TableHTMLAttributes -) { +export function TableAccordionHead(allProps: TableAccordionHeadProps) { const { children, className, @@ -72,7 +71,9 @@ export function TableAccordion( (element: React.ReactElement) => { return isAccordionElement(element) } - ) as React.ReactElement[] + ) as React.ReactElement< + TableAccordionContentSingleProps | TableAccordionContentRowProps + >[] const hasAccordionContent = accordionContent.length !== 0 && @@ -136,7 +137,6 @@ export function TableAccordion( trIsOpen, noAnimation, countTds, - hasAccordionContent, onOpened, onClosed, }} @@ -220,9 +220,10 @@ export function TableAccordion( export function TableAccordionToggleButton() { const tableAccordionContext = React.useContext(TableAccordionContext) - const allProps = React.useContext(TableContext)?.allProps + const tableContextAllProps = React.useContext(TableContext)?.allProps const iconSize = - allProps.size === 'medium' || allProps.size === 'small' + tableContextAllProps?.size === 'medium' || + tableContextAllProps?.size === 'small' ? 'basis' : 'medium' @@ -232,7 +233,7 @@ export function TableAccordionToggleButton() {
` -| Properties | Description | -| ---------- | ---------------------------------------------------------------------------------------------------- | -| `sortable` | _(optional)_ defines the table header as sortable if set to `true` (ascending). Defaults to `false`. | -| `active` | _(optional)_ defines the sortable column as the current active (ascending). Defaults to `false`. | -| `reversed` | _(optional)_ defines the sortable column as in reversed order (descending). Defaults to `false`. | -| `noWrap` | _(optional)_ if set to `true`, the header text will not wrap to new lines. Defaults to `false`. | + ### Table Data `` -| Properties | Description | -| ----------- | ----------------------------------------------------------------------------------------- | -| `noSpacing` | _(optional)_ if set to `true`, no padding will be added. Defaults to `false`. | -| `spacing` | _(optional)_ set to `horizontal` for padding on left and right side. Defaults to `false`. | + diff --git a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx index 14d81e7e591..930615e24ec 100644 --- a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx +++ b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx @@ -48,7 +48,7 @@ export const FormattedCode = ({ case 'value': { style.color = children.startsWith(`'`) ? colorString - : children === 'undefined' + : children === 'undefined' || children === 'null' ? colorUndefined : colorValue // falls through diff --git a/packages/dnb-eufemia/src/components/height-animation/HeightAnimationInstance.ts b/packages/dnb-eufemia/src/components/height-animation/HeightAnimationInstance.ts index e8515be8c55..4fbe8650f75 100644 --- a/packages/dnb-eufemia/src/components/height-animation/HeightAnimationInstance.ts +++ b/packages/dnb-eufemia/src/components/height-animation/HeightAnimationInstance.ts @@ -48,7 +48,7 @@ export default class HeightAnimation { visibility: 'hidden', opacity: '0', // prevents before/after elements to be visible height: 'auto', - } + } as const constructor(opts: HeightAnimationOptions = {}) { this.isInBrowser = typeof window !== 'undefined' diff --git a/packages/dnb-eufemia/src/components/height-animation/useHeightAnimation.tsx b/packages/dnb-eufemia/src/components/height-animation/useHeightAnimation.tsx index 19a5ecd6cf5..6214711101d 100644 --- a/packages/dnb-eufemia/src/components/height-animation/useHeightAnimation.tsx +++ b/packages/dnb-eufemia/src/components/height-animation/useHeightAnimation.tsx @@ -151,11 +151,14 @@ export function useHeightAnimation( * Returns the first paint style, to be used for the initial render, * to avoid flickering. */ - const firstPaintStyle = ((open && - !isVisible && - !isAnimating && - instRef.current?.firstPaintStyle) || - {}) as React.CSSProperties + const firstPaintStyle: + | typeof instRef.current.firstPaintStyle + | Record = + (open && + !isVisible && + !isAnimating && + instRef.current?.firstPaintStyle) || + {} const isInDOM = open || isVisible return { diff --git a/packages/dnb-eufemia/src/components/table/Table.tsx b/packages/dnb-eufemia/src/components/table/Table.tsx index 4fb53841da9..db42f10c6d2 100644 --- a/packages/dnb-eufemia/src/components/table/Table.tsx +++ b/packages/dnb-eufemia/src/components/table/Table.tsx @@ -45,7 +45,7 @@ export type TableProps = { size?: TableSizes /** - * The style variant of the component. + * The style variant of the component. Currently not implemented. * Default: generic. */ variant?: TableVariants diff --git a/packages/dnb-eufemia/src/components/table/TableAccordionTd.tsx b/packages/dnb-eufemia/src/components/table/TableAccordionTd.tsx deleted file mode 100644 index 4f9db8d5c27..00000000000 --- a/packages/dnb-eufemia/src/components/table/TableAccordionTd.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import classnames from 'classnames' -import useTableAnimationHandler from './useTableAnimationHandler' -import { TableAccordionContext, TableContext } from './TableContext' -import { TableAccordionTrProps } from './TableAccordionTr' - -export type TableAccordionTdProps = { - /** - * Overwrite the internal collected colSpan (add +1) - */ - colSpan?: number -} - -export default function TableAccordionTd( - componentProps: TableAccordionTdProps & - TableAccordionTrProps & - React.TableHTMLAttributes -) { - const { - expanded = null, - noAnimation = null, - className, - children, - colSpan = 100, - style, - ...props - } = componentProps - - const allProps = React.useContext(TableContext)?.allProps - const tableAccordionContext = React.useContext(TableAccordionContext) - const innerRef = React.useRef(null) - const trRef = React.useRef(null) - const { - ariaLive, - isInDOM, - isAnimating, - isVisibleParallax, - firstPaintStyle, - } = useTableAnimationHandler({ - innerRef, - trRef, - expanded, - noAnimation, - }) - const countTds = tableAccordionContext?.countTds || colSpan - - return ( -
- - - {isInDOM && ariaLive ? allProps?.accordionMoreContentSR : null} - - -
+ {children} + + + {isInDOM && ariaLive ? accordionMoreContentSR : null} + + +