diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/Examples.js b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/Examples.js index 560a0d0b475..66c03302ed7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/Examples.js +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/Examples.js @@ -55,7 +55,7 @@ export const FormStatusWithWarn = () => ( ) -export const FormSetDefaultInput = () => ( +export const FormStatusInput = () => ( {() => /* jsx */ ` ( ) +export const FormStatusAnimation = () => ( + + {() => /* jsx */ ` +const ToggleAnimation = () => { + const [status, setStatus] = React.useState(null) + const toggleStatus = () => { + setStatus((s) => (!s ? 'You have to fill in this field' : null)) + } + return ( + + + + Toggle + + + ) +} +render() +`} + +) + export const FormStatusCustom = () => ( {() => /* jsx */ ` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/demos.md b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/demos.md index 3ae91aa1c63..999b6ea7fea 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/demos.md +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/demos.md @@ -9,7 +9,8 @@ FormStatusDefault, FormStatusWithInfo, FormStatusWithStretch, FormStatusWithWarn, -FormSetDefaultInput, +FormStatusInput, +FormStatusAnimation, FormStatusCustom, FormStatusLarge, FormStatusWithIcons, @@ -35,15 +36,19 @@ NB: The inner text gets a max width of 47rem to ensure we not exceed 70 characte -### A form status, used by the Input Component +### A FormStatus, used by the Input Component - + -### A form status, with a custom styled content +### FormStatus Animation details + + + +### A FormStatus, with a custom styled content -### A form status with plain text/HTML +### A FormStatus with plain text/HTML diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/properties.md b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/properties.md index 5c121ed5a7a..48932537999 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/properties.md +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/form-status/properties.md @@ -17,6 +17,8 @@ redirect_from: | `icon_size` | _(optional)_ the icon size of the icon shows. Defaults to `medium`. | | `variant` | _(optional)_ as of now, there is the `flat` and the `outlined` variant. Defaults to `flat`. | | `stretch` | _(optional)_ if set to `true`, then the FormStatus will be 100% in available `width`. **NB:** Only use this on independent status messages. | +| `show` | _(optional)_ provide `false` if you want to animate the visibility. Defaults to `true`. | +| `no_animation` | _(optional)_ use `true` to omit the animation on content visibility. Defaults to `false`. | | `global_status_id` | _(optional)_ the `status_id` used for the target [GlobalStatus](/uilib/components/global-status). | | `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. | | [Space](/uilib/components/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | diff --git a/packages/dnb-eufemia-sandbox/stories/components/FormStatus.js b/packages/dnb-eufemia-sandbox/stories/components/FormStatus.js index 1345ffb2b8d..bc3f151c655 100644 --- a/packages/dnb-eufemia-sandbox/stories/components/FormStatus.js +++ b/packages/dnb-eufemia-sandbox/stories/components/FormStatus.js @@ -16,7 +16,9 @@ import { Modal, Switch, Button, + ToggleButton, Space, + HelpButton, } from '@dnb/eufemia/src/components' import { Link } from '@dnb/eufemia/src/elements' @@ -48,7 +50,6 @@ const SmallWidth = styled(Input)` // ) export const FormStatuseSandbox = () => { - const [showError, setShowError] = useState(false) return ( @@ -137,6 +138,19 @@ export const FormStatuseSandbox = () => { + + ) +} + +export const ToggleAnimation = () => { + const [status, setStatus] = React.useState(null) + const toggleStatus = () => { + setStatus((s) => (!s ? 'You have to fill in this field' : null)) + } + const [showError, setShowError] = useState(false) + + return ( + { Modal Content} /> - Modal Content - + @@ -1097,7 +1144,7 @@ exports[`Autocomplete markup have to match snapshot 1`] = ` "skeleton": "skeleton", "skip_portal": true, "status": "status", - "status_animation": "status_animation", + "status_no_animation": "status_no_animation", "status_state": "error", "stretch": "stretch", "submit_button_icon": "submit_button_icon", @@ -1329,7 +1376,20 @@ exports[`Autocomplete scss have to match snapshot 1`] = ` --form-status-radius: 0.25rem; } .dnb-form-status { - display: flex; } + display: flex; + opacity: 1; + transition: height 400ms cubic-bezier(0.42, 0, 0, 1), opacity 400ms cubic-bezier(0.42, 0, 0, 1), margin 400ms cubic-bezier(0.42, 0, 0, 1), padding 400ms cubic-bezier(0.42, 0, 0, 1); } + .dnb-form-status--hidden { + will-change: height, opacity, margin, padding; + width: 0; + height: 0; + opacity: 0; } + .dnb-form-status--is-animating { + overflow: hidden; + width: auto; } + .dnb-form-status--disappear, .dnb-form-status--hidden { + margin: 0 !important; + padding: 0 !important; } .dnb-form-status__shell { display: flex; justify-content: flex-start; @@ -1366,16 +1426,9 @@ exports[`Autocomplete scss have to match snapshot 1`] = ` max-width: 47rem; } .dnb-form-status[hidden] { display: none; } - .dnb-form-status__animation--fade-in { - overflow: hidden; - max-height: 0; - animation: form-status-fade-in 2s ease-out 400ms forwards; } - -@keyframes form-status-fade-in { - from { - max-height: 0; } - to { - max-height: calc(var(--input-height) * 8); } } + .dnb-form-status--no-animation, + .dnb-form-status html[data-visual-test] { + transition-duration: 1ms !important; } @media screen and (-ms-high-contrast: none) { .dnb-form-status__shell > .dnb-icon { border-width: 1px; } } @@ -1859,6 +1912,18 @@ legend.dnb-form-label { /* IE needs this to stay centered */ } .dnb-input__submit-button__button { border-radius: 0 var(--input-border-radius) var(--input-border-radius) 0; } + .dnb-input > .dnb-form-label { + line-height: var(--line-height-basis); } + @media screen and (max-width: 40em) { + .dnb-input { + flex-wrap: wrap; } + .dnb-input > .dnb-form-label { + margin-bottom: 0.5rem; + margin-top: 0.5rem; } } + .dnb-input:not(.dnb-input--vertical)[class*='__status'] { + align-items: flex-start; } + .dnb-input:not(.dnb-input--vertical)[class*='__status'] > .dnb-form-label { + margin-top: 0.25rem; } .dnb-input--small { line-height: var(--input-height--small); } .dnb-input--small .dnb-input__shell, @@ -2002,18 +2067,6 @@ legend.dnb-form-label { .dnb-input--icon-size-medium.dnb-input--icon-position-right.dnb-input--has-icon .dnb-input__input, .dnb-input--icon-size-medium.dnb-input--icon-position-right.dnb-input--has-icon .dnb-input__placeholder { padding-right: 3rem; } - .dnb-input > .dnb-form-label { - line-height: var(--line-height-basis); } - @media screen and (max-width: 40em) { - .dnb-input { - flex-wrap: wrap; } - .dnb-input > .dnb-form-label { - margin-bottom: 0.5rem; - margin-top: 0.5rem; } } - .dnb-input[class*='__status'] { - align-items: flex-start; } - .dnb-input[class*='__status'] > .dnb-form-label { - margin-top: 0.25rem; } @media screen and (max-width: 40em) { .dnb-responsive-component .dnb-input { display: flex; @@ -2406,9 +2459,9 @@ legend.dnb-form-label { display: flex; flex-direction: column; align-items: flex-start; } - .dnb-autocomplete[class*='__status'] { + .dnb-autocomplete:not(.dnb-autocomplete--vertical)[class*='__status'] { align-items: flex-start; } - .dnb-autocomplete[class*='__status'] > .dnb-form-label { + .dnb-autocomplete:not(.dnb-autocomplete--vertical)[class*='__status'] > .dnb-form-label { margin-top: 0.25rem; } @media screen and (max-width: 40em) { .dnb-responsive-component .dnb-autocomplete { diff --git a/packages/dnb-eufemia/src/components/autocomplete/style/_autocomplete.scss b/packages/dnb-eufemia/src/components/autocomplete/style/_autocomplete.scss index 3b75749cc86..da6a7bce6db 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/style/_autocomplete.scss +++ b/packages/dnb-eufemia/src/components/autocomplete/style/_autocomplete.scss @@ -198,7 +198,7 @@ align-items: flex-start; } - &[class*='__status'] { + &:not(&--vertical)[class*='__status'] { align-items: flex-start; > .dnb-form-label { // vertical align the current font diff --git a/packages/dnb-eufemia/src/components/button/Button.js b/packages/dnb-eufemia/src/components/button/Button.js index a4b3ddc6482..22dd399b879 100644 --- a/packages/dnb-eufemia/src/components/button/Button.js +++ b/packages/dnb-eufemia/src/components/button/Button.js @@ -60,7 +60,10 @@ export const buttonPropTypes = { PropTypes.node, ]), status_state: PropTypes.string, - status_animation: PropTypes.string, + status_no_animation: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), global_status_id: PropTypes.string, id: PropTypes.string, class: PropTypes.string, @@ -133,7 +136,7 @@ export default class Button extends React.PureComponent { tooltip: null, status: null, status_state: 'error', - status_animation: null, + status_no_animation: null, global_status_id: null, inner_ref: null, @@ -207,7 +210,7 @@ export default class Button extends React.PureComponent { tooltip, status, status_state, - status_animation, + status_no_animation, global_status_id, id, // eslint-disable-line disabled, @@ -338,19 +341,20 @@ export default class Button extends React.PureComponent { skeleton={isTrue(skeleton)} /> + {this.state.afterContent} - {showStatus && ( - - )} + + {tooltip && this._ref && ( + `; @@ -406,7 +430,7 @@ exports[`Button component have to match href="..." snapshot 1`] = ` size={null} skeleton="skeleton" status={null} - status_animation="status_animation" + status_no_animation="status_no_animation" status_state="status_state" stretch="stretch" text={null} @@ -729,7 +753,7 @@ exports[`Button component have to match href="..." snapshot 1`] = ` size={null} skeleton={false} status={null} - status_animation="status_animation" + status_no_animation="status_no_animation" status_state="status_state" stretch="stretch" text={null} @@ -799,6 +823,30 @@ exports[`Button component have to match href="..." snapshot 1`] = ` + `; @@ -1519,7 +1567,20 @@ exports[`Button scss have to match snapshot 1`] = ` --form-status-radius: 0.25rem; } .dnb-form-status { - display: flex; } + display: flex; + opacity: 1; + transition: height 400ms cubic-bezier(0.42, 0, 0, 1), opacity 400ms cubic-bezier(0.42, 0, 0, 1), margin 400ms cubic-bezier(0.42, 0, 0, 1), padding 400ms cubic-bezier(0.42, 0, 0, 1); } + .dnb-form-status--hidden { + will-change: height, opacity, margin, padding; + width: 0; + height: 0; + opacity: 0; } + .dnb-form-status--is-animating { + overflow: hidden; + width: auto; } + .dnb-form-status--disappear, .dnb-form-status--hidden { + margin: 0 !important; + padding: 0 !important; } .dnb-form-status__shell { display: flex; justify-content: flex-start; @@ -1556,16 +1617,9 @@ exports[`Button scss have to match snapshot 1`] = ` max-width: 47rem; } .dnb-form-status[hidden] { display: none; } - .dnb-form-status__animation--fade-in { - overflow: hidden; - max-height: 0; - animation: form-status-fade-in 2s ease-out 400ms forwards; } - -@keyframes form-status-fade-in { - from { - max-height: 0; } - to { - max-height: calc(var(--input-height) * 8); } } + .dnb-form-status--no-animation, + .dnb-form-status html[data-visual-test] { + transition-duration: 1ms !important; } @media screen and (-ms-high-contrast: none) { .dnb-form-status__shell > .dnb-icon { border-width: 1px; } } diff --git a/packages/dnb-eufemia/src/components/checkbox/Checkbox.js b/packages/dnb-eufemia/src/components/checkbox/Checkbox.js index 117588d1af6..5d25918e43e 100644 --- a/packages/dnb-eufemia/src/components/checkbox/Checkbox.js +++ b/packages/dnb-eufemia/src/components/checkbox/Checkbox.js @@ -60,7 +60,10 @@ export default class Checkbox extends React.PureComponent { PropTypes.node, ]), status_state: PropTypes.string, - status_animation: PropTypes.string, + status_no_animation: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), global_status_id: PropTypes.string, suffix: PropTypes.oneOfType([ PropTypes.string, @@ -96,7 +99,7 @@ export default class Checkbox extends React.PureComponent { size: null, status: null, status_state: 'error', - status_animation: null, + status_no_animation: null, global_status_id: null, suffix: null, value: null, @@ -194,7 +197,7 @@ export default class Checkbox extends React.PureComponent { value, status, status_state, - status_animation, + status_no_animation, global_status_id, suffix, size, @@ -263,8 +266,9 @@ export default class Checkbox extends React.PureComponent { // also used for code markup simulation validateDOMAttributes(this.props, inputParams) - const statusComp = showStatus && ( + const statusComp = ( ) diff --git a/packages/dnb-eufemia/src/components/checkbox/__tests__/__snapshots__/Checkbox.test.js.snap b/packages/dnb-eufemia/src/components/checkbox/__tests__/__snapshots__/Checkbox.test.js.snap index 69ecbe377dd..913c79ff6bd 100644 --- a/packages/dnb-eufemia/src/components/checkbox/__tests__/__snapshots__/Checkbox.test.js.snap +++ b/packages/dnb-eufemia/src/components/checkbox/__tests__/__snapshots__/Checkbox.test.js.snap @@ -26,7 +26,7 @@ exports[`Checkbox component have to match snapshot 1`] = ` "size": "'default'", "skeleton": "skeleton", "status": "status", - "status_animation": "status_animation", + "status_no_animation": "status_no_animation", "status_state": "status_state", "suffix": "suffix", "title": "title", @@ -61,7 +61,7 @@ exports[`Checkbox component have to match snapshot 1`] = ` size={null} skeleton={null} status={null} - status_animation={null} + status_no_animation={null} status_state="error" suffix={null} title={null} @@ -107,6 +107,30 @@ exports[`Checkbox component have to match snapshot 1`] = ` className="dnb-alignment-helper" /> + @@ -408,7 +432,20 @@ legend.dnb-form-label { --form-status-radius: 0.25rem; } .dnb-form-status { - display: flex; } + display: flex; + opacity: 1; + transition: height 400ms cubic-bezier(0.42, 0, 0, 1), opacity 400ms cubic-bezier(0.42, 0, 0, 1), margin 400ms cubic-bezier(0.42, 0, 0, 1), padding 400ms cubic-bezier(0.42, 0, 0, 1); } + .dnb-form-status--hidden { + will-change: height, opacity, margin, padding; + width: 0; + height: 0; + opacity: 0; } + .dnb-form-status--is-animating { + overflow: hidden; + width: auto; } + .dnb-form-status--disappear, .dnb-form-status--hidden { + margin: 0 !important; + padding: 0 !important; } .dnb-form-status__shell { display: flex; justify-content: flex-start; @@ -445,16 +482,9 @@ legend.dnb-form-label { max-width: 47rem; } .dnb-form-status[hidden] { display: none; } - .dnb-form-status__animation--fade-in { - overflow: hidden; - max-height: 0; - animation: form-status-fade-in 2s ease-out 400ms forwards; } - -@keyframes form-status-fade-in { - from { - max-height: 0; } - to { - max-height: calc(var(--input-height) * 8); } } + .dnb-form-status--no-animation, + .dnb-form-status html[data-visual-test] { + transition-duration: 1ms !important; } @media screen and (-ms-high-contrast: none) { .dnb-form-status__shell > .dnb-icon { border-width: 1px; } } diff --git a/packages/dnb-eufemia/src/components/date-picker/DatePicker.js b/packages/dnb-eufemia/src/components/date-picker/DatePicker.js index d457282ef3a..b1d32f8f088 100644 --- a/packages/dnb-eufemia/src/components/date-picker/DatePicker.js +++ b/packages/dnb-eufemia/src/components/date-picker/DatePicker.js @@ -155,7 +155,10 @@ export default class DatePicker extends React.PureComponent { PropTypes.node, ]), status_state: PropTypes.string, - status_animation: PropTypes.string, + status_no_animation: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), global_status_id: PropTypes.string, suffix: PropTypes.oneOfType([ PropTypes.string, @@ -231,7 +234,7 @@ export default class DatePicker extends React.PureComponent { skeleton: null, status: null, status_state: 'error', - status_animation: null, + status_no_animation: null, global_status_id: null, suffix: null, opened: false, @@ -507,7 +510,7 @@ export default class DatePicker extends React.PureComponent { skeleton, status, status_state, - status_animation, + status_no_animation, global_status_id, suffix, mask_order, @@ -625,19 +628,20 @@ export default class DatePicker extends React.PureComponent { {...pickerParams} > - {showStatus && ( - - )} + + +