Add component for customising navigation links, improve layouts and responsiveness
This commit is contained in:
parent
bd52745a90
commit
a522a87ee8
|
@ -78,7 +78,7 @@
|
|||
"posthog-js": "1.4.5",
|
||||
"remixicon": "2.5.0",
|
||||
"shortid": "2.2.15",
|
||||
"svelte-dnd-action": "^0.8.9",
|
||||
"svelte-dnd-action": "^0.9.8",
|
||||
"svelte-loading-spinners": "^0.1.1",
|
||||
"svelte-portal": "0.1.0",
|
||||
"uuid": "8.3.1",
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { setWith } from "lodash"
|
||||
|
||||
$: definition = store.actions.components.getDefinition(
|
||||
$selectedComponent._component
|
||||
$selectedComponent?._component
|
||||
)
|
||||
$: isComponentOrScreen =
|
||||
$store.currentView === "component" ||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<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 { store } from "builderStore"
|
||||
|
||||
export let links = []
|
||||
|
||||
const flipDurationMs = 150
|
||||
|
||||
$: urlOptions = $store.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
.filter(x => x != null)
|
||||
|
||||
const addLink = () => {
|
||||
links = [...links, { id: generate() }]
|
||||
}
|
||||
|
||||
const removeLink = id => {
|
||||
links = links.filter(link => link.id !== id)
|
||||
}
|
||||
|
||||
const updateLinks = e => {
|
||||
links = e.detail.items
|
||||
}
|
||||
</script>
|
||||
|
||||
<DrawerContent>
|
||||
<div class="container">
|
||||
<Layout>
|
||||
{#if links?.length}
|
||||
<div
|
||||
class="links"
|
||||
use:dndzone={{
|
||||
items: links,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
}}
|
||||
on:finalize={updateLinks}
|
||||
on:consider={updateLinks}
|
||||
>
|
||||
{#each links as link (link.id)}
|
||||
<div class="link" animate:flip={{ duration: flipDurationMs }}>
|
||||
<Icon name="DragHandle" size="XL" />
|
||||
<Input bind:value={link.text} placeholder="Text" />
|
||||
<Combobox
|
||||
bind:value={link.url}
|
||||
placeholder="URL"
|
||||
options={urlOptions}
|
||||
/>
|
||||
<Icon
|
||||
name="Close"
|
||||
hoverable
|
||||
size="S"
|
||||
on:click={() => removeLink(link.id)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button-container">
|
||||
<Button secondary icon="Add" on:click={addLink}>Add Link</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: var(--spacing-m) auto;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.link {
|
||||
padding: 4px 8px;
|
||||
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;
|
||||
}
|
||||
.button-container {
|
||||
margin-left: var(--spacing-l);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
<script>
|
||||
import { Button, Drawer } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import NavigationDrawer from "./NavigationDrawer.svelte"
|
||||
|
||||
export let value = []
|
||||
let drawer
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const save = () => {
|
||||
dispatch("change", value)
|
||||
drawer.hide()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button secondary on:click={drawer.show}>Configure Links</Button>
|
||||
<Drawer bind:this={drawer} title={"Navigation Links"}>
|
||||
<svelte:fragment slot="description">
|
||||
Configure the links in your navigation bar.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||
<NavigationDrawer slot="body" bind:links={value} />
|
||||
</Drawer>
|
|
@ -1,40 +0,0 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
|
||||
let flipDurationMs = 150
|
||||
|
||||
// This should be the screens and any external links the user has added
|
||||
let items = [
|
||||
{ text: "Test", id: 0 },
|
||||
{ text: "First", id: 1 },
|
||||
{ text: "Second", id: 2 },
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<ul
|
||||
use:dndzone={{
|
||||
items,
|
||||
flipDurationMs,
|
||||
dropTargetStyle: { outline: "none" },
|
||||
}}
|
||||
>
|
||||
{#each items as item (item)}
|
||||
<li animate:flip={{ duration: flipDurationMs }}>{item}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<ActionButton icon="Add">Add External Link</ActionButton>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: grid;
|
||||
}
|
||||
ul {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
list-style-type: none;
|
||||
}
|
||||
</style>
|
|
@ -16,7 +16,7 @@
|
|||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
||||
import SectionSelect from "./PropertyControls/SectionSelect.svelte"
|
||||
import NavigationSelect from "./PropertyControls/NavigationSelect.svelte"
|
||||
import NavigationEditor from "./PropertyControls/NavigationEditor/NavigationEditor.svelte"
|
||||
import EventsEditor from "./PropertyControls/EventsEditor"
|
||||
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
|
||||
import { IconSelect } from "./PropertyControls/IconSelect"
|
||||
|
@ -65,7 +65,7 @@
|
|||
multifield: MultiFieldSelect,
|
||||
schema: SchemaSelect,
|
||||
section: SectionSelect,
|
||||
navigationSelect: NavigationSelect,
|
||||
navigation: NavigationEditor,
|
||||
filter: FilterEditor,
|
||||
"field/string": StringFieldSelect,
|
||||
"field/number": NumberFieldSelect,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,9 +38,9 @@
|
|||
"defaultValue": "Top"
|
||||
},
|
||||
{
|
||||
"type": "navigationSelect",
|
||||
"type": "navigation",
|
||||
"label": "Links",
|
||||
"key": "type"
|
||||
"key": "links"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -58,7 +58,6 @@
|
|||
"type": "select",
|
||||
"label": "Direction",
|
||||
"key": "direction",
|
||||
"key": "direction",
|
||||
"showInBar": true,
|
||||
"options": [
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { ActionButton, Heading } from "@budibase/bbui"
|
||||
import { ActionButton, Heading, Icon } from "@budibase/bbui"
|
||||
|
||||
const { styleable, linkable } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
@ -11,11 +11,11 @@
|
|||
export let hideLogo = false
|
||||
export let navigation = "Top"
|
||||
export let sticky = true
|
||||
export let links
|
||||
|
||||
export let links = [
|
||||
{ text: "Some Text", url: "/" },
|
||||
{ text: "Some Text", url: "/" },
|
||||
]
|
||||
$: validLinks = links?.filter(link => link.text && link.url) || []
|
||||
$: type = navigationClasses[navigation] || "none"
|
||||
let mobileOpen = false
|
||||
|
||||
const navigationClasses = {
|
||||
Top: "top",
|
||||
|
@ -23,56 +23,76 @@
|
|||
None: "none",
|
||||
}
|
||||
|
||||
$: type = navigationClasses[navigation] || "none"
|
||||
let mobileOpen = false
|
||||
const isInternal = url => {
|
||||
return url.startsWith("/")
|
||||
}
|
||||
|
||||
const ensureExternal = url => {
|
||||
return !url.startsWith("http") ? `http://${url}` : url
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
mobileOpen = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="layout layout--{type}" use:styleable={$component.styles}>
|
||||
{#if type !== "none"}
|
||||
<div class="nav-wrapper" class:sticky>
|
||||
<div class="nav nav--{type}">
|
||||
<div class="nav-header">
|
||||
{#if validLinks?.length}
|
||||
<div class="burger">
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="ShowMenu"
|
||||
<Icon
|
||||
hoverable
|
||||
name="ShowMenu"
|
||||
on:click={() => (mobileOpen = !mobileOpen)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
<div class="logo">
|
||||
{#if !hideLogo}
|
||||
<img src="https://i.imgur.com/Xhdt1YP.png" alt={title} />
|
||||
<img src={logoUrl} alt={title} />
|
||||
{/if}
|
||||
{#if !hideTitle}
|
||||
<Heading>{title}</Heading>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="portal">
|
||||
<ActionButton quiet icon="Apps" on:click />
|
||||
<Icon
|
||||
hoverable
|
||||
name="Apps"
|
||||
on:click={() => (window.location.href = "/builder/apps")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mobile-click-handler"
|
||||
class:visible={mobileOpen}
|
||||
on:click={() => (mobileOpen = false)}
|
||||
/>
|
||||
{#if validLinks?.length}
|
||||
<div class="links" class:visible={mobileOpen}>
|
||||
{#each links as { text, url, external }}
|
||||
{#if external}
|
||||
<a class="link" href={url}>{text}</a>
|
||||
{#each validLinks as { text, url }}
|
||||
{#if isInternal(url)}
|
||||
<a class="link" href={url} use:linkable on:click={close}>
|
||||
{text}
|
||||
</a>
|
||||
{:else}
|
||||
<a class="link" href={url} use:linkable>{text}</a>
|
||||
<a class="link" href={ensureExternal(url)} on:click={close}>
|
||||
{text}
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="close">
|
||||
<ActionButton
|
||||
quiet
|
||||
icon="Close"
|
||||
<Icon
|
||||
hoverable
|
||||
name="Close"
|
||||
on:click={() => (mobileOpen = false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -110,11 +130,20 @@
|
|||
}
|
||||
|
||||
.nav {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
padding: var(--spacing-xl);
|
||||
max-width: 1400px;
|
||||
grid-template-columns: 1fr auto;
|
||||
width: 1400px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.nav-header {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
|
@ -124,12 +153,12 @@
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
.main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
max-width: 1400px;
|
||||
width: 1400px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -143,16 +172,13 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
grid-column: 1;
|
||||
}
|
||||
.logo img {
|
||||
height: 48px;
|
||||
}
|
||||
.portal {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
grid-column: 2;
|
||||
place-items: center;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
|
@ -160,8 +186,6 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-l);
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 2;
|
||||
}
|
||||
.link {
|
||||
color: var(--spectrum-alias-text-color);
|
||||
|
@ -175,8 +199,8 @@
|
|||
.close {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: var(--spacing-m);
|
||||
right: var(--spacing-m);
|
||||
top: var(--spacing-xl);
|
||||
right: var(--spacing-xl);
|
||||
}
|
||||
.mobile-click-handler {
|
||||
display: none;
|
||||
|
@ -194,12 +218,9 @@
|
|||
}
|
||||
|
||||
.nav--top {
|
||||
grid-template-rows: auto auto;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
.nav--left {
|
||||
grid-template-rows: auto 1fr;
|
||||
width: 250px;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
@ -246,19 +267,11 @@
|
|||
|
||||
/* Force standard top bar */
|
||||
.nav {
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-xl);
|
||||
grid-template-columns: auto auto auto;
|
||||
grid-template-rows: auto;
|
||||
padding: var(--spacing-m);
|
||||
padding: var(--spacing-m) var(--spacing-xl);
|
||||
}
|
||||
.burger {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-column: 1;
|
||||
}
|
||||
.logo {
|
||||
grid-column: 2;
|
||||
}
|
||||
.logo img {
|
||||
height: 36px;
|
||||
|
@ -266,9 +279,6 @@
|
|||
.logo :global(h1) {
|
||||
display: none;
|
||||
}
|
||||
.portal {
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
/* Transform links into drawer */
|
||||
.links {
|
||||
|
@ -289,6 +299,7 @@
|
|||
}
|
||||
.link {
|
||||
width: calc(100% - 30px);
|
||||
font-size: 120%;
|
||||
}
|
||||
.links.visible {
|
||||
opacity: 1;
|
|
@ -24,7 +24,7 @@ export { default as stackedlist } from "./StackedList.svelte"
|
|||
export { default as card } from "./Card.svelte"
|
||||
export { default as text } from "./Text.svelte"
|
||||
export { default as navigation } from "./Navigation.svelte"
|
||||
export { default as layout } from "./layout/Layout.svelte"
|
||||
export { default as layout } from "./Layout.svelte"
|
||||
export { default as link } from "./Link.svelte"
|
||||
export { default as heading } from "./Heading.svelte"
|
||||
export { default as image } from "./Image.svelte"
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
ActionButton,
|
||||
SideNavigation,
|
||||
SideNavigationItem as Item,
|
||||
} from "@budibase/bbui"
|
||||
export let links
|
||||
</script>
|
||||
|
||||
<div class="overlay">
|
||||
<SideNavigation>
|
||||
{#each links as { text, url }}
|
||||
<!-- Needs logic to select current route -->
|
||||
<Item selected={false} href={url} on:click>{text}</Item>
|
||||
{/each}
|
||||
</SideNavigation>
|
||||
<div class="close">
|
||||
<ActionButton quiet icon="Close" on:click />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
background: white;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
top: var(--spacing-m);
|
||||
right: var(--spacing-m);
|
||||
}
|
||||
</style>
|
|
@ -1,71 +0,0 @@
|
|||
<script>
|
||||
import Mobile from "./Mobile.svelte"
|
||||
import {
|
||||
ActionButton,
|
||||
SideNavigation,
|
||||
SideNavigationItem as Item,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
export let navigation
|
||||
export let links
|
||||
export let mobileOpen = false
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#if navigation === "Top"}
|
||||
<ul>
|
||||
{#each links as { text, url }}
|
||||
<li><a href={url}>{text}</a></li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<SideNavigation>
|
||||
{#each links as { text, url }}
|
||||
<!-- Needs logic to select current route -->
|
||||
<Item selected={false} href="/">{text}</Item>
|
||||
{/each}
|
||||
</SideNavigation>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mobile">
|
||||
<ActionButton
|
||||
quiet
|
||||
selected
|
||||
icon="ShowMenu"
|
||||
on:click={() => (mobileOpen = !mobileOpen)}
|
||||
/>
|
||||
{#if mobileOpen}
|
||||
<Mobile {links} on:click={() => (mobileOpen = !mobileOpen)} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: none;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ul > * {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
:global(ul > a) {
|
||||
font-size: 1.5em;
|
||||
text-decoration: none;
|
||||
margin-right: 16px;
|
||||
}
|
||||
@media (min-width: 600px) {
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
.container {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue