Add initial version of side panel component
This commit is contained in:
parent
a80821cd73
commit
09360cea55
|
@ -16,7 +16,8 @@
|
||||||
"children": [
|
"children": [
|
||||||
"container",
|
"container",
|
||||||
"section",
|
"section",
|
||||||
"grid"
|
"grid",
|
||||||
|
"sidepanel"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -5225,5 +5225,13 @@
|
||||||
"suffix": "repeater"
|
"suffix": "repeater"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"sidepanel": {
|
||||||
|
"name": "Side Panel",
|
||||||
|
"icon": "AdDisplay",
|
||||||
|
"hasChildren": true,
|
||||||
|
"illegalChildren": ["section"],
|
||||||
|
"showEmptyState": false,
|
||||||
|
"static": true
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
|
@ -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)"
|
||||||
|
|
Loading…
Reference in New Issue