Add initial version of side panel component

This commit is contained in:
Andrew Kingston 2022-10-27 08:08:35 +01:00
parent a80821cd73
commit 09360cea55
10 changed files with 241 additions and 90 deletions

View File

@ -16,7 +16,8 @@
"children": [ "children": [
"container", "container",
"section", "section",
"grid" "grid",
"sidepanel"
] ]
}, },
{ {

View File

@ -5225,5 +5225,13 @@
"suffix": "repeater" "suffix": "repeater"
} }
] ]
},
"sidepanel": {
"name": "Side Panel",
"icon": "AdDisplay",
"hasChildren": true,
"illegalChildren": ["section"],
"showEmptyState": false,
"static": true
} }
} }

View File

@ -16,6 +16,7 @@
builderStore, builderStore,
currentRole, currentRole,
environmentStore, environmentStore,
sidePanelStore,
} = sdk } = sdk
const component = getContext("component") const component = getContext("component")
const context = getContext("context") const context = getContext("context")
@ -150,6 +151,7 @@
class:desktop={!mobile} class:desktop={!mobile}
class:mobile={!!mobile} class:mobile={!!mobile}
> >
<div class="layout-body">
{#if typeClass !== "none"} {#if typeClass !== "none"}
<div <div
class="interactive component navigation" class="interactive component navigation"
@ -245,18 +247,34 @@
</div> </div>
</div> </div>
</div> </div>
<div id="side-panel-container" class:open={$sidePanelStore.open}>
<div class="side-panel-header">
<Icon name="Close" hoverable on:click={sidePanelStore.actions.close} />
</div>
</div>
</div>
<style> <style>
/* Main components */ /* Main components */
.layout {
height: 100%;
flex: 1 1 auto;
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
z-index: 1;
border-top: 1px solid var(--spectrum-global-color-gray-300);
overflow: hidden;
}
.component { .component {
display: contents; display: contents;
} }
.layout { .layout-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
height: 100%;
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
@ -316,6 +334,28 @@
align-items: center; align-items: center;
gap: var(--spacing-xl); gap: var(--spacing-xl);
} }
#side-panel-container {
flex: 0 0 360px;
background: var(--background);
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
transition: margin-right 130ms ease-out;
z-index: 3;
padding: var(--spacing-xl);
margin-right: -370px;
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
#side-panel-container.open {
margin-right: 0;
}
.side-panel-header {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.main-wrapper { .main-wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -0,0 +1,57 @@
<script>
import { getContext } from "svelte"
const component = getContext("component")
const { styleable, sidePanelStore } = getContext("sdk")
$: open = $sidePanelStore.contentId === $component.id
const showInSidePanel = (el, visible) => {
const target = document.getElementById("side-panel-container")
const destroy = () => {
el.parentNode?.removeChild(el)
}
const update = visible => {
if (visible) {
target.appendChild(el)
el.hidden = false
} else {
destroy()
el.hidden = true
}
}
// Apply initial visibility
update(visible)
return {
update,
destroy,
}
}
</script>
<div
use:styleable={$component.styles}
use:showInSidePanel={open}
hidden
class="side-panel"
>
<slot />
</div>
<div>
<button on:click={() => sidePanelStore.actions.open($component.id)}
>open</button
>
<button on:click={sidePanelStore.actions.close}>close</button>
</div>
<style>
.side-panel {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-xl);
}
</style>

View File

@ -35,10 +35,10 @@ export { default as tag } from "./Tag.svelte"
export { default as markdownviewer } from "./MarkdownViewer.svelte" export { default as markdownviewer } from "./MarkdownViewer.svelte"
export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte" export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
export { default as grid } from "./Grid.svelte" export { default as grid } from "./Grid.svelte"
export { default as sidepanel } from "./SidePanel.svelte"
export * from "./charts" export * from "./charts"
export * from "./forms" export * from "./forms"
export * from "./table" export * from "./table"
export * from "./blocks" export * from "./blocks"
export * from "./dynamic-filter" export * from "./dynamic-filter"

View File

@ -43,7 +43,8 @@
if (callbackCount >= observers.length) { if (callbackCount >= observers.length) {
return return
} }
nextIndicators[idx].visible = entries[0].isIntersecting nextIndicators[idx].visible =
nextIndicators[idx].isSidePanel || entries[0].isIntersecting
if (++callbackCount === observers.length) { if (++callbackCount === observers.length) {
indicators = nextIndicators indicators = nextIndicators
updating = false updating = false
@ -91,8 +92,9 @@
// Extract valid children // Extract valid children
// Sanity limit of 100 active indicators // Sanity limit of 100 active indicators
const children = Array.from(parents) const children = Array.from(
.map(parent => parent?.children?.[0]) document.getElementsByClassName(`${componentId}-dom`)
)
.filter(x => x != null) .filter(x => x != null)
.slice(0, 100) .slice(0, 100)
@ -121,6 +123,7 @@
width: elBounds.width + 4, width: elBounds.width + 4,
height: elBounds.height + 4, height: elBounds.height + 4,
visible: false, visible: false,
isSidePanel: child.classList.contains("side-panel"),
}) })
}) })
} }

View File

@ -10,6 +10,7 @@ import {
componentStore, componentStore,
currentRole, currentRole,
environmentStore, environmentStore,
sidePanelStore,
} from "stores" } from "stores"
import { styleable } from "utils/styleable" import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable" import { linkable } from "utils/linkable"
@ -30,6 +31,7 @@ export default {
uploadStore, uploadStore,
componentStore, componentStore,
environmentStore, environmentStore,
sidePanelStore,
currentRole, currentRole,
styleable, styleable,
linkable, linkable,

View File

@ -24,6 +24,7 @@ export {
dndIsNewComponent, dndIsNewComponent,
dndIsDragging, dndIsDragging,
} from "./dnd" } from "./dnd"
export { sidePanelStore } from "./sidePanel.js"
// Context stores are layered and duplicated, so it is not a singleton // Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context" export { createContextStore } from "./context"

View File

@ -0,0 +1,37 @@
import { writable, derived } from "svelte/store"
export const createSidePanelStore = () => {
const initialState = {
contentId: null,
}
const store = writable(initialState)
const derivedStore = derived(store, $store => {
return {
...$store,
open: $store.contentId != null,
}
})
const open = id => {
store.update(state => {
state.contentId = id
return state
})
}
const close = () => {
store.update(state => {
state.contentId = null
return state
})
}
return {
subscribe: derivedStore.subscribe,
actions: {
open,
close,
},
}
}
export const sidePanelStore = createSidePanelStore()

View File

@ -25,6 +25,8 @@ export const styleable = (node, styles = {}) => {
// Creates event listeners and applies initial styles // Creates event listeners and applies initial styles
const setupStyles = (newStyles = {}) => { const setupStyles = (newStyles = {}) => {
node.classList.add(`${newStyles.id}-dom`)
let baseStyles = {} let baseStyles = {}
if (newStyles.empty) { if (newStyles.empty) {
baseStyles.border = "2px dashed var(--spectrum-global-color-gray-400)" baseStyles.border = "2px dashed var(--spectrum-global-color-gray-400)"