Add support for buttons inside grids
This commit is contained in:
parent
0921bcf333
commit
2b8bbafcac
|
@ -2,6 +2,7 @@
|
||||||
// NOTE: this is not a block - it's just named as such to avoid confusing users,
|
// NOTE: this is not a block - it's just named as such to avoid confusing users,
|
||||||
// because it functions similarly to one
|
// because it functions similarly to one
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
|
||||||
// table is actually any datasource, but called table for legacy compatibility
|
// table is actually any datasource, but called table for legacy compatibility
|
||||||
|
@ -16,12 +17,21 @@
|
||||||
export let fixedRowHeight = null
|
export let fixedRowHeight = null
|
||||||
export let columns = null
|
export let columns = null
|
||||||
export let onRowClick = null
|
export let onRowClick = null
|
||||||
|
export let buttons = null
|
||||||
|
|
||||||
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, API, builderStore, notificationStore } = getContext("sdk")
|
const {
|
||||||
|
styleable,
|
||||||
|
API,
|
||||||
|
builderStore,
|
||||||
|
notificationStore,
|
||||||
|
enrichButtonActions,
|
||||||
|
} = getContext("sdk")
|
||||||
|
|
||||||
$: columnWhitelist = columns?.map(col => col.name)
|
$: columnWhitelist = columns?.map(col => col.name)
|
||||||
$: schemaOverrides = getSchemaOverrides(columns)
|
$: schemaOverrides = getSchemaOverrides(columns)
|
||||||
|
$: parsedButtons = parseButtons(buttons)
|
||||||
|
|
||||||
const getSchemaOverrides = columns => {
|
const getSchemaOverrides = columns => {
|
||||||
let overrides = {}
|
let overrides = {}
|
||||||
|
@ -33,6 +43,20 @@
|
||||||
})
|
})
|
||||||
return overrides
|
return overrides
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseButtons = buttons => {
|
||||||
|
if (!buttons?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return buttons.map(settings => ({
|
||||||
|
size: "M",
|
||||||
|
text: settings.text,
|
||||||
|
type: settings.type,
|
||||||
|
onClick: async () => {
|
||||||
|
return await enrichButtonActions(settings.onClick, get(context))()
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -58,6 +82,7 @@
|
||||||
showControls={false}
|
showControls={false}
|
||||||
notifySuccess={notificationStore.actions.success}
|
notifySuccess={notificationStore.actions.success}
|
||||||
notifyError={notificationStore.actions.error}
|
notifyError={notificationStore.actions.error}
|
||||||
|
buttons={parsedButtons}
|
||||||
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,9 +24,12 @@ import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { ActionTypes } from "./constants"
|
import { ActionTypes } from "./constants"
|
||||||
import { fetchDatasourceSchema } from "./utils/schema.js"
|
import { fetchDatasourceSchema } from "./utils/schema.js"
|
||||||
import { getAPIKey } from "./utils/api.js"
|
import { getAPIKey } from "./utils/api.js"
|
||||||
|
import { enrichButtonActions } from "./utils/buttonActions.js"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
API,
|
API,
|
||||||
|
|
||||||
|
// Stores
|
||||||
authStore,
|
authStore,
|
||||||
notificationStore,
|
notificationStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
|
@ -41,13 +44,20 @@ export default {
|
||||||
currentRole,
|
currentRole,
|
||||||
confirmationStore,
|
confirmationStore,
|
||||||
roleStore,
|
roleStore,
|
||||||
|
|
||||||
|
// Utils
|
||||||
styleable,
|
styleable,
|
||||||
linkable,
|
linkable,
|
||||||
getAction,
|
getAction,
|
||||||
fetchDatasourceSchema,
|
fetchDatasourceSchema,
|
||||||
Provider,
|
|
||||||
ActionTypes,
|
|
||||||
getAPIKey,
|
getAPIKey,
|
||||||
|
enrichButtonActions,
|
||||||
|
|
||||||
|
// Components
|
||||||
|
Provider,
|
||||||
Block,
|
Block,
|
||||||
BlockComponent,
|
BlockComponent,
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
ActionTypes,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
$: style = getStyle(width, selectedUser)
|
$: style = getStyle(width, selectedUser)
|
||||||
|
|
||||||
const getStyle = (width, selectedUser) => {
|
const getStyle = (width, selectedUser) => {
|
||||||
let style = `flex: 0 0 ${width}px;`
|
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
style += `--user-color:${selectedUser.color};`
|
style += `--user-color:${selectedUser.color};`
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
<script>
|
||||||
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
import GridCell from "../cells/GridCell.svelte"
|
||||||
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
|
|
||||||
|
const {
|
||||||
|
renderedRows,
|
||||||
|
hoveredRowId,
|
||||||
|
props,
|
||||||
|
width,
|
||||||
|
focusedRow,
|
||||||
|
selectedRows,
|
||||||
|
visibleColumns,
|
||||||
|
scroll,
|
||||||
|
isDragging,
|
||||||
|
buttonColumnWidth,
|
||||||
|
} = getContext("grid")
|
||||||
|
|
||||||
|
let measureContainer
|
||||||
|
|
||||||
|
$: buttons = $props.buttons?.slice(0, 3) || []
|
||||||
|
$: columnsWidth = $visibleColumns.reduce(
|
||||||
|
(total, col) => (total += col.width),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
$: end = columnsWidth - 1 - $scroll.left
|
||||||
|
$: left = Math.min($width - $buttonColumnWidth, end)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const observer = new ResizeObserver(entries => {
|
||||||
|
const width = entries?.[0]?.contentRect?.width ?? 0
|
||||||
|
buttonColumnWidth.set(width)
|
||||||
|
})
|
||||||
|
observer.observe(measureContainer)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Hidden copy of buttons to measure -->
|
||||||
|
<div class="measure" bind:this={measureContainer}>
|
||||||
|
<GridCell width="auto">
|
||||||
|
<div class="buttons">
|
||||||
|
{#each buttons as button}
|
||||||
|
<Button size="S">
|
||||||
|
{button.text || "Button"}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</GridCell>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="button-column"
|
||||||
|
style="left:{left}px"
|
||||||
|
class:hidden={$buttonColumnWidth === 0}
|
||||||
|
>
|
||||||
|
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
||||||
|
<GridScrollWrapper scrollVertically attachHandlers>
|
||||||
|
{#each $renderedRows as row}
|
||||||
|
{@const rowSelected = !!$selectedRows[row._id]}
|
||||||
|
{@const rowHovered = $hoveredRowId === row._id}
|
||||||
|
{@const rowFocused = $focusedRow?._id === row._id}
|
||||||
|
<div
|
||||||
|
class="row"
|
||||||
|
on:mouseenter={$isDragging ? null : () => ($hoveredRowId = row._id)}
|
||||||
|
on:mouseleave={$isDragging ? null : () => ($hoveredRowId = null)}
|
||||||
|
>
|
||||||
|
<GridCell
|
||||||
|
width="auto"
|
||||||
|
rowIdx={row.__idx}
|
||||||
|
selected={rowSelected}
|
||||||
|
highlighted={rowHovered || rowFocused}
|
||||||
|
>
|
||||||
|
<div class="buttons">
|
||||||
|
{#each buttons as button}
|
||||||
|
<Button
|
||||||
|
newStyles
|
||||||
|
size="S"
|
||||||
|
cta={button.type === "cta"}
|
||||||
|
primary={button.type === "primary"}
|
||||||
|
secondary={button.type === "secondary"}
|
||||||
|
warning={button.type === "warning"}
|
||||||
|
overBackground={button.type === "overBackground"}
|
||||||
|
on:click={() => button.onClick?.(row)}
|
||||||
|
>
|
||||||
|
{button.text || "Button"}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</GridCell>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</GridScrollWrapper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--cell-background);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.button-column.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--cell-padding);
|
||||||
|
gap: var(--cell-padding);
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add left cell border */
|
||||||
|
.button-column :global(.cell) {
|
||||||
|
border-left: var(--cell-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden copy of buttons to measure width against */
|
||||||
|
.measure {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -48,6 +48,7 @@
|
||||||
export let fixedRowHeight = null
|
export let fixedRowHeight = null
|
||||||
export let notifySuccess = null
|
export let notifySuccess = null
|
||||||
export let notifyError = null
|
export let notifyError = null
|
||||||
|
export let buttons = null
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
@ -99,6 +100,7 @@
|
||||||
fixedRowHeight,
|
fixedRowHeight,
|
||||||
notifySuccess,
|
notifySuccess,
|
||||||
notifyError,
|
notifyError,
|
||||||
|
buttons,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import GridRow from "./GridRow.svelte"
|
import GridRow from "./GridRow.svelte"
|
||||||
import { BlankRowID } from "../lib/constants"
|
import { BlankRowID } from "../lib/constants"
|
||||||
|
import ButtonColumn from "./ButtonColumn.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
dispatch,
|
dispatch,
|
||||||
isDragging,
|
isDragging,
|
||||||
config,
|
config,
|
||||||
|
props,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
let body
|
let body
|
||||||
|
@ -54,6 +56,9 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
|
{#if $props.buttons?.length}
|
||||||
|
<ButtonColumn />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -20,8 +20,15 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { rows, visibleColumns, stickyColumn, rowHeight, width, height } =
|
const {
|
||||||
context
|
rows,
|
||||||
|
visibleColumns,
|
||||||
|
stickyColumn,
|
||||||
|
rowHeight,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buttonColumnWidth,
|
||||||
|
} = context
|
||||||
|
|
||||||
// Memoize store primitives
|
// Memoize store primitives
|
||||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||||
|
@ -40,9 +47,10 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Derive horizontal limits
|
// Derive horizontal limits
|
||||||
const contentWidth = derived(
|
const contentWidth = derived(
|
||||||
[visibleColumns, stickyColumnWidth],
|
[visibleColumns, stickyColumnWidth, buttonColumnWidth],
|
||||||
([$visibleColumns, $stickyColumnWidth]) => {
|
([$visibleColumns, $stickyColumnWidth, $buttonColumnWidth]) => {
|
||||||
let width = GutterWidth + Padding + $stickyColumnWidth
|
const space = Math.max(Padding, $buttonColumnWidth - 1)
|
||||||
|
let width = GutterWidth + space + $stickyColumnWidth
|
||||||
$visibleColumns.forEach(col => {
|
$visibleColumns.forEach(col => {
|
||||||
width += col.width
|
width += col.width
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,7 @@ export const createStores = context => {
|
||||||
const previousFocusedRowId = writable(null)
|
const previousFocusedRowId = writable(null)
|
||||||
const gridFocused = writable(false)
|
const gridFocused = writable(false)
|
||||||
const isDragging = writable(false)
|
const isDragging = writable(false)
|
||||||
|
const buttonColumnWidth = writable(0)
|
||||||
|
|
||||||
// Derive the current focused row ID
|
// Derive the current focused row ID
|
||||||
const focusedRowId = derived(
|
const focusedRowId = derived(
|
||||||
|
@ -51,6 +52,7 @@ export const createStores = context => {
|
||||||
rowHeight,
|
rowHeight,
|
||||||
gridFocused,
|
gridFocused,
|
||||||
isDragging,
|
isDragging,
|
||||||
|
buttonColumnWidth,
|
||||||
selectedRows: {
|
selectedRows: {
|
||||||
...selectedRows,
|
...selectedRows,
|
||||||
actions: {
|
actions: {
|
||||||
|
|
Loading…
Reference in New Issue