This example was inspired by traf’s work using framer.
<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>
.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);}
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()