Add new nav item configuration list, popover and initial drawer. Fix BBUI combobox overflow
This commit is contained in:
parent
003b0b8698
commit
26c4e504bf
8 changed files with 291 additions and 208 deletions
|
@ -4,6 +4,7 @@
|
|||
import "@spectrum-css/menu/dist/index-vars.css"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import clickOutside from "../../Actions/click_outside"
|
||||
import Popover from "../../Popover/Popover.svelte"
|
||||
|
||||
export let value = null
|
||||
export let id = null
|
||||
|
@ -15,8 +16,10 @@
|
|||
export let getOptionValue = option => option
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let open = false
|
||||
let focus = false
|
||||
let anchor
|
||||
|
||||
const selectOption = value => {
|
||||
dispatch("change", value)
|
||||
|
@ -35,11 +38,11 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="spectrum-InputGroup"
|
||||
class:is-focused={open || focus}
|
||||
class:is-disabled={disabled}
|
||||
bind:this={anchor}
|
||||
>
|
||||
<div
|
||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||
|
@ -67,7 +70,7 @@
|
|||
tabindex="-1"
|
||||
aria-haspopup="true"
|
||||
{disabled}
|
||||
on:click={() => (open = true)}
|
||||
on:click={() => (open = !open)}
|
||||
>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon spectrum-InputGroup-icon"
|
||||
|
@ -77,42 +80,44 @@
|
|||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
</svg>
|
||||
</button>
|
||||
{#if open}
|
||||
<div
|
||||
class="spectrum-Popover spectrum-Popover--bottom is-open"
|
||||
use:clickOutside={() => {
|
||||
open = false
|
||||
}}
|
||||
>
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if options && Array.isArray(options)}
|
||||
{#each options as option}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={getOptionValue(option) === value}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onPick(getOptionValue(option))}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel"
|
||||
>{getOptionLabel(option)}</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}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Popover
|
||||
{anchor}
|
||||
{open}
|
||||
align="left"
|
||||
on:close={() => (open = false)}
|
||||
useAnchorWidth
|
||||
>
|
||||
<div class="popover-content" use:clickOutside={() => (open = false)}>
|
||||
<ul class="spectrum-Menu" role="listbox">
|
||||
{#if options && Array.isArray(options)}
|
||||
{#each options as option}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={getOptionValue(option) === value}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => onPick(getOptionValue(option))}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel">{getOptionLabel(option)}</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}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.spectrum-InputGroup {
|
||||
min-width: 0;
|
||||
|
@ -124,10 +129,13 @@
|
|||
.spectrum-Textfield-input {
|
||||
width: 0;
|
||||
}
|
||||
.spectrum-Popover {
|
||||
max-height: 240px;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
top: 100%;
|
||||
|
||||
/* Popover */
|
||||
.popover-content {
|
||||
display: contents;
|
||||
}
|
||||
.popover-content:not(.auto-width) .spectrum-Menu-itemLabel {
|
||||
width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
const dispatch = createEventDispatcher()
|
||||
|
||||
let button
|
||||
let popover
|
||||
let component
|
||||
|
||||
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||
|
@ -146,11 +145,11 @@
|
|||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Popover
|
||||
anchor={customAnchor ? customAnchor : button}
|
||||
align={align || "left"}
|
||||
bind:this={popover}
|
||||
{open}
|
||||
on:close={() => (open = false)}
|
||||
useAnchorWidth={!autoWidth}
|
||||
|
@ -266,16 +265,6 @@
|
|||
width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.subtitle-text {
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
display: block;
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.spectrum-Picker-label.auto-width {
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
|
@ -356,11 +345,9 @@
|
|||
.option-extra.icon.field-icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.option-tag {
|
||||
margin: 0 var(--spacing-m) 0 var(--spacing-m);
|
||||
}
|
||||
|
||||
.option-tag :global(.spectrum-Tags-item > .spectrum-Icon) {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
@ -374,4 +361,13 @@
|
|||
.loading--withAutocomplete {
|
||||
top: calc(34px + var(--spacing-m));
|
||||
}
|
||||
|
||||
.subtitle-text {
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
display: block;
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
<script>
|
||||
import { Icon, Popover, Layout } from "@budibase/bbui"
|
||||
import { Icon, Popover, RadioGroup } from "@budibase/bbui"
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import PropertyControl from "components/design/settings/controls/PropertyControl.svelte"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||
import SubLinksDrawer from "./SubLinksDrawer.svelte"
|
||||
import { screenStore } from "stores/builder"
|
||||
|
||||
export let anchor
|
||||
export let navItem
|
||||
export let bindings
|
||||
|
||||
const draggable = getContext("draggable")
|
||||
const dispatch = createEventDispatcher()
|
||||
const typeOptions = [
|
||||
{ label: "Inline link", value: "link" },
|
||||
{ label: "Open sub links", value: "sublinks" },
|
||||
]
|
||||
|
||||
let popover
|
||||
let drawers = []
|
||||
let open = false
|
||||
|
||||
$: console.log(anchor)
|
||||
$: urlOptions = $screenStore.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
.filter(x => x != null)
|
||||
.sort()
|
||||
|
||||
// Auto hide the component when another item is selected
|
||||
$: if (open && $draggable.selected !== navItem.id) {
|
||||
|
@ -25,7 +39,7 @@
|
|||
open = true
|
||||
}
|
||||
|
||||
const updateNavItem = async (setting, value) => {
|
||||
const update = setting => async value => {
|
||||
dispatch("change", {
|
||||
...navItem,
|
||||
[setting]: value,
|
||||
|
@ -33,17 +47,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Icon
|
||||
name="Settings"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => {
|
||||
if (!open) {
|
||||
popover.show()
|
||||
open = true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Icon name={navItem.type === "sublinks" ? "Dropdown" : "Link"} size="S" />
|
||||
|
||||
<Popover
|
||||
bind:this={popover}
|
||||
|
@ -64,13 +68,64 @@
|
|||
maxHeight={600}
|
||||
offset={18}
|
||||
>
|
||||
<span class="popover-wrap">
|
||||
<Layout noPadding />
|
||||
</span>
|
||||
<div class="settings">
|
||||
<PropertyControl
|
||||
label="Nav item"
|
||||
control={RadioGroup}
|
||||
value={navItem.type}
|
||||
onChange={update("type")}
|
||||
props={{
|
||||
options: typeOptions,
|
||||
}}
|
||||
/>
|
||||
<PropertyControl
|
||||
label="Label"
|
||||
control={DrawerBindableInput}
|
||||
value={navItem.text}
|
||||
onChange={update("text")}
|
||||
{bindings}
|
||||
props={{
|
||||
updateOnChange: false,
|
||||
}}
|
||||
/>
|
||||
{#if navItem.type === "sublinks"}
|
||||
<PropertyControl
|
||||
label="Sub links"
|
||||
control={SubLinksDrawer}
|
||||
value={navItem.subLinks}
|
||||
onChange={update("subLinks")}
|
||||
/>
|
||||
{:else}
|
||||
<PropertyControl
|
||||
label="Link"
|
||||
control={DrawerBindableCombobox}
|
||||
value={navItem.url}
|
||||
onChange={update("url")}
|
||||
{bindings}
|
||||
props={{
|
||||
options: urlOptions,
|
||||
appendBindingsAsOptions: false,
|
||||
placeholder: null,
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<PropertyControl
|
||||
label="Access"
|
||||
control={RoleSelect}
|
||||
value={navItem.roleId}
|
||||
onChange={update("roleId")}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.popover-wrap {
|
||||
background-color: var(--spectrum-alias-background-color-primary);
|
||||
.settings {
|
||||
background: var(--spectrum-alias-background-color-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
DrawerContent,
|
||||
Layout,
|
||||
Input,
|
||||
Combobox,
|
||||
} from "@budibase/bbui"
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import { generate } from "shortid"
|
||||
import { screenStore } from "stores/builder"
|
||||
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
||||
|
||||
export let links = []
|
||||
|
||||
const flipDurationMs = 150
|
||||
let dragDisabled = true
|
||||
|
||||
$: links.forEach(link => {
|
||||
if (!link.id) {
|
||||
link.id = generate()
|
||||
}
|
||||
})
|
||||
$: urlOptions = $screenStore.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
.filter(x => x != null)
|
||||
|
||||
const addLink = () => {
|
||||
links = [...links, {}]
|
||||
}
|
||||
|
||||
const removeLink = id => {
|
||||
links = links.filter(link => link.id !== id)
|
||||
}
|
||||
|
||||
const updateLinks = e => {
|
||||
links = e.detail.items
|
||||
}
|
||||
|
||||
const handleFinalize = e => {
|
||||
updateLinks(e)
|
||||
dragDisabled = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<DrawerContent>
|
||||
<div class="container">
|
||||
<Layout noPadding gap="S">
|
||||
{#if links?.length}
|
||||
<div
|
||||
class="links"
|
||||
use:dndzone={{
|
||||
items: links,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
dragDisabled,
|
||||
}}
|
||||
on:finalize={handleFinalize}
|
||||
on:consider={updateLinks}
|
||||
>
|
||||
{#each links as link (link.id)}
|
||||
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
||||
<div
|
||||
class="handle"
|
||||
aria-label="drag-handle"
|
||||
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||
on:mousedown={() => (dragDisabled = false)}
|
||||
>
|
||||
<Icon name="DragHandle" size="XL" />
|
||||
</div>
|
||||
<Input bind:value={link.text} placeholder="Text" />
|
||||
<Combobox
|
||||
bind:value={link.url}
|
||||
placeholder="URL"
|
||||
options={urlOptions}
|
||||
/>
|
||||
<RoleSelect bind:value={link.roleId} placeholder="Minimum role" />
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => removeLink(link.id)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<Button secondary icon="Add" on:click={addLink}>Add Link</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.link {
|
||||
gap: var(--spacing-l);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: var(--border-radius-s);
|
||||
transition: background-color ease-in-out 130ms;
|
||||
}
|
||||
.link:hover {
|
||||
background-color: var(--spectrum-global-color-gray-100);
|
||||
}
|
||||
.link > :global(.spectrum-Form-item) {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
.handle {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
|
@ -1,16 +1,20 @@
|
|||
<script>
|
||||
import { runtimeToReadableBinding } from "dataBinding"
|
||||
import EditNavItemPopover from "./EditNavItemPopover.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
|
||||
export let item
|
||||
export let removeNavItem
|
||||
export let anchor
|
||||
export let bindings
|
||||
|
||||
$: text = runtimeToReadableBinding(bindings, item.text)
|
||||
</script>
|
||||
|
||||
<div class="list-item-body">
|
||||
<div class="list-item-left">
|
||||
<EditNavItemPopover {anchor} navItem={item} on:change />
|
||||
<div class="field-label">{item.text}</div>
|
||||
<EditNavItemPopover {anchor} {bindings} navItem={item} on:change />
|
||||
<div class="field-label">{text}</div>
|
||||
</div>
|
||||
<div class="list-item-right">
|
||||
<Icon
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
import { getSequentialName } from "helpers/duplicate"
|
||||
import { Constants } from "@budibase/frontend-core"
|
||||
|
||||
export let bindings
|
||||
|
||||
$: navItems = enrichNavItems($navigationStore.links)
|
||||
$: navItemProps = {
|
||||
removeNavItem,
|
||||
bindings,
|
||||
}
|
||||
|
||||
const enrichNavItems = links => {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<script>
|
||||
import {
|
||||
ActionButton,
|
||||
Button,
|
||||
Icon,
|
||||
DrawerContent,
|
||||
Layout,
|
||||
Input,
|
||||
Combobox,
|
||||
Drawer,
|
||||
} from "@budibase/bbui"
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import { generate } from "shortid"
|
||||
import { screenStore } from "stores/builder"
|
||||
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
||||
|
||||
export let value = []
|
||||
|
||||
const flipDurationMs = 150
|
||||
|
||||
let dragDisabled = true
|
||||
let drawer
|
||||
|
||||
$: links = value || []
|
||||
$: links.forEach(link => {
|
||||
if (!link.id) {
|
||||
link.id = generate()
|
||||
}
|
||||
})
|
||||
$: urlOptions = $screenStore.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
.filter(x => x != null)
|
||||
|
||||
const addLink = () => {
|
||||
links = [...links, {}]
|
||||
}
|
||||
|
||||
const removeLink = id => {
|
||||
links = links.filter(link => link.id !== id)
|
||||
}
|
||||
|
||||
const updateLinks = e => {
|
||||
links = e.detail.items
|
||||
}
|
||||
|
||||
const handleFinalize = e => {
|
||||
updateLinks(e)
|
||||
dragDisabled = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<Drawer bind:this={drawer}>
|
||||
<DrawerContent>
|
||||
<div class="container">
|
||||
<Layout noPadding gap="S">
|
||||
{#if links?.length}
|
||||
<div
|
||||
class="links"
|
||||
use:dndzone={{
|
||||
items: links,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
dragDisabled,
|
||||
}}
|
||||
on:finalize={handleFinalize}
|
||||
on:consider={updateLinks}
|
||||
>
|
||||
{#each links as link (link.id)}
|
||||
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
||||
<div
|
||||
class="handle"
|
||||
aria-label="drag-handle"
|
||||
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||
on:mousedown={() => (dragDisabled = false)}
|
||||
>
|
||||
<Icon name="DragHandle" size="XL" />
|
||||
</div>
|
||||
<Input bind:value={link.text} placeholder="Text" />
|
||||
<Combobox
|
||||
bind:value={link.url}
|
||||
placeholder="URL"
|
||||
options={urlOptions}
|
||||
/>
|
||||
<RoleSelect
|
||||
bind:value={link.roleId}
|
||||
placeholder="Minimum role"
|
||||
/>
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => removeLink(link.id)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<Button secondary icon="Add" on:click={addLink}>Add Link</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="button">
|
||||
<ActionButton>No sub links</ActionButton>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.button :global(.spectrum-ActionButton) {
|
||||
width: 100%;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.link {
|
||||
gap: var(--spacing-l);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: var(--border-radius-s);
|
||||
transition: background-color ease-in-out 130ms;
|
||||
}
|
||||
.link:hover {
|
||||
background-color: var(--spectrum-global-color-gray-100);
|
||||
}
|
||||
.link > :global(.spectrum-Form-item) {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
.handle {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
|
@ -75,7 +75,7 @@
|
|||
|
||||
{#if $selectedScreen?.showNavigation}
|
||||
<DetailSummary name="Customize" initiallyShow collapsible={false}>
|
||||
<NavItemConfiguration />
|
||||
<NavItemConfiguration {bindings} />
|
||||
<div class="settings">
|
||||
<PropertyControl
|
||||
label="Position"
|
||||
|
|
Loading…
Reference in a new issue