budibase/packages/builder/src/components/userInterface/OptionSelect.svelte

250 lines
5.2 KiB
Svelte
Raw Normal View History

2020-05-07 15:30:04 +02:00
<script>
2020-11-03 10:43:08 +01:00
import { onMount } from "svelte"
import Portal from "svelte-portal"
import { buildStyle } from "../../helpers.js"
2020-05-28 17:24:53 +02:00
export let options = []
2020-05-07 15:30:04 +02:00
export let value = ""
2020-05-28 17:24:53 +02:00
export let styleBindingProperty
2020-11-03 10:43:08 +01:00
export let onChange = () => {}
2020-05-28 17:24:53 +02:00
let open = null
let rotate = ""
let select
let selectMenu
let icon
let width = 0
2020-05-28 17:24:53 +02:00
2020-07-02 12:03:15 +02:00
let selectAnchor = null
let dimensions = { top: 0, bottom: 0, left: 0 }
2020-05-28 17:24:53 +02:00
let positionSide = "top"
let maxHeight = 0
2020-07-02 12:03:15 +02:00
let scrollTop = 0
let containerEl = null
2020-10-27 16:28:13 +01:00
const handleStyleBind = value =>
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
2020-05-07 15:30:04 +02:00
onMount(() => {
2020-05-28 17:24:53 +02:00
if (select) {
2020-06-29 14:38:08 +02:00
select.addEventListener("keydown", handleEnter)
2020-05-28 17:24:53 +02:00
}
2020-05-28 17:24:53 +02:00
return () => {
2020-06-29 14:38:08 +02:00
select.removeEventListener("keydown", handleEnter)
2020-05-07 15:30:04 +02:00
}
})
2020-05-28 17:24:53 +02:00
2020-06-29 14:38:08 +02:00
function handleEscape(e) {
2020-07-02 12:03:15 +02:00
if (open && e.key === "Escape") {
toggleSelect(false)
2020-06-29 14:38:08 +02:00
}
}
function getDimensions() {
2020-07-02 12:03:15 +02:00
const {
bottom,
top: spaceAbove,
left,
} = selectAnchor.getBoundingClientRect()
2020-05-28 17:24:53 +02:00
const spaceBelow = window.innerHeight - bottom
2020-07-02 12:03:15 +02:00
let y
2020-05-28 17:24:53 +02:00
if (spaceAbove > spaceBelow) {
positionSide = "bottom"
2020-07-02 12:03:15 +02:00
maxHeight = spaceAbove - 20
y = window.innerHeight - spaceAbove
2020-05-28 17:24:53 +02:00
} else {
positionSide = "top"
y = bottom
maxHeight = spaceBelow - 20
2020-05-28 17:24:53 +02:00
}
2020-07-02 12:03:15 +02:00
dimensions = { [positionSide]: y, left }
2020-05-28 17:24:53 +02:00
}
2020-06-29 14:38:08 +02:00
function handleEnter(e) {
if (!open && e.key === "Enter") {
2020-07-02 12:03:15 +02:00
toggleSelect(true)
}
2020-05-28 17:24:53 +02:00
}
function toggleSelect(isOpen) {
getDimensions()
2020-05-28 17:24:53 +02:00
if (isOpen) {
2020-07-02 12:03:15 +02:00
icon.style.transform = "rotate(180deg)"
2020-05-28 17:24:53 +02:00
} else {
icon.style.transform = "rotate(0deg)"
}
2020-06-29 16:32:37 +02:00
open = isOpen
2020-05-28 17:24:53 +02:00
}
function handleClick(val) {
value = val
onChange(value)
2020-07-02 12:22:30 +02:00
toggleSelect(false)
2020-05-28 17:24:53 +02:00
}
$: menuStyle = buildStyle({
"max-height": `${maxHeight.toFixed(0)}px`,
2020-05-29 11:45:19 +02:00
"transform-origin": `center ${positionSide}`,
[positionSide]: `${dimensions[positionSide]}px`,
2020-07-02 12:03:15 +02:00
left: `${dimensions.left.toFixed(0)}px`,
width: `${width}px`,
2020-05-28 17:24:53 +02:00
})
2020-10-27 16:28:13 +01:00
$: isOptionsObject = options.every(o => typeof o === "object")
2020-05-28 17:24:53 +02:00
$: selectedOption = isOptionsObject
2020-10-27 16:28:13 +01:00
? options.find(o => o.value === value)
2020-05-28 17:24:53 +02:00
: {}
2020-07-02 12:03:15 +02:00
$: if (open && selectMenu) {
2020-06-29 16:32:37 +02:00
selectMenu.focus()
}
2020-05-28 17:24:53 +02:00
$: displayLabel =
selectedOption && selectedOption.label ? selectedOption.label : value || ""
2020-05-07 15:30:04 +02:00
</script>
2020-05-28 17:24:53 +02:00
<div
bind:clientWidth={width}
2020-05-28 17:24:53 +02:00
tabindex="0"
bind:this={select}
class="bb-select-container"
on:click={() => toggleSelect(!open)}>
2020-07-02 12:22:30 +02:00
<div bind:this={selectAnchor} title={value} class="bb-select-anchor selected">
2020-05-28 17:24:53 +02:00
<span>{displayLabel}</span>
<i bind:this={icon} class="ri-arrow-down-s-fill" />
</div>
2020-06-29 16:32:37 +02:00
{#if open}
2020-07-02 12:03:15 +02:00
<Portal>
<div
tabindex="0"
class:open
bind:this={selectMenu}
style={menuStyle}
on:keydown={handleEscape}
class="bb-select-menu">
<ul>
{#if isOptionsObject}
{#each options as { value: v, label }}
<li
{...handleStyleBind(v)}
on:click|self={handleClick(v)}
class:selected={value === v}>
{label}
</li>
{/each}
{:else}
{#each options as v}
<li
{...handleStyleBind(v)}
on:click|self={handleClick(v)}
class:selected={value === v}>
{v}
</li>
{/each}
{/if}
</ul>
</div>
<div on:click|self={() => toggleSelect(false)} class="overlay" />
</Portal>
2020-06-29 16:32:37 +02:00
{/if}
2020-05-28 17:24:53 +02:00
</div>
<style>
.overlay {
position: fixed;
2020-05-28 17:24:53 +02:00
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
}
.bb-select-container {
outline: none;
cursor: pointer;
overflow: hidden;
flex: 1 1 auto;
2020-05-28 17:24:53 +02:00
}
.bb-select-anchor {
cursor: pointer;
display: flex;
padding: var(--spacing-s) var(--spacing-m);
background-color: var(--grey-2);
border-radius: var(--border-radius-m);
2020-06-01 17:31:58 +02:00
align-items: center;
2020-07-02 12:22:30 +02:00
white-space: nowrap;
2020-05-28 17:24:53 +02:00
}
.bb-select-anchor > span {
color: var(--ink);
font-weight: 400;
2020-05-29 11:45:19 +02:00
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--font-size-xs);
flex: 1 1 auto;
2020-05-28 17:24:53 +02:00
}
.bb-select-anchor > i {
2020-05-29 11:45:19 +02:00
transition: transform 0.13s ease;
transform-origin: center;
width: 20px;
height: 20px;
2020-05-29 11:45:19 +02:00
text-align: center;
2020-05-28 17:24:53 +02:00
}
.selected {
color: var(--ink);
font-weight: 400;
2020-05-28 17:24:53 +02:00
}
.bb-select-menu {
position: absolute;
display: flex;
2020-06-29 14:38:08 +02:00
outline: none;
2020-05-28 17:24:53 +02:00
box-sizing: border-box;
flex-direction: column;
opacity: 0;
z-index: 2;
color: var(--ink);
font-weight: 400;
2020-05-28 17:24:53 +02:00
height: fit-content !important;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
background-color: var(--grey-2);
2020-05-28 17:24:53 +02:00
transform: scale(0);
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
overflow-y: auto;
}
.open {
transform: scale(1);
opacity: 1;
}
ul {
list-style-type: none;
margin: 0;
2020-05-29 11:49:51 +02:00
padding: 5px 0px;
2020-05-28 17:24:53 +02:00
}
li {
height: auto;
padding: 5px 0px;
cursor: pointer;
padding-left: 10px;
font-size: var(--font-size-xs);
2020-05-28 17:24:53 +02:00
}
2020-05-29 11:45:19 +02:00
li:hover {
2020-10-29 21:42:34 +01:00
background-color: var(--grey-3);
2020-05-28 17:24:53 +02:00
}
</style>