Skip to content

Commit

Permalink
feat: configurable panel displayType option with auto fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinchappell committed Mar 30, 2020
1 parent 7581ab1 commit be66bc9
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 113 deletions.
15 changes: 7 additions & 8 deletions src/js/components/controls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ const defaultElements = [...formControls, ...htmlControls, ...layoutControls]
*/
export class Controls {
constructor() {
const _this = this
this.data = new Map()

this.controlEvents = {
focus: ({ target }) => {
const group = target.closest(`.${CONTROL_GROUP_CLASSNAME}`)
return group && _this.panels.nav.refresh(indexOfNode(group))
return group && this.panels.nav.refresh(indexOfNode(group))
},
click: ({ target }) => _this.addElement(target.parentElement.id),
click: ({ target }) => this.addElement(target.parentElement.id),
}
}

Expand Down Expand Up @@ -249,14 +248,14 @@ export class Controls {
* @return {DOM}
*/
buildDOM(sticky) {
const _this = this
const groupedFields = this.groupElements()
const formActions = this.formActions()
_this.panels = new Panels({ panels: groupedFields, type: 'controls', displayType: 'slider' })
const { displayType } = this.options.panels
this.panels = new Panels({ panels: groupedFields, type: 'controls', displayType })
const groupsWrapClasses = ['control-groups', 'formeo-panels-wrap', `panel-count-${groupedFields.length}`]
const groupsWrap = dom.create({
className: groupsWrapClasses,
content: _this.panels.children,
content: [this.panels.panelNav, this.panels.panelsWrap],
})

let controlClass = 'formeo-controls'
Expand Down Expand Up @@ -303,7 +302,7 @@ export class Controls {
filteredTerm.remove()
}
},
addElement: _this.addElement,
addElement: this.addElement,
// @todo finish the addGroup method
addGroup: group => console.log(group),
}
Expand All @@ -324,7 +323,7 @@ export class Controls {
pull: 'clone',
put: false,
},
onRemove: _this.applyControlEvents,
onRemove: this.applyControlEvents,
onStart: ({ item }) => {
const { controlData } = this.get(item.id)
if (this.options.ghostPreview) {
Expand Down
1 change: 1 addition & 0 deletions src/js/components/controls/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const defaultOptions = Object.freeze({
},
elements: [],
container: null,
panels: { displayType: 'slider' },
})

export default defaultOptions
11 changes: 6 additions & 5 deletions src/js/components/fields/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,19 +216,20 @@ export default class Field extends Component {
const panelsData = {
panels: this.editPanels.map(({ panelConfig }) => panelConfig),
id: _this.id,
displayType: 'auto',
}

const editPanelLength = this.editPanels.length

if (editPanelLength) {
const editPanels = (_this.panels = new Panels(panelsData))
this.panels = new Panels(panelsData)
fieldEdit.className.push(`panel-count-${editPanelLength}`)
fieldEdit.content = editPanels.children
_this.panelNav = editPanels.nav
_this.resizePanelWrap = editPanels.action.resize
fieldEdit.content = [this.panels.panelNav, this.panels.panelsWrap]
this.panelNav = this.panels.nav
this.resizePanelWrap = this.panels.nav.refresh
fieldEdit.action = {
onRender: () => {
_this.resizePanelWrap()
this.resizePanelWrap()
if (!editPanelLength) {
const field = this.dom
const editToggle = field.querySelector('.item-edit-toggle')
Expand Down
164 changes: 79 additions & 85 deletions src/js/components/panels.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import Sortable from 'sortablejs'
import h, { indexOfNode } from '../common/helpers'
import dom from '../common/dom'
import { ANIMATION_SPEED_SLOW, ANIMATION_SPEED_FAST } from '../constants'
import { merge } from '../common/utils'

const defaults = {
const defaults = Object.freeze({
type: 'field',
}
displayType: 'slider',
})

const getTransition = val => ({ transform: `translateX(${val}px)` })
const getTransition = val => ({ transform: `translateX(${val ? `${val}px` : 0})` })

/**
* Edit and control sliding panels
Expand All @@ -20,50 +22,45 @@ export default class Panels {
* @return {Object} Panels
*/
constructor(options) {
this.opts = Object.assign({}, defaults, options)

this.labels = this.panelNav()
const panelsWrap = this.createPanelsWrap()
this.opts = merge(defaults, options)
this.panelDisplay = this.opts.displayType

this.panels = panelsWrap.childNodes
this.activePanelIndex = 0
this.currentPanel = this.panels[this.activePanelIndex]
this.nav = this.navActions()

this.panelDisplay = this.opts.displayType || 'slider'
this.panelNav = this.createPanelNav()
const panelsWrap = this.createPanelsWrap()
this.nav = this.navActions()

const resizeObserver = new window.ResizeObserver(([{ contentRect: { width } }]) => {
if (this.activePanelIndex && this.currentWidth !== width) {
if (this.currentWidth !== width) {
this.toggleTabbedLayout()
this.currentWidth = width
this.nav.setTranslateX(this.activePanelIndex, false)
this.toggleTabbedLayout()
}
})

const observeTimeout = window.setTimeout(() => {
resizeObserver.observe(this.panelsWrap)
resizeObserver.observe(panelsWrap)
window.clearTimeout(observeTimeout)
}, ANIMATION_SPEED_SLOW)

return {
children: [this.labels, panelsWrap],
nav: this.nav,
action: {
resize: this.resizePanels,
},
}
}

toggleTabbedLayout = () => {
const panelsWrap = this.panelsWrap
const column = panelsWrap.parentElement.parentElement
getPanelDisplay() {
const column = this.panelsWrap
const width = parseInt(dom.getStyle(column, 'width'))
const autoDisplayType = width > 390 ? 'tabbed' : 'slider'
this.panelDisplay = this.opts.displayType || autoDisplayType
const isTabbed = this.panelDisplay === 'tabbed'
panelsWrap.parentElement.classList.toggle('tabbed-panels', isTabbed)
const isAuto = this.opts.displayType === 'auto'
this.panelDisplay = isAuto ? autoDisplayType : this.opts.displayType || defaults.displayType
console.log(autoDisplayType, this.panelDisplay)
return this.panelDisplay
}

toggleTabbedLayout = () => {
this.getPanelDisplay()
const isTabbed = this.isTabbed
this.panelsWrap.parentElement.classList.toggle('tabbed-panels', isTabbed)
if (isTabbed) {
column.querySelector('.panel-labels div').removeAttribute('style')
this.panelNav.removeAttribute('style')
}
return isTabbed
}
Expand All @@ -86,18 +83,20 @@ export default class Panels {
* @return {Object} DOM element
*/
createPanelsWrap() {
this.panelsWrap = dom.create({
const panelsWrap = dom.create({
className: 'panels',
content: this.opts.panels,
content: this.opts.panels.map(({ config: { label }, ...panel }) => panel),
})

this.panelsWrap = this.panelsWrap

if (this.opts.type === 'field') {
this.sortableProperties(this.panelsWrap)
this.sortableProperties(panelsWrap)
}

return this.panelsWrap
this.panelsWrap = panelsWrap
this.panels = panelsWrap.children
this.currentPanel = this.panels[this.activePanelIndex]

return panelsWrap
}

/**
Expand Down Expand Up @@ -130,43 +129,41 @@ export default class Panels {
})
}

/**
* Panel navigation, tabs and arrow buttons for slider
* @return {Object} DOM object for panel navigation wrapper
*/
panelNav() {
const _this = this
const panelNavLabels = {
createPanelNavLabels() {
const labels = this.opts.panels.map(panel => ({
tag: 'h5',
action: {
click: evt => {
const index = indexOfNode(evt.target, evt.target.parentElement)
this.currentPanel = this.panels[index]
const labels = evt.target.parentElement.childNodes
this.nav.refresh(index)
dom.removeClasses(labels, 'active-tab')
evt.target.classList.add('active-tab')
},
},
content: panel.config.label,
}))

const panelLabels = {
className: 'panel-labels',
content: {
content: [],
content: labels,
},
}
const panels = this.opts.panels // make new array

for (let i = 0; i < panels.length; i++) {
const panelLabel = {
tag: 'h5',
action: {
click: evt => {
const index = indexOfNode(evt.target, evt.target.parentElement)
_this.currentPanel = _this.panels[index]
const labels = evt.target.parentElement.childNodes
_this.nav.refresh(index)
dom.removeClasses(labels, 'active-tab')
evt.target.classList.add('active-tab')
},
},
content: panels[i].config.label,
}
delete panels[i].config.label

if (i === 0) {
panelLabel.className = 'active-tab'
}
const [firstLabel] = labels
firstLabel.className = 'active-tab'

panelNavLabels.content.content.push(panelLabel)
}
return dom.create(panelLabels)
}

/**
* Panel navigation, tabs and arrow buttons for slider
* @return {Object} DOM object for panel navigation wrapper
*/
createPanelNav() {
this.labels = this.createPanelNavLabels()

const next = {
tag: 'button',
Expand Down Expand Up @@ -206,27 +203,30 @@ export default class Panels {
attrs: {
className: 'panel-nav',
},
content: [prev, panelNavLabels, next],
content: [prev, this.labels, next],
})
}

get isTabbed() {
return this.panelDisplay === 'tabbed'
}

/**
* Handlers for navigating between panel groups
* @todo refactor to use requestAnimationFrame instead of css transitions
* @return {Object} actions that control panel groups
*/
navActions() {
const _this = this
const action = {}
const groupParent = this.currentPanel.parentElement
const firstControlNav = this.labels.querySelector('.panel-labels').firstChild
const labelWrap = this.labels.firstChild
const siblingGroups = this.currentPanel.parentElement.childNodes
this.activePanelIndex = indexOfNode(this.currentPanel, groupParent)
let offset = { nav: 0, panel: 0 }
let lastOffset = { ...offset }

const groupChange = newIndex => {
const labels = this.labels.querySelector('.panel-labels div').children
const labels = labelWrap.children
dom.removeClasses(siblingGroups, 'active-panel')
dom.removeClasses(labels, 'active-tab')
this.currentPanel = siblingGroups[newIndex]
Expand All @@ -238,21 +238,14 @@ export default class Panels {

const getOffset = index => {
return {
nav: -firstControlNav.offsetWidth * index,
nav: -labelWrap.offsetWidth * index,
panel: -groupParent.offsetWidth * index,
}
}

const translateX = ({ offset, reset, duration = ANIMATION_SPEED_FAST, animate = true }) => {
if (_this.panelDisplay === 'tabbed') {
firstControlNav.removeAttribute('style')
const { transform } = getTransition(offset.panel)
groupParent.style.transform = transform
return null
}

const translateX = ({ offset, reset, duration = ANIMATION_SPEED_FAST, animate = !this.isTabbed }) => {
const panelQueue = [getTransition(lastOffset.panel), getTransition(offset.panel)]
const navQueue = [getTransition(lastOffset.nav), getTransition(offset.nav)]
const navQueue = [getTransition(lastOffset.nav), getTransition(this.isTabbed ? 0 : offset.nav)]

if (reset) {
const [panelStart] = panelQueue
Expand All @@ -268,7 +261,8 @@ export default class Panels {
}

const panelTransition = groupParent.animate(panelQueue, animationOptions)
firstControlNav.animate(navQueue, animationOptions)
labelWrap.animate(navQueue, animationOptions)

const handleFinish = () => {
this.panelsWrap.style.height = dom.getStyle(this.currentPanel, 'height')
panelTransition.removeEventListener('finish', handleFinish)
Expand All @@ -289,8 +283,8 @@ export default class Panels {
this.activePanelIndex = newIndex
groupChange(newIndex)
}
_this.resizePanels()
action.setTranslateX(this.activePanelIndex)
this.resizePanels()
action.setTranslateX(this.activePanelIndex, false)
}

/**
Expand All @@ -302,7 +296,7 @@ export default class Panels {
if (newIndex !== siblingGroups.length) {
const curPanel = groupChange(newIndex)
offset = {
nav: -firstControlNav.offsetWidth * newIndex,
nav: -labelWrap.offsetWidth * newIndex,
panel: -curPanel.offsetLeft,
}
translateX({ offset })
Expand All @@ -323,7 +317,7 @@ export default class Panels {
const newIndex = this.activePanelIndex - 1
const curPanel = groupChange(newIndex)
offset = {
nav: -firstControlNav.offsetWidth * newIndex,
nav: -labelWrap.offsetWidth * newIndex,
panel: -curPanel.offsetLeft,
}
translateX({ offset })
Expand Down
6 changes: 6 additions & 0 deletions src/sass/components/_controls.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
width: calc(100% - 2px);
}

.tabbed-panels {
nav {
padding: 0;
}
}

nav {
position: relative;
padding: 0 $action-btn-width;
Expand Down
2 changes: 0 additions & 2 deletions src/sass/components/_field-edit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
margin-bottom: 0;
padding: 0;
overflow: hidden;
border-top-left-radius: $input-border-radius;
border-top-right-radius: $input-border-radius;
button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
Expand Down
Loading

0 comments on commit be66bc9

Please sign in to comment.