Add support for buttons inside grids

This commit is contained in:
Andrew Kingston 2023-11-01 16:45:37 +00:00
parent 0921bcf333
commit 2b8bbafcac
8 changed files with 198 additions and 9 deletions

View File

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

View File

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

View File

@ -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};`
} }

View File

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

View File

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

View File

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

View File

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

View File

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