Instantly share code, notes, and snippets.
Created
May 26, 2025 16:19
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save manzt/ada6567d035bc260fc707ff7ca839794 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# /// script | |
# requires-python = ">=3.13" | |
# dependencies = [ | |
# "anywidget==0.9.18", | |
# "marimo", | |
# "traitlets==5.14.3", | |
# ] | |
# /// | |
import marimo | |
__generated_with = "0.13.11" | |
app = marimo.App(width="medium") | |
@app.cell | |
def _(genres): | |
genres.value | |
return | |
@app.cell | |
def _(Tags): | |
import marimo as mo | |
genres = mo.ui.anywidget( | |
Tags( | |
[ | |
"Action", | |
"Comedy", | |
"Drama", | |
"Thriller", | |
"Sci-Fi", | |
"Animation", | |
"Documentary", | |
] | |
) | |
) | |
genres | |
return (genres,) | |
@app.cell | |
def _(): | |
import typing | |
import anywidget | |
import traitlets | |
class Tags(anywidget.AnyWidget): | |
_esm = """ | |
function render({ model, el }) { | |
el.classList.add("tags-anywidget"); | |
function renderTags() { | |
el.replaceChildren(); | |
let container = document.createElement("div"); | |
container.className = "tag-list"; | |
model.get("value").forEach((item, index) => { | |
let tag = document.createElement("span"); | |
tag.className = "tag"; | |
let label = document.createElement("span"); | |
label.className = "tag-label"; | |
label.textContent = item; | |
let remove = document.createElement("button"); | |
remove.className = "tag-remove"; | |
remove.setAttribute("aria-label", "Remove tag"); | |
remove.innerHTML = | |
`<svg viewBox="0 0 14 14"><path d="M4 4l6 6m0-6l-6 6"></path></svg>`; | |
remove.onclick = () => { | |
model.set("value", model.get("value").toSpliced(index, 1)); | |
model.save_changes(); | |
}; | |
tag.setAttribute("draggable", "true"); | |
tag.dataset.index = index; | |
tag.ondragstart = (e) => { | |
e.dataTransfer.setData("text/plain", index); | |
e.dataTransfer.effectAllowed = "move"; | |
}; | |
tag.ondragover = (e) => { | |
e.preventDefault(); | |
e.dataTransfer.dropEffect = "move"; | |
}; | |
tag.ondrop = (e) => { | |
e.preventDefault(); | |
let fromIndex = Number(e.dataTransfer.getData("text/plain")); | |
let toIndex = Number(tag.dataset.index); | |
if (fromIndex === toIndex) return; | |
let updated = [...model.get("value")]; | |
let [moved] = updated.splice(fromIndex, 1); | |
updated.splice(toIndex, 0, moved); | |
model.set("value", updated); | |
model.save_changes(); | |
}; | |
tag.ondragstart = (e) => { | |
e.dataTransfer.setData("text/plain", index); | |
tag.classList.add("dragging"); | |
}; | |
tag.ondragend = () => { | |
tag.classList.remove("dragging"); | |
}; | |
tag.appendChild(label); | |
tag.appendChild(remove); | |
container.appendChild(tag); | |
}); | |
let input = document.createElement("input"); | |
input.type = "text"; | |
input.className = "tag-input"; | |
input.onkeydown = (e) => { | |
if ( | |
(e.key === "Enter" || | |
(e.key === "Enter" && (e.metaKey || e.ctrlKey))) && | |
input.value.trim() | |
) { | |
e.preventDefault(); | |
let updated = [...model.get("value"), input.value.trim()]; | |
model.set("value", updated); | |
model.save_changes(); | |
input.value = ""; | |
input.focus(); | |
} | |
}; | |
container.appendChild(input); | |
el.appendChild(container); | |
} | |
renderTags(); | |
model.on("change:value", renderTags); | |
} | |
export default { render }; | |
""" | |
_css = """ | |
.tags-anywidget { | |
font-family: system-ui, sans-serif; | |
display: block; | |
max-width: 100%; | |
border: 0px solid rgb(229, 231, 235); | |
box-sizing: border-box; | |
.tag-list { | |
display: flex; | |
flex-wrap: wrap; | |
align-items: center; | |
gap: 0.5em; | |
} | |
.tag { | |
display: inline-flex; | |
align-items: center; | |
column-gap: 0.125rem; | |
border-radius: 0.375rem; | |
background: oklch(96.7% .003 264.542); | |
padding-inline: 0.5rem; | |
padding-block: 0.25rem; | |
font-size: 0.75rem; | |
font-weight: 500; | |
color: oklch(44.6% .03 256.802) | |
} | |
.tag-remove { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
border-radius: 0.125rem; | |
width: 0.875rem; | |
height: 0.875rem; | |
margin-right: -0.25rem; | |
position: relative; | |
color: inherit; | |
opacity: 1; | |
cursor: pointer; | |
border: none; | |
background: transparent; | |
padding: 0; | |
} | |
.tag-remove:hover { | |
background-color: rgba(0, 0, 0, 0.1); | |
} | |
.tag-remove > svg { | |
display: block; | |
vertical-align: middle; | |
stroke: currentColor; | |
width: 0.75rem; | |
height: 0.75rem; | |
} | |
.tag-input { | |
flex: 1; | |
min-width: 120px; | |
font-size: 0.875rem; | |
padding: 0.25em 0.5em; | |
border: none; | |
outline: none; | |
} | |
.tag[draggable="true"] { | |
cursor: grab; | |
} | |
.tag.dragging { | |
opacity: 0.5; | |
} | |
} | |
""" | |
value = traitlets.List(traitlets.Unicode()).tag(sync=True) | |
def __init__(self, value: typing.Sequence[str]) -> None: | |
super().__init__(value=list(value)) | |
return (Tags,) | |
if __name__ == "__main__": | |
app.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment