diff --git a/components/atom/icon/demo/articles/ArticleDesign.js b/components/atom/icon/demo/articles/ArticleDesign.js index 060d3d5b9..ece4a1675 100644 --- a/components/atom/icon/demo/articles/ArticleDesign.js +++ b/components/atom/icon/demo/articles/ArticleDesign.js @@ -1,8 +1,9 @@ import {Fragment} from 'react' -import {IconBrandGithub, IconBrandGithubFilled} from '@tabler/icons-react' import PropTypes from 'prop-types' +import {IconBrandGithub, IconBrandGithubFilled} from '@tabler/icons-react' + import {Article, Cell, Code, Grid, H2, Label, Paragraph} from '@s-ui/documentation-library' import AtomIcon, {ATOM_ICON_COLORS, ATOM_ICON_DESIGNS} from '../../src/index.js' diff --git a/components/atom/icon/src/index.js b/components/atom/icon/src/index.js index 768a89979..09cbe40fa 100644 --- a/components/atom/icon/src/index.js +++ b/components/atom/icon/src/index.js @@ -5,11 +5,11 @@ import Icon from './Icon.js' import LazyIcon from './LazyIcon.js' import { ATOM_ICON_COLORS, + ATOM_ICON_DESIGNS, ATOM_ICON_RENDERS, ATOM_ICON_SHAPES, ATOM_ICON_SIZES, - BASE_CLASS, - ATOM_ICON_DESIGNS + BASE_CLASS } from './settings.js' const AtomIcon = ({ diff --git a/components/atom/progressBar/src/ProgressBarCircle/Indicator.js b/components/atom/progressBar/src/ProgressBarCircle/Indicator.js index 206dba6e5..dff07ccb7 100644 --- a/components/atom/progressBar/src/ProgressBarCircle/Indicator.js +++ b/components/atom/progressBar/src/ProgressBarCircle/Indicator.js @@ -1,7 +1,7 @@ import cx from 'classnames' import PropTypes from 'prop-types' -import {SIZES, STATUS, COLORS} from '../settings.js' +import {COLORS, SIZES, STATUS} from '../settings.js' import {INDICATOR_CLASS_NAME} from './settings.js' const Indicator = ({percentage, status, errorIcon, size, children, color}) => { diff --git a/components/atom/progressBar/src/ProgressBarLine/Indicator/index.js b/components/atom/progressBar/src/ProgressBarLine/Indicator/index.js index accd8afe1..babf94cc2 100644 --- a/components/atom/progressBar/src/ProgressBarLine/Indicator/index.js +++ b/components/atom/progressBar/src/ProgressBarLine/Indicator/index.js @@ -1,8 +1,8 @@ import cx from 'classnames' import PropTypes from 'prop-types' -import {CLASS_INDICATOR} from './settings.js' import {COLORS} from '../../settings.js' +import {CLASS_INDICATOR} from './settings.js' const Indicator = ({indicatorBottom, indicatorTotal, percentage, color}) => { return ( diff --git a/components/molecule/tabs/demo/Articles/ArticleKeepTabsContentMounted.js b/components/molecule/tabs/demo/Articles/ArticleKeepTabsContentMounted.js new file mode 100644 index 000000000..de6bb41e5 --- /dev/null +++ b/components/molecule/tabs/demo/Articles/ArticleKeepTabsContentMounted.js @@ -0,0 +1,131 @@ +import {useState} from 'react' + +import MoleculeTabs, {MoleculeTab} from 'components/molecule/tabs/src/index.js' +import PropTypes from 'prop-types' + +import {Article, Code, H2, H3, Paragraph} from '@s-ui/documentation-library' + +import {CLASS_DEMO_CONTENT_TAB} from '../config.js' + +const TabContentWithState = ({title, number}) => { + const [count, setCount] = useState(0) + const [text, setText] = useState('') + + return ( +
+

+ {title} {number} +

+
+ + +
+
+ + setText(e.target.value)} + placeholder="Type something..." + style={{padding: '8px', width: '100%', maxWidth: '300px'}} + /> +
+
+ ) +} + +TabContentWithState.displayName = 'TabContentWithState' + +TabContentWithState.propTypes = { + title: PropTypes.string, + number: PropTypes.number +} + +const ArticleKeepTabsContentMounted = ({className}) => { + return ( +
+

Keep Tabs Content Mounted

+ + The keepTabsContentMounted (boolean) prop controls whether tab contents remain mounted in the DOM + when switching between tabs. This is useful when you want to preserve the internal state of each tab's content. + + +

Without keepTabsContentMounted (default: false)

+ + By default, when you switch tabs, the previous tab's content is unmounted and its state is lost. Try + incrementing the counter or typing text, then switch tabs and come back - the state will be reset. + + + {Array(3) + .fill(true) + .map((v, index) => ( + Tab {index + 1}} + numTab={index + 1} + active={index + 1 === 1} + > + + + ))} + + +

+ With keepTabsContentMounted={'{'}true{'}'} +

+ + When enabled, all tab contents remain mounted (but hidden) in the DOM. The state is preserved when switching + between tabs. Try incrementing the counter or typing text, then switch tabs and come back - the state will be + maintained. + + + {Array(3) + .fill(true) + .map((v, index) => ( + Tab {index + 1}} + numTab={index + 1} + active={index + 1 === 1} + > + + + ))} + + + + Use cases: This prop is particularly useful when tab contents have: + + + + Note: When keepTabsContentMounted is true, inactive tabs are hidden using + visibility techniques (visually hidden) but remain in the DOM, which may have performance implications if tabs + contain heavy content. + +
+ ) +} + +ArticleKeepTabsContentMounted.displayName = 'ArticleKeepTabsContentMounted' + +ArticleKeepTabsContentMounted.propTypes = { + className: PropTypes.string +} + +export default ArticleKeepTabsContentMounted diff --git a/components/molecule/tabs/demo/index.js b/components/molecule/tabs/demo/index.js index b89907860..7cae83f9f 100644 --- a/components/molecule/tabs/demo/index.js +++ b/components/molecule/tabs/demo/index.js @@ -3,9 +3,10 @@ import {H1, Paragraph} from '@s-ui/documentation-library' import ArticleActiveTabs from './Articles/ArticleActiveTabs.js' import ArticleDefault from './Articles/ArticleDefault.js' import ArticleIconsCounters from './Articles/ArticleIconsCounters.js' +import ArticleKeepTabsContentMounted from './Articles/ArticleKeepTabsContentMounted.js' +import ArticleTabsJustifyContent from './Articles/ArticleTabsJustifyContent.js' import ArticleType from './Articles/ArticleType.js' import ArticleVariant from './Articles/ArticleVariant.js' -import ArticleTabsJustifyContent from './Articles/ArticleTabsJustifyContent.js' import {CLASS_DEMO_SECTION} from './config.js' import './index.scss' @@ -19,6 +20,8 @@ const Demo = () => {
+ +

diff --git a/components/molecule/tabs/package.json b/components/molecule/tabs/package.json index e714e9236..1bffc229f 100644 --- a/components/molecule/tabs/package.json +++ b/components/molecule/tabs/package.json @@ -20,7 +20,8 @@ }, "dependencies": { "@s-ui/component-dependencies": "1", - "@s-ui/react-hooks": "1" + "@s-ui/react-hooks": "1", + "@s-ui/react-primitive-visually-hidden": "1" }, "repository": { "type": "git", diff --git a/components/molecule/tabs/src/components/MoleculeTabs.js b/components/molecule/tabs/src/components/MoleculeTabs.js index 95ded26b6..395dccbff 100644 --- a/components/molecule/tabs/src/components/MoleculeTabs.js +++ b/components/molecule/tabs/src/components/MoleculeTabs.js @@ -4,8 +4,39 @@ import cx from 'classnames' import PropTypes from 'prop-types' import useOnScreen from '@s-ui/react-hooks/lib/useOnScreen' +import PrimitiveVisuallyHidden from '@s-ui/react-primitive-visually-hidden' + +import {BASE_CLASS, CLASS_CONTENT, CLASS_SCROLLER, TABS_JUSTIFY_CONTENT, TYPES, VARIANTS} from '../config.js' + +const Wrapper = ({isActive, keepTabsContentMounted, children, id, numTab, ...props}) => { + if (isActive) { + return ( +
+ {children} +
+ ) + } else if (keepTabsContentMounted) { + return ( + +
+ {children} +
+
+ ) + } else { + return null + } +} -import {BASE_CLASS, CLASS_CONTENT, CLASS_SCROLLER, TYPES, VARIANTS, TABS_JUSTIFY_CONTENT} from '../config.js' +Wrapper.displayName = 'Wrapper' + +Wrapper.propTypes = { + isActive: PropTypes.bool, + keepTabsContentMounted: PropTypes.bool, + children: PropTypes.node, + id: PropTypes.string, + numTab: PropTypes.number +} const MoleculeTabs = ({ autoScrollIntoView = true, @@ -14,7 +45,8 @@ const MoleculeTabs = ({ onChange, type = TYPES.HORIZONTAL, variant = VARIANTS.CLASSIC, - tabsJustifyContent = TABS_JUSTIFY_CONTENT.FLEX_START + tabsJustifyContent = TABS_JUSTIFY_CONTENT.FLEX_START, + keepTabsContentMounted = false }) => { const className = cx(BASE_CLASS, { [`${BASE_CLASS}--${variant}`]: variant, @@ -108,20 +140,17 @@ const MoleculeTabs = ({ }) }) - const activeTabContent = childrenArray.reduce((activeContent, child) => { + const tabContent = childrenArray.reduce((activeContent, child) => { if (child) { const {children: childrenChild, active, numTab} = child.props - - if (active) { - return ( -
- {childrenChild} -
- ) - } + activeContent.push( + + {childrenChild} + + ) } return activeContent - }, null) + }, []) return (
@@ -134,7 +163,7 @@ const MoleculeTabs = ({ > {extendedChildren} - {activeTabContent} + {tabContent}
) } @@ -161,7 +190,10 @@ MoleculeTabs.propTypes = { type: PropTypes.oneOf(Object.values(TYPES)), /** justify the tabs elements in its area following the element declared **/ - tabsJustifyContent: PropTypes.oneOf(Object.values(TABS_JUSTIFY_CONTENT)) + tabsJustifyContent: PropTypes.oneOf(Object.values(TABS_JUSTIFY_CONTENT)), + + /** keep the inactive tabs content rendered **/ + keepTabsContentMounted: PropTypes.bool } export default MoleculeTabs diff --git a/components/molecule/tabs/src/index.js b/components/molecule/tabs/src/index.js index 45c9b74a2..de18e36da 100644 --- a/components/molecule/tabs/src/index.js +++ b/components/molecule/tabs/src/index.js @@ -6,7 +6,7 @@ import useControlledState from '@s-ui/react-hooks/lib/useControlledState' import MoleculeTab from './components/MoleculeTab.js' import MoleculeTabs from './components/MoleculeTabs.js' -import {TYPES, VARIANTS, TABS_JUSTIFY_CONTENT} from './config.js' +import {TABS_JUSTIFY_CONTENT, TYPES, VARIANTS} from './config.js' const MoleculeTabsWithStateActive = ({ children, @@ -14,6 +14,7 @@ const MoleculeTabsWithStateActive = ({ defaultActiveTabIndex: defaultActiveTabIndexProp = 1, tabsJustifyContent, onChange, + keepTabsContentMounted, ...props }) => { const [activeTab, setActiveTab] = useControlledState(activeTabIndexProp, defaultActiveTabIndexProp) @@ -24,7 +25,12 @@ const MoleculeTabsWithStateActive = ({ } return ( - + {Children.toArray(children) .filter(Boolean) .map((child, index) => cloneElement(child, {active: activeTab === index + 1}))} @@ -46,7 +52,9 @@ MoleculeTabsWithStateActive.propTypes = { /** onChange callback */ onChange: PropTypes.func, - tabsJustifyContent: PropTypes.oneOfType(Object.values(TABS_JUSTIFY_CONTENT)) + tabsJustifyContent: PropTypes.oneOfType(Object.values(TABS_JUSTIFY_CONTENT)), + + keepTabsContentMounted: PropTypes.bool } export { diff --git a/components/molecule/tabs/src/index.scss b/components/molecule/tabs/src/index.scss index 81b21b544..7f074843e 100644 --- a/components/molecule/tabs/src/index.scss +++ b/components/molecule/tabs/src/index.scss @@ -1,4 +1,5 @@ @import '~@s-ui/theme/lib/index'; +@import '~@s-ui/react-primitive-visually-hidden/lib/index'; @import './styles/settings.scss'; @import './styles/index.scss'; diff --git a/package-lock.json b/package-lock.json index 01f295a03..d65138187 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1674,11 +1674,12 @@ }, "components/molecule/tabs": { "name": "@s-ui/react-molecule-tabs", - "version": "2.41.0", + "version": "2.42.0", "license": "MIT", "dependencies": { "@s-ui/component-dependencies": "1", - "@s-ui/react-hooks": "1" + "@s-ui/react-hooks": "1", + "@s-ui/react-primitive-visually-hidden": "1" } }, "components/molecule/tabs/demo": { @@ -9321,9 +9322,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", - "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "version": "2.11.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", + "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -9712,9 +9713,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001813", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001813.tgz", - "integrity": "sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==", + "version": "1.0.30001814", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", + "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", "funding": [ { "type": "opencollective", @@ -11496,9 +11497,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.442", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.442.tgz", - "integrity": "sha512-najZYZ3+ZpjN1z3VOsrBiv5ej18vQgfV2lvEmxWzfmKrk4bdm3Owseyud4yGU6DfFn9pyunoAHDDyM4KmP78Ew==", + "version": "1.5.443", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.443.tgz", + "integrity": "sha512-TDJG36L9A3CWWwZ97HKaE+Iz1sW80pNp6sAU0owzCIV3Zc9eVyRqZK8Vz6NKuMIU8WjP0pIkoqCa0VRf/yFD0Q==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -13818,9 +13819,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", "funding": [ { "type": "individual", @@ -21743,9 +21744,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0"