Skip to content

Commit

Permalink
Repeater: Add Tab layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-jaussaud committed Dec 5, 2024
1 parent ea145f7 commit 5ba8015
Show file tree
Hide file tree
Showing 22 changed files with 580 additions and 265 deletions.
2 changes: 1 addition & 1 deletion assets/build/beaver-builder/index.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/build/default/index.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/build/elementor/index.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/build/example.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 19 additions & 19 deletions assets/build/index.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/build/wp/index.min.css

Large diffs are not rendered by default.

46 changes: 27 additions & 19 deletions assets/src/components/repeater/Repeater.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,32 @@ const Repeater = props => {
)
)

/**
* Default function to render footer action, this has to be called
* by the layout (which can use a different render for it)
*/
const renderFooterActions = () => (
repeatable && (
<div className="tf-repeater-actions">
<Button
type="action"
onPress={ () => dispatch({ type: 'add' }) }
isDisabled={ maxLength <= items.length }
>
Add item
</Button>
<ModalTrigger
title="Confirmation"
label="Remove all"
isDisabled={ items.length <= 0 }
onValidate={ () => dispatch({ type: 'clear' })}
>
Are you sure you want to clear all item(s)?
</ModalTrigger>
</div>
)
)

useEffect(() => props.onChange && props.onChange( getSavedValue() ), [items])

