1
0
Fork 0
mirror of synced 2024-06-01 18:20:18 +12:00

Add component for customising navigation links, improve layouts and responsiveness

This commit is contained in:
Andrew Kingston 2021-06-17 12:18:48 +01:00
parent bd52745a90
commit a522a87ee8
12 changed files with 231 additions and 1224 deletions

View file

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

View file

@ -8,7 +8,7 @@
import { setWith } from "lodash"
$: definition = store.actions.components.getDefinition(
$selectedComponent._component
$selectedComponent?._component
)
$: isComponentOrScreen =
$store.currentView === "component" ||

View file

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

View file

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

View file

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

View file

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

View file

@ -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": [
{

View file

@ -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="burger">
<ActionButton
quiet
icon="ShowMenu"
on:click={() => (mobileOpen = !mobileOpen)}
/>
</div>
<div class="logo">
{#if !hideLogo}
<img src="https://i.imgur.com/Xhdt1YP.png" alt={title} />
{/if}
{#if !hideTitle}
<Heading>{title}</Heading>
<div class="nav-header">
{#if validLinks?.length}
<div class="burger">
<Icon
hoverable
name="ShowMenu"
on:click={() => (mobileOpen = !mobileOpen)}
/>
</div>
{/if}
<div class="logo">
{#if !hideLogo}
<img src={logoUrl} alt={title} />
{/if}
{#if !hideTitle}
<Heading>{title}</Heading>
{/if}
</div>
<div class="portal">
<Icon
hoverable
name="Apps"
on:click={() => (window.location.href = "/builder/apps")}
/>
</div>
</div>
<div class="portal">
<ActionButton quiet icon="Apps" on:click />
</div>
<div
class="mobile-click-handler"
class:visible={mobileOpen}
on:click={() => (mobileOpen = false)}
/>
<div class="links" class:visible={mobileOpen}>
{#each links as { text, url, external }}
{#if external}
<a class="link" href={url}>{text}</a>
{:else}
<a class="link" href={url} use:linkable>{text}</a>
{/if}
{/each}
<div class="close">
<ActionButton
quiet
icon="Close"
on:click={() => (mobileOpen = false)}
/>
{#if validLinks?.length}
<div class="links" class:visible={mobileOpen}>
{#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={ensureExternal(url)} on:click={close}>
{text}
</a>
{/if}
{/each}
<div class="close">
<Icon
hoverable
name="Close"
on:click={() => (mobileOpen = false)}
/>
</div>
</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;

View file

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

View file

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

View file

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