Public
Edited
Sep 7, 2023
Importers
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function Combobox({ label, onInputChange, onSelect, placeholder = '', height = '350px', displayOptions = true }) {

const inputContainer = render(['div', { class: 'p-1 border rounded-md flex-1 flex border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus-within:border-indigo-500 focus-within:outline-none focus-within:ring-1 focus-within:ring-indigo-500 space-x-2 items-center'}]);
const optionsContainer = render(['div']);

function renderOptionsPlaceholder() {
const placeholder = render(['div', { class: 'h-60 mt-2 border border-dashed rounded-md flex items-center justify-center text-gray-400 text-md' }, 'SEARCH RESULTS']);
optionsContainer.replaceChildren(placeholder);
}

function renderInput(value = '') {
function handleSearchInput(e) {
const text = e.target.value;
(async function renderOptions() {
const options = await onInputChange(text);
if (!displayOptions || !Array.isArray(options)) return;
const el = render(['ul', {
class: `z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg
ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm`,
role: "listbox"
},
options.map(createListOption)
]);
optionsContainer.replaceChildren(el);
})()
}
const el = render(['input', {
type: 'text',
class: "flex-1 border-none focus:ring-0 sm:text-sm",
role: "combobox",
'aria-controls': "options",
'aria-expanded': "false",
oninput: handleSearchInput,
value,
placeholder
}
]);

if (value) {
function removeSelectedItem() {
onSelect(null);
renderInput();
}
const close = render(['div', { class: 'flex justify-end' }, CloseButton({ onclick: removeSelectedItem })]);
inputContainer.replaceChildren(el, close);
} else {
inputContainer.replaceChildren(el);
}

if (displayOptions) {
renderOptionsPlaceholder();
}
}

function createListOption(option) {
return ["li", {
role: "option",
class: "relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600",
tabindex: "-1",
onclick: () => {
onSelect(option);
renderInput(option.label);
}
}, ['span', { class: "block truncate"}, option.label]]
}

function CloseButton({ onclick }) {
return ['button',
{
type: 'button',
class: 'group relative -mr-1 h-6 w-6 rounded-sm hover:bg-blue-600/20',
onclick
},
['svg', {
xmlns: "http://www.w3.org/2000/svg",
viewBox: '0 0 14 14',
class: 'h-6 w-6 stroke-blue-800/50 group-hover:stroke-blue-800/75'},
['path', { d: 'M4 4l6 6m0-6l-6 6'}]
],
['span', { class: 'absolute -inset-1'}]
]
}

// set initial container
renderInput()
const tree = [
'div',
label ? ['label', { for: "combobox", class: "block text-sm font-medium text-gray-700" }, label] : ['div'],
inputContainer,
displayOptions ? optionsContainer : ['div']
]
const layout = ['div', { style: { padding: '6px'}}, tree];
return renderIframe(render(layout), { height });
}
Insert cell
function MultiSelect({ label, onInputChange, onSelect, placeholder = '', height = '350px', displayOptions = true }){
let selectedOptions = [];

// Create input and options containers
const multiInputContainer = render(['div', { class: 'p-1 border rounded-md flex-1 flex border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus-within:border-indigo-500 focus-within:outline-none focus-within:ring-1 focus-within:ring-indigo-500 space-x-2'}]);
const inputContainer = render(['div', { class: "relative mt-1" }]);
const optionsContainer = render(['div']);


function renderOptionsPlaceholder() {
const placeholder = render(['div', { class: 'h-60 mt-2 border border-dashed rounded-md flex items-center justify-center text-gray-400 text-md' }, 'SEARCH RESULTS']);
optionsContainer.replaceChildren(placeholder);
}

function renderInput() {
function handleSearchInput(e) {
const text = e.target.value;
(async function renderOptions() {
const options = await onInputChange(text);
if (!displayOptions || !Array.isArray(options)) return;
const el = render(['ul', {
class: `z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg
ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm`,
role: "listbox"
},
options.map(createListOption)
]);
optionsContainer.replaceChildren(el);
})()
}
const inputEl = render(['input', {
type: 'text',
class: "flex-1 border-none focus:ring-0 sm:text-sm",
role: "combobox",
'aria-controls': "options",
'aria-expanded': "false",
oninput: handleSearchInput,
placeholder
}
]);

if (selectedOptions.length > 0) {
function removeSelectedItem(item) {
selectedOptions = selectedOptions.filter(option => option.label !== item.label);
onSelect(selectedOptions);
renderInput();
}

function renderSelectedOption(selected) {
return render([
'div',
{class: 'inline-flex items-center gap-x-0.5 rounded-md bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700'},
['div', selected.label],
['button',
{
type: 'button',
class: 'group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-blue-600/20',
onclick: () => removeSelectedItem(selected)
},
['svg', {
xmlns: "http://www.w3.org/2000/svg",
viewBox: '0 0 14 14',
class: 'h-3.5 w-3.5 stroke-blue-800/50 group-hover:stroke-blue-800/75'},
['path', { d: 'M4 4l6 6m0-6l-6 6'}]
],
['span', { class: 'absolute -inset-1'}]
]
]);
}
multiInputContainer.replaceChildren(...selectedOptions.map(renderSelectedOption), inputEl);
} else {
multiInputContainer.replaceChildren(inputEl);
}

if (displayOptions) {
renderOptionsPlaceholder();
}
}


function createListOption(option) {
return ["li", {
role: "option",
class: "relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600",
tabindex: "-1",
onclick: () => {
selectedOptions = [...selectedOptions, option];
onSelect(selectedOptions);
renderInput();
}
}, ['span', { class: "block truncate"}, option.label]]
}

// set initial container
renderInput();
const tree = [
'div',
label ? ['label', { for: "combobox", class: "block text-sm font-medium text-gray-700" }, label] : ['div'],
multiInputContainer,
displayOptions ? optionsContainer : ['div']
]
const layout = ['div', { style: { padding: '6px'}}, tree];
return renderIframe(render(layout), { height: '350px' });
}
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more