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,
|
||||
// because it functions similarly to one
|
||||
import { getContext } from "svelte"
|
||||
import { get } from "svelte/store"
|
||||
import { Grid } from "@budibase/frontend-core"
|
||||
|
||||
// table is actually any datasource, but called table for legacy compatibility
|
||||
|
@ -16,12 +17,21 @@
|
|||
export let fixedRowHeight = null
|
||||
export let columns = null
|
||||
export let onRowClick = null
|
||||
export let buttons = null
|
||||
|
||||
const context = getContext("context")
|
||||
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)
|
||||
$: schemaOverrides = getSchemaOverrides(columns)
|
||||
$: parsedButtons = parseButtons(buttons)
|
||||
|
||||
const getSchemaOverrides = columns => {
|
||||
let overrides = {}
|
||||
|
@ -33,6 +43,20 @@
|
|||
})
|
||||
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>
|
||||
|
||||
<div
|
||||
|
@ -58,6 +82,7 @@
|
|||
showControls={false}
|
||||
notifySuccess={notificationStore.actions.success}
|
||||
notifyError={notificationStore.actions.error}
|
||||
buttons={parsedButtons}
|
||||
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -24,9 +24,12 @@ import BlockComponent from "components/BlockComponent.svelte"
|
|||
import { ActionTypes } from "./constants"
|
||||
import { fetchDatasourceSchema } from "./utils/schema.js"
|
||||
import { getAPIKey } from "./utils/api.js"
|
||||
import { enrichButtonActions } from "./utils/buttonActions.js"
|
||||
|
||||
export default {
|
||||
API,
|
||||
|
||||
// Stores
|
||||
authStore,
|
||||
notificationStore,
|
||||
routeStore,
|
||||
|
@ -41,13 +44,20 @@ export default {
|
|||
currentRole,
|
||||
confirmationStore,
|
||||
roleStore,
|
||||
|
||||
// Utils
|
||||
styleable,
|
||||
linkable,
|
||||
getAction,
|
||||
fetchDatasourceSchema,
|
||||
Provider,
|
||||
ActionTypes,
|
||||
getAPIKey,
|
||||
enrichButtonActions,
|
||||
|
||||
// Components
|
||||
Provider,
|
||||
Block,
|
||||
BlockComponent,
|
||||
|
||||
// Constants
|
||||
ActionTypes,
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
$: style = 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) {
|
||||
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 notifySuccess = null
|
||||
export let notifyError = null
|
||||
export let buttons = null
|
||||
|
||||
// Unique identifier for DOM nodes inside this instance
|
||||
const rand = Math.random()
|
||||
|
@ -99,6 +100,7 @@
|
|||
fixedRowHeight,
|
||||
notifySuccess,
|
||||
notifyError,
|
||||
buttons,
|
||||
})
|
||||
|
||||
// Set context for children to consume
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||
import GridRow from "./GridRow.svelte"
|
||||
import { BlankRowID } from "../lib/constants"
|
||||
import ButtonColumn from "./ButtonColumn.svelte"
|
||||
|
||||
const {
|
||||
bounds,
|
||||
|
@ -13,6 +14,7 @@
|
|||
dispatch,
|
||||
isDragging,
|
||||
config,
|
||||
props,
|
||||
} = getContext("grid")
|
||||
|
||||
let body
|
||||
|
@ -54,6 +56,9 @@
|
|||
/>
|
||||
{/if}
|
||||
</GridScrollWrapper>
|
||||
{#if $props.buttons?.length}
|
||||
<ButtonColumn />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -20,8 +20,15 @@ export const createStores = () => {
|
|||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { rows, visibleColumns, stickyColumn, rowHeight, width, height } =
|
||||
context
|
||||
const {
|
||||
rows,
|
||||
visibleColumns,
|
||||
stickyColumn,
|
||||
rowHeight,
|
||||
width,
|
||||
height,
|
||||
buttonColumnWidth,
|
||||
} = context
|
||||
|
||||
// Memoize store primitives
|
||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||
|
@ -40,9 +47,10 @@ export const deriveStores = context => {
|
|||
|
||||
// Derive horizontal limits
|
||||
const contentWidth = derived(
|
||||
[visibleColumns, stickyColumnWidth],
|
||||
([$visibleColumns, $stickyColumnWidth]) => {
|
||||
let width = GutterWidth + Padding + $stickyColumnWidth
|
||||
[visibleColumns, stickyColumnWidth, buttonColumnWidth],
|
||||
([$visibleColumns, $stickyColumnWidth, $buttonColumnWidth]) => {
|
||||
const space = Math.max(Padding, $buttonColumnWidth - 1)
|
||||
let width = GutterWidth + space + $stickyColumnWidth
|
||||
$visibleColumns.forEach(col => {
|
||||
width += col.width
|
||||
})
|
||||
|
|
|
@ -18,6 +18,7 @@ export const createStores = context => {
|
|||
const previousFocusedRowId = writable(null)
|
||||
const gridFocused = writable(false)
|
||||
const isDragging = writable(false)
|
||||
const buttonColumnWidth = writable(0)
|
||||
|
||||
// Derive the current focused row ID
|
||||
const focusedRowId = derived(
|
||||
|
@ -51,6 +52,7 @@ export const createStores = context => {
|
|||
rowHeight,
|
||||
gridFocused,
|
||||
isDragging,
|
||||
buttonColumnWidth,
|
||||
selectedRows: {
|
||||
...selectedRows,
|
||||
actions: {
|
||||
|
|
Loading…
Reference in New Issue