Add support for multiselect type

This commit is contained in:
Andrew Kingston 2023-02-20 19:04:22 +00:00
parent 654c348e4e
commit e26163e274
3 changed files with 74 additions and 49 deletions

View File

@ -0,0 +1,5 @@
<script>
import OptionsCell from "./OptionsCell.svelte"
</script>
<OptionsCell {...$$props} multi />

View File

@ -5,12 +5,14 @@
export let schema export let schema
export let selected = false export let selected = false
export let onChange export let onChange
export let multi = false
const options = schema?.constraints?.inclusion || [] const options = schema?.constraints?.inclusion || []
let open = false let open = false
$: color = getColor(value) $: values = Array.isArray(value) ? value : [value].filter(x => x != null)
$: unselectedOptions = options.filter(x => !values.includes(x))
$: { $: {
// Close when deselected // Close when deselected
if (!selected) { if (!selected) {
@ -26,44 +28,59 @@
return `hsla(${((index + 1) * 222) % 360}, 90%, 75%, 0.3)` return `hsla(${((index + 1) * 222) % 360}, 90%, 75%, 0.3)`
} }
const toggle = () => { const toggleOption = option => {
open = !open if (!multi) {
onChange(option)
} else {
if (values.includes(option)) {
onChange(values.filter(x => x !== option))
} else {
onChange([...values, option])
}
}
} }
</script> </script>
<div <div
class="container" class="container"
class:multi
class:selected class:selected
class:open class:open
on:click={selected ? toggle : null} on:click={selected ? () => (open = true) : null}
> >
<div class="values">
{#each values as val (val)}
{@const color = getColor(val)}
{#if color} {#if color}
<div class="badge text" style="--color: {color}"> <div class="badge text" style="--color: {color}">
{value} {val}
</div> </div>
{:else} {:else}
<div class="text"> <div class="text">
{value || ""} {val || ""}
</div> </div>
{/if} {/if}
{/each}
</div>
{#if selected} {#if selected}
<Icon name="ChevronDown" /> <Icon name="ChevronDown" />
{/if} {/if}
{#if open} {#if open}
<div class="options"> <div class="options">
{#if value} {#each values as val (val)}
<div class="option"> {@const color = getColor(val)}
<div class="option" on:click={() => toggleOption(val)}>
<div class="badge text" style="--color: {color}"> <div class="badge text" style="--color: {color}">
{value} {val}
</div> </div>
<Icon <Icon
name="Checkmark" name="Checkmark"
color="var(--spectrum-global-color-blue-400)" color="var(--spectrum-global-color-blue-400)"
/> />
</div> </div>
{/if} {/each}
{#each options.filter(x => x !== value) as option} {#each unselectedOptions as option (option)}
<div class="option" on:click={() => onChange(option)}> <div class="option" on:click={() => toggleOption(option)}>
<div class="badge text" style="--color: {getColor(option)}"> <div class="badge text" style="--color: {getColor(option)}">
{option} {option}
</div> </div>
@ -87,11 +104,24 @@
.container.selected:hover { .container.selected:hover {
cursor: pointer; cursor: pointer;
} }
.values {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
flex: 1 1 auto;
width: 0;
gap: 4px;
overflow: hidden;
}
.text { .text {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.multi .text {
flex: 0 0 auto;
}
.badge { .badge {
padding: 2px 8px; padding: 2px 8px;
background: var(--color); background: var(--color);

View File

@ -5,6 +5,7 @@
import TextCell from "./TextCell.svelte" import TextCell from "./TextCell.svelte"
import OptionsCell from "./OptionsCell.svelte" import OptionsCell from "./OptionsCell.svelte"
import DateCell from "./DateCell.svelte" import DateCell from "./DateCell.svelte"
import MultiSelectCell from "./MultiSelectCell.svelte"
export let table export let table
export let filter export let filter
@ -15,7 +16,7 @@
getContext("sdk") getContext("sdk")
const component = getContext("component") const component = getContext("component")
const limit = 100 const limit = 100
const defaultWidth = 200 const defaultWidth = 160
const minWidth = 100 const minWidth = 100
let widths let widths
@ -36,7 +37,6 @@
}) })
$: fields = Object.keys($fetch.schema || {}) $: fields = Object.keys($fetch.schema || {})
$: initWidths(fields) $: initWidths(fields)
$: sliderPositions = getSliderPositions(widths)
$: gridStyles = getGridStyles(widths) $: gridStyles = getGridStyles(widths)
$: schema = $fetch.schema $: schema = $fetch.schema
$: rowCount = $fetch.rows?.length || 0 $: rowCount = $fetch.rows?.length || 0
@ -65,14 +65,7 @@
if (!widths?.length) { if (!widths?.length) {
return "--grid: 1fr;" return "--grid: 1fr;"
} }
return `--grid: 50px ${widths.map(x => `${x}px`).join(" ")} 180px;` return `--grid: 40px ${widths.map(x => `${x}px`).join(" ")} 180px;`
}
const getSliderPositions = widths => {
let offset = 50
return widths.map(width => {
return (offset += width)
})
} }
const handleScroll = e => { const handleScroll = e => {
@ -88,6 +81,8 @@
return OptionsCell return OptionsCell
} else if (type === "datetime") { } else if (type === "datetime") {
return DateCell return DateCell
} else if (type === "array") {
return MultiSelectCell
} }
return TextCell return TextCell
} }
@ -262,20 +257,12 @@
<span> <span>
{field} {field}
</span> </span>
<div class="slider" on:mousedown={e => startResizing(fieldIdx, e)} />
</div> </div>
{/each} {/each}
<!-- Horizontal spacer --> <!-- Horizontal spacer -->
<div /> <div />
<!-- Sliders for resizing columns -->
{#each sliderPositions as left, idx}
<div
class="slider"
on:mousedown={e => startResizing(idx, e)}
style="--left: {left}px"
/>
{/each}
<!-- All real rows --> <!-- All real rows -->
{#each rows as row, rowIdx (row._id)} {#each rows as row, rowIdx (row._id)}
{@const rowSelected = !!selectedRows[row._id]} {@const rowSelected = !!selectedRows[row._id]}
@ -368,7 +355,7 @@
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
overflow: auto; overflow: auto;
max-height: 800px; max-height: 600px;
position: relative; position: relative;
cursor: default; cursor: default;
} }
@ -444,7 +431,7 @@
} }
.cell.sticky { .cell.sticky {
position: sticky; position: sticky;
left: 50px; left: 40px;
z-index: 2; z-index: 2;
} }
.cell.sticky.selected { .cell.sticky.selected {
@ -479,25 +466,28 @@
/* Column resizing */ /* Column resizing */
.slider { .slider {
position: absolute; position: absolute;
z-index: 5;
left: var(--left);
top: 0; top: 0;
right: 0;
width: 16px; width: 16px;
height: 32px; height: 100%;
transform: translateX(-50%);
} }
.slider:hover:after { .slider:after {
opacity: 0;
content: " "; content: " ";
position: absolute; position: absolute;
width: 2px; width: 4px;
left: 50%; right: 0;
top: 0;
height: 100%; height: 100%;
background: var(--spectrum-global-color-blue-400); background: var(--spectrum-global-color-gray-600);
transform: translateX(-50%); transition: opacity 130ms ease-out;
} }
.slider:hover { .slider:hover {
cursor: col-resize; cursor: col-resize;
} }
.slider:hover:after {
opacity: 1;
}
.sticky.shadow:after { .sticky.shadow:after {
content: " "; content: " ";