add ability to select groups from dropdown
This commit is contained in:
parent
45fc55278e
commit
5ce3861ef2
|
@ -0,0 +1,406 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/menu/dist/index-vars.css"
|
||||||
|
import { fly } from "svelte/transition"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import clickOutside from "../../Actions/click_outside"
|
||||||
|
import Search from "./Search.svelte"
|
||||||
|
import Icon from "../../Icon/Icon.svelte"
|
||||||
|
import StatusLight from "../../StatusLight/StatusLight.svelte"
|
||||||
|
import Detail from "../../Typography/Detail.svelte"
|
||||||
|
|
||||||
|
export let primaryLabel = ""
|
||||||
|
export let id = null
|
||||||
|
export let placeholder = "Choose an option or type"
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let updateOnChange = true
|
||||||
|
export let error = null
|
||||||
|
export let secondaryOptions = []
|
||||||
|
export let primaryOptions = []
|
||||||
|
export let secondaryFieldText = ""
|
||||||
|
export let secondaryFieldIcon = ""
|
||||||
|
export let secondaryFieldColour = ""
|
||||||
|
export let getPrimaryOptionLabel = option => option
|
||||||
|
export let getPrimaryOptionValue = option => option
|
||||||
|
export let getPrimaryOptionColour = () => null
|
||||||
|
export let getPrimaryOptionIcon = () => null
|
||||||
|
export let getSecondaryOptionLabel = option => option
|
||||||
|
export let getSecondaryOptionValue = option => option
|
||||||
|
export let getSecondaryOptionColour = () => null
|
||||||
|
export let onSelectOption = () => {}
|
||||||
|
export let autoWidth = false
|
||||||
|
export let autocomplete = false
|
||||||
|
export let isOptionSelected = () => false
|
||||||
|
export let isPlaceholder = false
|
||||||
|
export let placeholderOption = null
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
let primaryOpen = false
|
||||||
|
let secondaryOpen = false
|
||||||
|
let focus = false
|
||||||
|
let searchTerm = null
|
||||||
|
|
||||||
|
$: groupTitles = Object.keys(primaryOptions)
|
||||||
|
$: filteredOptions = getFilteredOptions(
|
||||||
|
primaryOptions,
|
||||||
|
searchTerm,
|
||||||
|
getPrimaryOptionLabel
|
||||||
|
)
|
||||||
|
let iconData
|
||||||
|
/*
|
||||||
|
$: iconData = primaryOptions?.find(x => {
|
||||||
|
return x.name === primaryFieldText
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
const updateValue = newValue => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dispatch("change", newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickPrimary = () => {
|
||||||
|
dispatch("click")
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
primaryOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickSecondary = () => {
|
||||||
|
dispatch("click")
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secondaryOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPickPrimary = newValue => {
|
||||||
|
dispatch("pickprimary", newValue)
|
||||||
|
primaryOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPickSecondary = newValue => {
|
||||||
|
console.log(newValue)
|
||||||
|
dispatch("picksecondary", newValue)
|
||||||
|
secondaryOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFocus = () => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
focus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur = event => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
focus = false
|
||||||
|
updateValue(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInput = event => {
|
||||||
|
if (readonly || !updateOnChange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateValue(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateValueOnEnter = event => {
|
||||||
|
if (readonly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
updateValue(event.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFilteredOptions = (options, term, getLabel) => {
|
||||||
|
if (autocomplete && term) {
|
||||||
|
const lowerCaseTerm = term.toLowerCase()
|
||||||
|
return options.filter(option => {
|
||||||
|
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="spectrum-InputGroup"
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
class:is-disabled={disabled}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
class:is-disabled={disabled}
|
||||||
|
class:is-focused={focus}
|
||||||
|
style="width: 70%"
|
||||||
|
>
|
||||||
|
{#if iconData}
|
||||||
|
<svg
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
class="spectrum-Icon iconPadding"
|
||||||
|
style="color: {iconData?.color}"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-{iconData?.icon}" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<input
|
||||||
|
{id}
|
||||||
|
on:click={() => (primaryOpen = true)}
|
||||||
|
on:blur
|
||||||
|
on:focus
|
||||||
|
on:input
|
||||||
|
on:keyup
|
||||||
|
on:blur={onBlur}
|
||||||
|
on:input={onInput}
|
||||||
|
on:keyup={updateValueOnEnter}
|
||||||
|
value={primaryLabel || ""}
|
||||||
|
placeholder={placeholder || ""}
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||||
|
class:labelPadding={iconData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if primaryOpen}
|
||||||
|
<div
|
||||||
|
use:clickOutside={() => (primaryOpen = false)}
|
||||||
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
|
class:auto-width={autoWidth}
|
||||||
|
style="width: 70%"
|
||||||
|
>
|
||||||
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
|
{#if placeholderOption}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item placeholder"
|
||||||
|
class:is-selected={isPlaceholder}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => onSelectOption(null)}
|
||||||
|
>
|
||||||
|
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{#each groupTitles as title}
|
||||||
|
<div class="spectrum-Menu-item">
|
||||||
|
<Detail>{title}</Detail>
|
||||||
|
</div>
|
||||||
|
{#if primaryOptions}
|
||||||
|
{#each primaryOptions[title].data as option, idx}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item"
|
||||||
|
class:is-selected={isOptionSelected(
|
||||||
|
getPrimaryOptionValue(option, idx)
|
||||||
|
)}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() =>
|
||||||
|
onPickPrimary({
|
||||||
|
value: primaryOptions[title].getValue(option),
|
||||||
|
label: primaryOptions[title].getLabel(option),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{#if primaryOptions[title].getIcon(option)}
|
||||||
|
<div
|
||||||
|
style="background: {primaryOptions[title].getColour(
|
||||||
|
option
|
||||||
|
)};"
|
||||||
|
class="circle"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Icon
|
||||||
|
size="S"
|
||||||
|
name={primaryOptions[title].getIcon(option)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if getPrimaryOptionColour(option, idx)}
|
||||||
|
<span class="option-left">
|
||||||
|
<StatusLight color={getPrimaryOptionColour(option, idx)} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<span class="spectrum-Menu-itemLabel">
|
||||||
|
<span
|
||||||
|
class:spacing-group={primaryOptions[title].getIcon(option)}
|
||||||
|
>
|
||||||
|
{primaryOptions[title].getLabel(option)}
|
||||||
|
<span />
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
{#if getPrimaryOptionIcon(option, idx) && getPrimaryOptionColour(option, idx)}
|
||||||
|
<span class="option-right">
|
||||||
|
<StatusLight
|
||||||
|
color={getPrimaryOptionColour(option, idx)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div style="width: 30%">
|
||||||
|
<button
|
||||||
|
{id}
|
||||||
|
class="spectrum-Picker spectrum-Picker--sizeM override-borders"
|
||||||
|
{disabled}
|
||||||
|
class:is-open={secondaryOpen}
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
on:mousedown={onClickSecondary}
|
||||||
|
>
|
||||||
|
{#if secondaryFieldIcon}
|
||||||
|
<span class="option-left">
|
||||||
|
<Icon name={secondaryFieldIcon} />
|
||||||
|
</span>
|
||||||
|
{:else if secondaryFieldColour}
|
||||||
|
<span class="option-left">
|
||||||
|
<StatusLight color={secondaryFieldColour} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<span class:auto-width={autoWidth} class="spectrum-Picker-label">
|
||||||
|
{secondaryFieldText}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{#if secondaryOpen}
|
||||||
|
<div
|
||||||
|
use:clickOutside={() => (secondaryOpen = false)}
|
||||||
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
|
style="width: 30%"
|
||||||
|
>
|
||||||
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
|
{#each secondaryOptions as option, idx}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item"
|
||||||
|
class:is-selected={isOptionSelected(
|
||||||
|
getSecondaryOptionValue(option, idx)
|
||||||
|
)}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() =>
|
||||||
|
onPickSecondary(getSecondaryOptionValue(option, idx))}
|
||||||
|
>
|
||||||
|
{#if getSecondaryOptionColour(option, idx)}
|
||||||
|
<span class="option-left">
|
||||||
|
<StatusLight color={getSecondaryOptionColour(option, idx)} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<span class="spectrum-Menu-itemLabel">
|
||||||
|
{getSecondaryOptionLabel(option, idx)}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spacing-group {
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.spectrum-InputGroup {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.override-borders {
|
||||||
|
border-top-left-radius: 0px;
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.override-borders-left {
|
||||||
|
border-top-right-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
border-right: 0px;
|
||||||
|
}
|
||||||
|
.spectrum-Popover {
|
||||||
|
max-height: 240px;
|
||||||
|
z-index: 999;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-left {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
.option-right {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 28px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 48px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
width: 28px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle > div {
|
||||||
|
position: absolute;
|
||||||
|
text-decoration: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconPadding {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: silver;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelPadding {
|
||||||
|
padding-left: calc(1em + 10px + 8px);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,129 @@
|
||||||
|
<script>
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import PickerDropdown from "./Core/PickerDropdown.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let primaryValue = null
|
||||||
|
export let secondaryValue = null
|
||||||
|
export let inputType = "text"
|
||||||
|
export let label = null
|
||||||
|
export let labelPosition = "above"
|
||||||
|
export let primaryPlaceholder = "test"
|
||||||
|
export let secondaryPlaceholder = null
|
||||||
|
export let autocomplete
|
||||||
|
export let placeholder = null
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let updateOnChange = true
|
||||||
|
export let getSecondaryOptionLabel = option =>
|
||||||
|
extractProperty(option, "label")
|
||||||
|
export let getSecondaryOptionValue = option =>
|
||||||
|
extractProperty(option, "value")
|
||||||
|
export let getSecondaryOptionColour = () => {}
|
||||||
|
export let getSecondaryOptionIcon = () => {}
|
||||||
|
export let quiet = false
|
||||||
|
export let dataCy
|
||||||
|
export let autofocus
|
||||||
|
export let primaryOptions = []
|
||||||
|
export let secondaryOptions = []
|
||||||
|
|
||||||
|
let primaryLabel
|
||||||
|
let secondaryLabel
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: secondaryFieldText = getSecondaryFieldText(
|
||||||
|
secondaryValue,
|
||||||
|
secondaryOptions,
|
||||||
|
secondaryPlaceholder
|
||||||
|
)
|
||||||
|
$: secondaryFieldIcon = getSecondaryFieldAttribute(
|
||||||
|
getSecondaryOptionIcon,
|
||||||
|
secondaryValue,
|
||||||
|
secondaryOptions
|
||||||
|
)
|
||||||
|
$: secondaryFieldColour = getSecondaryFieldAttribute(
|
||||||
|
getSecondaryOptionColour,
|
||||||
|
secondaryValue,
|
||||||
|
secondaryOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
const getSecondaryFieldAttribute = (getAttribute, value, options) => {
|
||||||
|
// Wait for options to load if there is a value but no options
|
||||||
|
|
||||||
|
if (!options?.length) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = options.findIndex(
|
||||||
|
(option, idx) => getSecondaryOptionValue(option, idx) === value
|
||||||
|
)
|
||||||
|
|
||||||
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSecondaryFieldText = (value, options, placeholder) => {
|
||||||
|
// Always use placeholder if no value
|
||||||
|
if (value == null || value === "") {
|
||||||
|
return placeholder || "Choose an option"
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSecondaryFieldAttribute(getSecondaryOptionLabel, value, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPickPrimary = e => {
|
||||||
|
console.log("hello")
|
||||||
|
|
||||||
|
primaryLabel = e.detail.label
|
||||||
|
primaryValue = e.detail.value
|
||||||
|
dispatch("pickprimary", e.detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPickSecondary = e => {
|
||||||
|
console.log("hello222")
|
||||||
|
secondaryValue = e.detail
|
||||||
|
dispatch("picksecondary", e.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractProperty = (value, property) => {
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return value[property]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {label} {labelPosition} {error}>
|
||||||
|
<PickerDropdown
|
||||||
|
{autocomplete}
|
||||||
|
{dataCy}
|
||||||
|
{updateOnChange}
|
||||||
|
{error}
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
{placeholder}
|
||||||
|
{inputType}
|
||||||
|
{quiet}
|
||||||
|
{autofocus}
|
||||||
|
{primaryOptions}
|
||||||
|
{secondaryOptions}
|
||||||
|
{getSecondaryOptionLabel}
|
||||||
|
{getSecondaryOptionValue}
|
||||||
|
{getSecondaryOptionIcon}
|
||||||
|
{getSecondaryOptionColour}
|
||||||
|
{secondaryFieldText}
|
||||||
|
{secondaryFieldIcon}
|
||||||
|
{secondaryFieldColour}
|
||||||
|
{primaryValue}
|
||||||
|
{secondaryValue}
|
||||||
|
{primaryLabel}
|
||||||
|
{secondaryLabel}
|
||||||
|
on:pickprimary={onPickPrimary}
|
||||||
|
on:picksecondary={onPickSecondary}
|
||||||
|
on:click
|
||||||
|
on:input
|
||||||
|
on:blur
|
||||||
|
on:focus
|
||||||
|
on:keyup
|
||||||
|
/>
|
||||||
|
</Field>
|
Loading…
Reference in New Issue