Skip to content
eloxi
v0.12.0

Stacked dropdown

This example was inspired by traf’s work using framer.

HTML

<div
class="dropdown"
data-vel-plugin="DropdownPlugin"
data-vel-view="container"
data-vel-data-open="false"
>
<div
class="item top-item"
data-vel-plugin="DropdownPlugin"
data-vel-view="item"
>
List
<svg
class="icon"
fill="none"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="m4.93934 10.9393c.58579-.5857 1.53553-.5857 2.12132 0l8.93934 8.9394 8.9393-8.9394c.5858-.5857 1.5356-.5857 2.1214 0 .5857.5858.5857 1.5356 0 2.1214l-10 10c-.5858.5857-1.5356.5857-2.1214 0l-9.99996-10c-.58579-.5858-.58579-1.5356 0-2.1214z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</div>
<div class="item" data-vel-plugin="DropdownPlugin" data-vel-view="item">
Item 1
</div>
<div class="item" data-vel-plugin="DropdownPlugin" data-vel-view="item">
Item 2
</div>
<div class="item" data-vel-plugin="DropdownPlugin" data-vel-view="item">
Item 3
</div>
<div class="item" data-vel-plugin="DropdownPlugin" data-vel-view="item">
Item 4
</div>
</div>

CSS

.dropdown {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
width: 100%;
max-width: 280px;
gap: 10px 0;
}
.item {
background: hsl(208.7, 60%, 20%);
color: white;
width: 100%;
padding: 20px 15px;
border-radius: 10px;
display: flex;
justify-content: space-between;
cursor: pointer;
box-shadow: 0px -1px 10px rgba(0, 0, 0, 0.24);
}
.icon {
transform: rotate(-90deg);
width: 18px;
color: white;
}
.dropdown[data-vel-data-open='true'] .icon {
transform: rotate(0deg);
}

JavaScript

API Style:
Functions
Classes
import { createApp } from 'veloxi'
const DropdownPlugin = (context) => {
let items = null
let container = null
setup() {
container = context.getView('container')
items = context.getViews('item')
items.forEach((item, index) => {
item.position.animator.set('spring')
item.scale.animator.set('dynamic')
item.styles.zIndex = `${items.length - index}`
})
updateItems(false)
}
updateItems(animate = true) {
const centerY =
container.position.initialY + container.size.height / 2
items.forEach((item, index) => {
if (!isOpen()) {
const offset = index * 8
item.position.set({ y: centerY - offset }, animate)
item.scale.set({ x: 1 - index * 0.05 }, animate)
item.styles.filter = `brightness(${100 - index * 5}%)`
} else {
item.position.reset(animate)
item.scale.reset(animate)
item.styles.filter = ''
}
})
}
function onDataChanged({ dataName }) {
if (dataName === 'open') {
updateItems()
}
}
function isOpen() {
return container.data.open === 'true'
}
}
const topItem = document.querySelector('.top-item')
topItem.addEventListener('click', () => {
const dropdown = document.querySelector('[data-vel-view="container"]')
const isOpen = dropdown.dataset.velDataOpen === 'true'
dropdown.dataset.velDataOpen = isOpen ? 'false' : 'true'
})
const app = createApp()
app.addPlugin(DropdownPlugin)
DropdownPlugin.pluginName = 'DropdownPlugin'
DropdownPlugin.scope = 'container'
app.run()
import { Plugin, createApp } from 'veloxi'
class DropdownPlugin extends Plugin {
static pluginName = 'DropdownPlugin'
static scope = 'container'
items = null
container = null
setup() {
this.container = this.getView('container')
this.items = this.getViews('item')
this.items.forEach((item, index) => {
item.position.animator.set('spring')
item.scale.animator.set('dynamic')
item.styles.zIndex = `${this.items.length - index}`
})
this.updateItems(false)
}
updateItems(animate = true) {
const centerY =
this.container.position.initialY + this.container.size.height / 2
this.items.forEach((item, index) => {
if (!this.isOpen) {
const offset = index * 8
item.position.set({ y: centerY - offset }, animate)
item.scale.set({ x: 1 - index * 0.05 }, animate)
item.styles.filter = `brightness(${100 - index * 5}%)`
} else {
item.position.reset(animate)
item.scale.reset(animate)
item.styles.filter = ''
}
})
}
onDataChanged({ dataName }) {
if (dataName === 'open') {
this.updateItems()
}
}
get isOpen() {
return this.container.data.open === 'true'
}
}
const topItem = document.querySelector('.top-item')
topItem.addEventListener('click', () => {
const dropdown = document.querySelector('[data-vel-view="container"]')
const isOpen = dropdown.dataset.velDataOpen === 'true'
dropdown.dataset.velDataOpen = isOpen ? 'false' : 'true'
})
const app = createApp()
app.addPlugin(DropdownPlugin)
app.run()