return(
Expand All @@ -165,26 +191,8 @@ const Repeater = props => {
afterRow={ props.afterRow }
beforeRow={ props.beforeRow }
name={ props.name ?? '' }
renderFooterActions={ renderFooterActions }
/>
{ repeatable && (
<div className="tf-repeater-actions">
<Button
type="action"
onPress={ () => dispatch({ type: 'add' }) }
isDisabled={ maxLength <= items.length }
>
Add item
</Button>
<ModalTrigger
title="Confirmation"
label="Remove all"
isDisabled={ items.length <= 0 }
onValidate={ () => dispatch({ type: 'clear' })}
>
Are you sure you want to clear all item(s)?
</ModalTrigger>
</div>
)}
</div>
</div>
)
Expand Down
24 changes: 24 additions & 0 deletions assets/src/components/repeater/common/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { isDependentString } from '../../dependent/utils'

/**
* In order to support dependent value from the repeater, we have
* to render the title as an element so that it's wrapped inside
* a new dependent wrapper that can access repeater values
*/
const renderTitle = (item, i, title, name, renderItem) => {

const text = title ? title : ('Item ' + (i + 1))

if( ! isDependentString(text) ) return text;

const element = {
type : 'wrapper',
name : `_repeater-title-${name}-${item.key}`,
content : title,
dependent : true
}

return renderItem(element, item, i)
}

export { renderTitle }
1 change: 1 addition & 0 deletions assets/src/components/repeater/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import './layout/advanced/index.scss';
@import './layout/bare/index.scss';
@import './layout/block/index.scss';
@import './layout/tab/index.scss';
@import './layout/table/index.scss';

.tf-repeater-actions {
Expand Down
164 changes: 84 additions & 80 deletions assets/src/components/repeater/layout/advanced/Advanced.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const Advanced = ({
dispatch,
headerFields = false,
beforeRow = false,
afterRow = false
afterRow = false,
renderFooterActions
}) => {

const [openSection, setOpenSection] = useState(false)
Expand All @@ -28,89 +29,92 @@ const Advanced = ({
}

return(
<div className='tf-repeater-advanced'>
<div className='tf-repeater-advanced-header tf-repeater-advanced-label-row'>
<div key={ 'index' } className='tf-repeater-advanced-label-row-index'></div>
{ headerColumns.map((column, h) => (
<div key={ h } className='tf-repeater-advanced-header-item tf-repeater-advanced-label-row-item'>
{ column.label ?? '' }
</div>
)) }
<div key={ 'arrow' } className='tf-repeater-advanced-label-row-arrow'></div>
</div>
<div className='tf-repeater-items tf-repeater-advanced-items'>
{ items && items.slice(0, maxLength).map((item, i) => (
<div
key={ item.key ?? i }
className="tf-repeater-advanced-item"
data-open={ openSection === i ? 'true' : 'false' }
>
<div className='tf-repeater-advanced-overview tf-repeater-advanced-label-row'>
<div key={ 'index' } className='tf-repeater-advanced-label-row-index'>
{ i + 1 }
</div>
<div className="tf-repeater-advanced-overview-item-container">
<div className="tf-repeater-advanced-overview-item-fields">
{ headerColumns.map((column, h) => (
<div key={ h } className='tf-repeater-advanced-overview-item tf-repeater-advanced-label-row-item'>
{ item[ column.name ] && item[ column.name ] !== ''
? (
typeof item[ column.name ] === 'object'
? formatHeaderFieldsObject(item, column.name)
: item[ column.name ]
)
: <i>Empty</i> }
</div>
)) }
<>
<div className='tf-repeater-advanced'>
<div className='tf-repeater-advanced-header tf-repeater-advanced-label-row'>
<div key={ 'index' } className='tf-repeater-advanced-label-row-index'></div>
{ headerColumns.map((column, h) => (
<div key={ h } className='tf-repeater-advanced-header-item tf-repeater-advanced-label-row-item'>
{ column.label ?? '' }
</div>
)) }
<div key={ 'arrow' } className='tf-repeater-advanced-label-row-arrow'></div>
</div>
<div className='tf-repeater-items tf-repeater-advanced-items'>
{ items && items.slice(0, maxLength).map((item, i) => (
<div
key={ item.key ?? i }
className="tf-repeater-advanced-item"
data-open={ openSection === i ? 'true' : 'false' }
>
<div className='tf-repeater-advanced-overview tf-repeater-advanced-label-row'>
<div key={ 'index' } className='tf-repeater-advanced-label-row-index'>
{ i + 1 }
</div>
{ maxLength !== undefined &&
<div className="tf-repeater-advanced-overview-item-actions">
<Button type="text-primary" onPress={ () => setOpenSection(openSection === i ? false : i) }>
{ openSection === i ? 'Close' : 'Edit' }
</Button>
<Button
type="text-primary"
isDisabled={ maxLength <= items.length }
onPress={() => dispatch({
type : 'clone',
item : item
})}
>
Duplicate
</Button>
<ModalTrigger
label="Delete"
title="Confirmation"
onValidate={ () => dispatch({ type : 'remove', item : i })}
buttonProps={{ type: 'text-danger' }}
>
Are you sure you want to remove item { i + 1 }?
</ModalTrigger>
</div> }
<div className="tf-repeater-advanced-overview-item-container">
<div className="tf-repeater-advanced-overview-item-fields">
{ headerColumns.map((column, h) => (
<div key={ h } className='tf-repeater-advanced-overview-item tf-repeater-advanced-label-row-item'>
{ item[ column.name ] && item[ column.name ] !== ''
? (
typeof item[ column.name ] === 'object'
? formatHeaderFieldsObject(item, column.name)
: item[ column.name ]
)
: <i>Empty</i> }
</div>
)) }
</div>
{ maxLength !== undefined &&
<div className="tf-repeater-advanced-overview-item-actions">
<Button type="text-primary" onPress={ () => setOpenSection(openSection === i ? false : i) }>
{ openSection === i ? 'Close' : 'Edit' }
</Button>
<Button
type="text-primary"
isDisabled={ maxLength <= items.length }
onPress={() => dispatch({
type : 'clone',
item : item
})}
>
Duplicate
</Button>
<ModalTrigger
label="Delete"
title="Confirmation"
onValidate={ () => dispatch({ type : 'remove', item : i })}
buttonProps={{ type: 'text-danger' }}
>
Are you sure you want to remove item { i + 1 }?
</ModalTrigger>
</div> }
</div>
<Button
key={ 'arrow' }
type="repeater-overview-open"
onPress={ () => setOpenSection(openSection === i ? false : i) }
changeTag={ 'span' }
className={ 'tf-repeater-advanced-label-row-arrow' }
>
<div></div>
</Button>
</div>
<Button
key={ 'arrow' }
type="repeater-overview-open"
onPress={ () => setOpenSection(openSection === i ? false : i) }
changeTag={ 'span' }
className={ 'tf-repeater-advanced-label-row-arrow' }
>
<div></div>
</Button>
{ openSection === i && <div className='tf-repeater-advanced-row'>
{ beforeRow && beforeRow(item, i, dispatch) }
{ rowFields.map(control => (
<div key={ control.name ?? i } className="tf-repeater-advanced-item-field">
{ renderItem(control, item, i) }
</div>
)) }
{ afterRow && afterRow(item, i, dispatch) }
</div> }
</div>
{ openSection === i && <div className='tf-repeater-advanced-row'>
{ beforeRow && beforeRow(item, i, dispatch) }
{ rowFields.map(control => (
<div key={ control.name ?? i } className="tf-repeater-advanced-item-field">
{ renderItem(control, item, i) }
</div>
)) }
{ afterRow && afterRow(item, i, dispatch) }
</div> }
</div>
)) }
)) }
</div>
</div>
</div>
{ renderFooterActions() }
</>
)
}

Expand Down
48 changes: 26 additions & 22 deletions assets/src/components/repeater/layout/bare/Bare.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,33 @@ const Bare = ({
maxLength,
dispatch,
beforeRow = false,
afterRow = false
afterRow = false,
renderFooterActions
}) => (
<div className='tf-repeater-items tf-repeater-bare-items'>
{ items && items.slice(0, maxLength).map((item, i) => (
<div key={ item.key ?? i } className='tf-repeater-bare-row'>
{ beforeRow && beforeRow(item, i, dispatch) }
{ rowFields.map(control => (
<div key={ control.name ?? i } className="tf-repeater-bare-item-field">
{ renderItem(control, item, i) }
</div>
)) }
{ maxLength !== undefined &&
<ModalTrigger
label="Remove"
title="Confirmation"
onValidate={ () => dispatch({ type : 'remove', item : i })}
>
Are you sure you want to remove item { i + 1 }?
</ModalTrigger> }
{ afterRow && afterRow(item, i, dispatch) }
</div>
)) }
</div>
<>
<div className='tf-repeater-items tf-repeater-bare-items'>
{ items && items.slice(0, maxLength).map((item, i) => (
<div key={ item.key ?? i } className='tf-repeater-bare-row'>
{ beforeRow && beforeRow(item, i, dispatch) }
{ rowFields.map(control => (
<div key={ control.name ?? i } className="tf-repeater-bare-item-field">
{ renderItem(control, item, i) }
</div>
)) }
{ maxLength !== undefined &&
<ModalTrigger
label="Remove"
title="Confirmation"
onValidate={ () => dispatch({ type : 'remove', item : i }) }
>
Are you sure you want to remove item { i + 1 }?
</ModalTrigger> }
{ afterRow && afterRow(item, i, dispatch) }
</div>
)) }
</div>
{ renderFooterActions() }
</>
)

export default Bare
Loading

0 comments on commit 5ba8015

Please sign in to comment.