Add new nav item configuration list, popover and initial drawer. Fix BBUI combobox overflow

This commit is contained in:
Andrew Kingston 2024-03-28 12:00:36 +00:00
parent 003b0b8698
commit 26c4e504bf
8 changed files with 291 additions and 208 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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 => {

View File

@ -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>

View File

@ -75,7 +75,7 @@
{#if $selectedScreen?.showNavigation}
<DetailSummary name="Customize" initiallyShow collapsible={false}>
<NavItemConfiguration />
<NavItemConfiguration {bindings} />
<div class="settings">
<PropertyControl
label="Position"