forked from tsai/budibase
committed by
GitHub
18 changed files with 595 additions and 187 deletions
@ -1,29 +1,42 @@ |
|||||
<script> |
<script> |
||||
|
import {onMount} from "svelte" |
||||
|
import { buildStyle } from "../../helpers.js" |
||||
export let value = "" |
export let value = "" |
||||
export let width = "" |
export let textAlign = "left" |
||||
|
export let width = "160px" |
||||
|
export let placeholder = "" |
||||
|
|
||||
|
let centerPlaceholder = textAlign === "center" |
||||
|
|
||||
let style = { width } |
let style = buildStyle({ width, textAlign }) |
||||
</script> |
</script> |
||||
|
|
||||
<input type="text" style={`width: ${width};`} on:change bind:value /> |
<input class:centerPlaceholder type="text" {placeholder} {style} on:change bind:value /> |
||||
|
|
||||
<style> |
<style> |
||||
input { |
input { |
||||
display: block; |
/* width: 32px; */ |
||||
font-size: 14px; |
height: 32px; |
||||
font-weight: 500; |
font-size: 12px; |
||||
|
font-weight: 700; |
||||
|
margin: 0px 0px 0px 1px; |
||||
color: var(--ink); |
color: var(--ink); |
||||
|
opacity: 0.7; |
||||
|
padding: 0px 4px; |
||||
line-height: 1.3; |
line-height: 1.3; |
||||
padding: 12px; |
/* padding: 12px; */ |
||||
width: 164px; |
width: 164px; |
||||
max-width: 100%; |
|
||||
box-sizing: border-box; |
box-sizing: border-box; |
||||
margin: 0; |
border: 1px solid var(--grey); |
||||
-moz-appearance: none; |
border-radius: 2px; |
||||
-webkit-appearance: none; |
outline: none; |
||||
appearance: none; |
} |
||||
background: #fff; |
|
||||
border: 1px solid var(--grey-dark); |
input::placeholder { |
||||
height: 32px; |
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.centerPlaceholder::placeholder { |
||||
|
text-align: center; |
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -1,36 +1,223 @@ |
|||||
<script> |
<script> |
||||
import { onMount } from "svelte" |
import { onMount, beforeUpdate } from "svelte" |
||||
|
import {buildStyle} from "../../helpers.js" |
||||
|
export let options = [] |
||||
export let value = "" |
export let value = "" |
||||
|
export let styleBindingProperty |
||||
export let onChange = value => {} |
export let onChange = value => {} |
||||
export let options = [] |
|
||||
export let initialValue = "" |
let open = null |
||||
export let styleBindingProperty = "" |
let rotate = "" |
||||
|
let select |
||||
|
let selectMenu |
||||
|
let icon |
||||
|
|
||||
|
let selectYPosition = null |
||||
|
let availableSpace = 0 |
||||
|
|
||||
|
let positionSide = "top" |
||||
|
let maxHeight = null |
||||
|
let menuHeight |
||||
|
|
||||
const handleStyleBind = value => |
const handleStyleBind = value => |
||||
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
||||
|
|
||||
$: isOptionsObject = options.every(o => typeof o === "object") |
|
||||
|
|
||||
onMount(() => { |
onMount(() => { |
||||
if (!value && !!initialValue) { |
if (select) { |
||||
value = initialValue |
select.addEventListener("keydown", addSelectKeyEvents) |
||||
|
} |
||||
|
return () => { |
||||
|
select.removeEventListener("keydown", addSelectKeyEvents) |
||||
} |
} |
||||
}) |
}) |
||||
|
|
||||
|
function checkPosition() { |
||||
|
const { bottom, top: spaceAbove } = select.getBoundingClientRect() |
||||
|
const spaceBelow = window.innerHeight - bottom |
||||
|
|
||||
|
if (spaceAbove > spaceBelow) { |
||||
|
positionSide = "bottom" |
||||
|
maxHeight = `${spaceAbove.toFixed(0) - 20}px` |
||||
|
} else { |
||||
|
positionSide = "top" |
||||
|
maxHeight = `${spaceBelow.toFixed(0) - 20}px` |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function addSelectKeyEvents(e) { |
||||
|
if (e.key === "Enter") { |
||||
|
if (!open) { |
||||
|
toggleSelect(true) |
||||
|
} |
||||
|
} else if (e.key === "Escape") { |
||||
|
if (open) { |
||||
|
toggleSelect(false) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function toggleSelect(isOpen) { |
||||
|
checkPosition() |
||||
|
if (isOpen) { |
||||
|
icon.style.transform = "rotate(180deg)" |
||||
|
} else { |
||||
|
icon.style.transform = "rotate(0deg)" |
||||
|
} |
||||
|
open = isOpen |
||||
|
} |
||||
|
|
||||
|
function handleClick(val) { |
||||
|
value = val |
||||
|
onChange(value) |
||||
|
} |
||||
|
|
||||
|
$: menuStyle = buildStyle({ |
||||
|
"max-height": maxHeight, |
||||
|
"transform-origin": `center ${positionSide}`, |
||||
|
[positionSide]: "32px", |
||||
|
}) |
||||
|
|
||||
|
$: isOptionsObject = options.every(o => typeof o === "object") |
||||
|
|
||||
|
$: selectedOption = isOptionsObject |
||||
|
? options.find(o => o.value === value) |
||||
|
: {} |
||||
|
|
||||
|
$: displayLabel = |
||||
|
selectedOption && selectedOption.label ? selectedOption.label : value || "" |
||||
</script> |
</script> |
||||
|
|
||||
<select |
<div |
||||
class="uk-select uk-form-small" |
tabindex="0" |
||||
{value} |
bind:this={select} |
||||
on:change={ev => onChange(ev.target.value)}> |
class="bb-select-container" |
||||
{#if isOptionsObject} |
on:click={() => toggleSelect(!open)}> |
||||
{#each options as { value, label }} |
<div class="bb-select-anchor selected"> |
||||
<option {...handleStyleBind(value || label)} value={value || label}> |
<span>{displayLabel}</span> |
||||
{label} |
<i bind:this={icon} class="ri-arrow-down-s-fill" /> |
||||
</option> |
</div> |
||||
{/each} |
<div |
||||
{:else} |
bind:this={selectMenu} |
||||
{#each options as value} |
style={menuStyle} |
||||
<option {...handleStyleBind(value)} {value}>{value}</option> |
class="bb-select-menu" |
||||
{/each} |
class:open> |
||||
{/if} |
<ul> |
||||
</select> |
{#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> |
||||
|
{#if open} |
||||
|
<div on:click|self={() => toggleSelect(false)} class="overlay" /> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
.overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.bb-select-container { |
||||
|
position: relative; |
||||
|
outline: none; |
||||
|
width: 160px; |
||||
|
height: 32px; |
||||
|
cursor: pointer; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor { |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
padding: 5px 10px; |
||||
|
background-color: #f2f2f2; |
||||
|
border-radius: 2px; |
||||
|
border: 1px solid var(--grey-dark); |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor > span { |
||||
|
color: #565a66; |
||||
|
font-weight: 500; |
||||
|
width: 140px; |
||||
|
overflow-x: hidden; |
||||
|
} |
||||
|
|
||||
|
.bb-select-anchor > i { |
||||
|
transition: transform 0.13s ease; |
||||
|
transform-origin: center; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
color: #565a66; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.bb-select-menu { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
box-sizing: border-box; |
||||
|
flex-direction: column; |
||||
|
opacity: 0; |
||||
|
width: 160px; |
||||
|
z-index: 2; |
||||
|
color: #808192; |
||||
|
font-weight: 500; |
||||
|
height: fit-content !important; |
||||
|
border-bottom-left-radius: 2px; |
||||
|
border-bottom-right-radius: 2px; |
||||
|
border-right: 1px solid var(--grey-dark); |
||||
|
border-left: 1px solid var(--grey-dark); |
||||
|
border-bottom: 1px solid var(--grey-dark); |
||||
|
background-color: #f2f2f2; |
||||
|
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; |
||||
|
padding: 5px 0px; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
height: auto; |
||||
|
padding: 5px 0px; |
||||
|
cursor: pointer; |
||||
|
padding-left: 10px |
||||
|
} |
||||
|
|
||||
|
li:hover { |
||||
|
background-color:#e6e6e6 |
||||
|
} |
||||
|
</style> |
||||
|
|||||
@ -0,0 +1,33 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
export let value = "" |
||||
|
export let onChange = value => {} |
||||
|
export let options = [] |
||||
|
export let initialValue = "" |
||||
|
export let styleBindingProperty = "" |
||||
|
|
||||
|
const handleStyleBind = value => |
||||
|
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
||||
|
|
||||
|
$: isOptionsObject = options.every(o => typeof o === "object") |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!value && !!initialValue) { |
||||
|
value = initialValue |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<select {value} on:change={ev => onChange(ev.target.value)}> |
||||
|
{#if isOptionsObject} |
||||
|
{#each options as { value, label }} |
||||
|
<option {...handleStyleBind(value || label)} value={value || label}> |
||||
|
{label} |
||||
|
</option> |
||||
|
{/each} |
||||
|
{:else} |
||||
|
{#each options as value} |
||||
|
<option {...handleStyleBind(value)} {value}>{value}</option> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</select> |
||||
@ -0,0 +1,14 @@ |
|||||
|
export const buildStyle = styles => { |
||||
|
let str = "" |
||||
|
for (let s in styles) { |
||||
|
if (styles[s]) { |
||||
|
let key = convertCamel(s) |
||||
|
str += `${key}: ${styles[s]}; ` |
||||
|
} |
||||
|
} |
||||
|
return str |
||||
|
} |
||||
|
|
||||
|
export const convertCamel = str => { |
||||
|
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) |
||||
|
} |
||||
Loading…
Reference in new issue