Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/atom/icon/demo/articles/ArticleDesign.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
4 changes: 2 additions & 2 deletions components/atom/icon/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -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}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={CLASS_DEMO_CONTENT_TAB}
style={{
borderTop: 0,
borderLeft: '1px solid #e6e6e6',
borderRight: '1px solid #e6e6e6',
borderBottom: '1px solid #e6e6e6',
padding: '16px'
}}
>
<h3>
{title} {number}
</h3>
<div style={{marginBottom: '16px'}}>
<label style={{display: 'block', marginBottom: '8px'}}>Counter: {count}</label>
<button onClick={() => setCount(count + 1)} style={{padding: '8px 16px'}}>
Increment
</button>
</div>
<div>
<label style={{display: 'block', marginBottom: '8px'}}>Text input:</label>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Type something..."
style={{padding: '8px', width: '100%', maxWidth: '300px'}}
/>
</div>
</div>
)
}

TabContentWithState.displayName = 'TabContentWithState'

TabContentWithState.propTypes = {
title: PropTypes.string,
number: PropTypes.number
}

const ArticleKeepTabsContentMounted = ({className}) => {
return (
<Article className={className}>
<H2>Keep Tabs Content Mounted</H2>
<Paragraph>
The <Code>keepTabsContentMounted</Code> (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.
</Paragraph>

<H3>Without keepTabsContentMounted (default: false)</H3>
<Paragraph>
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.
</Paragraph>
<MoleculeTabs>
{Array(3)
.fill(true)
.map((v, index) => (
<MoleculeTab
key={index + 1}
label={<span style={{padding: '0 8px'}}>Tab {index + 1}</span>}
numTab={index + 1}
active={index + 1 === 1}
>
<TabContentWithState title="Content" number={index + 1} />
</MoleculeTab>
))}
</MoleculeTabs>

<H3>
With keepTabsContentMounted={'{'}true{'}'}
</H3>
<Paragraph>
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.
</Paragraph>
<MoleculeTabs keepTabsContentMounted>
{Array(3)
.fill(true)
.map((v, index) => (
<MoleculeTab
key={index + 1}
label={<span style={{padding: '0 8px'}}>Tab {index + 1}</span>}
numTab={index + 1}
active={index + 1 === 1}
>
<TabContentWithState title="Content" number={index + 1} />
</MoleculeTab>
))}
</MoleculeTabs>

<Paragraph>
<strong>Use cases:</strong> This prop is particularly useful when tab contents have:
</Paragraph>
<ul>
<li>Form inputs that users fill across multiple tabs</li>
<li>Interactive components with internal state (counters, toggles, etc.)</li>
<li>Media players or timers that should continue running</li>
<li>Complex components with expensive initialization</li>
</ul>
<Paragraph>
<strong>Note:</strong> When <Code>keepTabsContentMounted</Code> 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.
</Paragraph>
</Article>
)
}

ArticleKeepTabsContentMounted.displayName = 'ArticleKeepTabsContentMounted'

ArticleKeepTabsContentMounted.propTypes = {
className: PropTypes.string
}

export default ArticleKeepTabsContentMounted
5 changes: 4 additions & 1 deletion components/molecule/tabs/demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -19,6 +20,8 @@ const Demo = () => {
</Paragraph>
<ArticleDefault className={CLASS_DEMO_SECTION} />
<br />
<ArticleKeepTabsContentMounted className={CLASS_DEMO_SECTION} />
<br />
<ArticleActiveTabs className={CLASS_DEMO_SECTION} />
<br />
<ArticleType className={CLASS_DEMO_SECTION} />
Expand Down
3 changes: 2 additions & 1 deletion components/molecule/tabs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
60 changes: 46 additions & 14 deletions components/molecule/tabs/src/components/MoleculeTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={CLASS_CONTENT} id={`${id}-${numTab}`} role="tabpanel" {...props}>
{children}
</div>
)
} else if (keepTabsContentMounted) {
return (
<PrimitiveVisuallyHidden>
<div className={CLASS_CONTENT} id={`${id}-${numTab}`} role="tabpanel" style={{display: 'none'}} {...props}>
{children}
</div>
</PrimitiveVisuallyHidden>
)
} 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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 (
<div className={CLASS_CONTENT} id={`${id}-${numTab}`} role="tabpanel">
{childrenChild}
</div>
)
}
activeContent.push(
<Wrapper id={id} keepTabsContentMounted={keepTabsContentMounted} isActive={active} numTab={numTab}>
{childrenChild}
</Wrapper>
)
}
return activeContent
}, null)
}, [])

return (
<div className={className}>
Expand All @@ -134,7 +163,7 @@ const MoleculeTabs = ({
>
{extendedChildren}
</ul>
{activeTabContent}
{tabContent}
</div>
)
}
Expand All @@ -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
14 changes: 11 additions & 3 deletions components/molecule/tabs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ 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,
activeTabIndex: activeTabIndexProp,
defaultActiveTabIndex: defaultActiveTabIndexProp = 1,
tabsJustifyContent,
onChange,
keepTabsContentMounted,
...props
}) => {
const [activeTab, setActiveTab] = useControlledState(activeTabIndexProp, defaultActiveTabIndexProp)
Expand All @@ -24,7 +25,12 @@ const MoleculeTabsWithStateActive = ({
}

return (
<MoleculeTabs {...props} tabsJustifyContent={tabsJustifyContent} onChange={handleChange}>
<MoleculeTabs
{...props}
tabsJustifyContent={tabsJustifyContent}
keepTabsContentMounted={keepTabsContentMounted}
onChange={handleChange}
>
{Children.toArray(children)
.filter(Boolean)
.map((child, index) => cloneElement(child, {active: activeTab === index + 1}))}
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions components/molecule/tabs/src/index.scss
Original file line number Diff line number Diff line change
@@ -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';
Loading
Loading