Add common collapsed button group and use it for row actions and client button groups. Add collapsed settings to grids and form blocks

This commit is contained in:
Andrew Kingston 2024-09-02 11:44:49 +01:00
parent d8b6d10dce
commit 51cbced119
No known key found for this signature in database
8 changed files with 123 additions and 85 deletions

View File

@ -0,0 +1,39 @@
<script>
import Button from "../Button/Button.svelte"
import Popover from "../Popover/Popover.svelte"
import Menu from "../Menu/Menu.svelte"
import MenuItem from "../Menu/Item.svelte"
export let buttons
export let text = "Action"
export let size = "M"
export let align = "left"
export let offset
let anchor
let popover
const handleClick = async button => {
popover.hide()
await button.onClick?.()
}
</script>
<Button
bind:ref={anchor}
{size}
icon="ChevronDown"
cta
on:click={() => popover?.show()}
>
{text || "Action"}
</Button>
<Popover bind:this={popover} {align} {anchor} {offset}>
<Menu>
{#each buttons as button}
<MenuItem on:click={() => handleClick(button)} disabled={button.disabled}>
{button.text || "Button"}
</MenuItem>
{/each}
</Menu>
</Popover>

View File

@ -39,6 +39,7 @@ export { default as ActionGroup } from "./ActionGroup/ActionGroup.svelte"
export { default as ActionMenu } from "./ActionMenu/ActionMenu.svelte" export { default as ActionMenu } from "./ActionMenu/ActionMenu.svelte"
export { default as Button } from "./Button/Button.svelte" export { default as Button } from "./Button/Button.svelte"
export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.svelte" export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.svelte"
export { default as CollapsedButtonGroup } from "./ButtonGroup/CollapsedButtonGroup.svelte"
export { default as ClearButton } from "./ClearButton/ClearButton.svelte" export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
export { default as Icon } from "./Icon/Icon.svelte" export { default as Icon } from "./Icon/Icon.svelte"
export { default as IconAvatar } from "./Icon/IconAvatar.svelte" export { default as IconAvatar } from "./Icon/IconAvatar.svelte"

View File

@ -30,10 +30,6 @@
let rowActions = [] let rowActions = []
let generateButton let generateButton
let rowActionPopover
let rowActionRow
let rowActionAnchor
let refreshRow
$: autoColumnStatus = verifyAutocolumns($tables?.selected) $: autoColumnStatus = verifyAutocolumns($tables?.selected)
$: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => { $: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => {
@ -58,21 +54,22 @@
$: relationshipsEnabled = relationshipSupport(tableDatasource) $: relationshipsEnabled = relationshipSupport(tableDatasource)
$: currentTheme = $themeStore?.theme $: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light") $: darkMode = !currentTheme.includes("light")
$: buttons = [ $: buttons = makeRowActionButtons(rowActions)
{
text: "Action",
type: "cta",
icon: "ChevronDown",
onClick: async (e, row, refresh) => {
rowActionRow = row
rowActionAnchor = e.currentTarget
rowActionPopover.show()
refreshRow = refresh
},
},
]
$: fetchRowActions(id) $: fetchRowActions(id)
const makeRowActionButtons = rowActions => {
return rowActions.map(action => ({
text: action.name,
onClick: async row => {
await API.rowActions.trigger({
rowActionId: action.id,
tableId: id,
rowId: row._id,
})
},
}))
}
const relationshipSupport = datasource => { const relationshipSupport = datasource => {
const integration = $integrations[datasource?.source] const integration = $integrations[datasource?.source]
return !isInternal && integration?.relationships !== false return !isInternal && integration?.relationships !== false
@ -110,16 +107,6 @@
const res = await API.rowActions.fetch(tableId) const res = await API.rowActions.fetch(tableId)
rowActions = Object.values(res || {}) rowActions = Object.values(res || {})
} }
const runRowAction = async action => {
await API.rowActions.trigger({
rowActionId: action.id,
tableId: id,
rowId: rowActionRow._id,
})
await refreshRow()
rowActionPopover.hide()
}
</script> </script>
{#if $tables?.selected?.name} {#if $tables?.selected?.name}
@ -143,7 +130,8 @@
schemaOverrides={isUsersTable ? userSchemaOverrides : null} schemaOverrides={isUsersTable ? userSchemaOverrides : null}
showAvatars={false} showAvatars={false}
isCloud={$admin.cloud} isCloud={$admin.cloud}
buttons={rowActions.length ? buttons : null} {buttons}
buttonsCollapsed
on:updatedatasource={handleGridTableUpdate} on:updatedatasource={handleGridTableUpdate}
> >
<!-- Controls --> <!-- Controls -->
@ -192,19 +180,6 @@
<i>Create your first table to start building</i> <i>Create your first table to start building</i>
{/if} {/if}
<Popover
bind:this={rowActionPopover}
align="right"
anchor={rowActionAnchor}
offset={5}
>
<Menu>
{#each rowActions as action}
<MenuItem on:click={() => runRowAction(action)}>{action.name}</MenuItem>
{/each}
</Menu>
</Popover>
<style> <style>
i { i {
font-size: var(--font-size-m); font-size: var(--font-size-m);

View File

@ -7591,6 +7591,18 @@
"key": "row" "key": "row"
} }
] ]
},
{
"type": "boolean",
"label": "Collapse",
"key": "buttonsCollapsed"
},
{
"type": "text",
"label": "Collapsed text",
"key": "buttonsCollapsedText",
"dependsOn": "buttonsCollapsed",
"placeholder": "Action"
} }
] ]
} }

View File

@ -1,7 +1,13 @@
<script> <script>
import BlockComponent from "../BlockComponent.svelte" import BlockComponent from "../BlockComponent.svelte"
import Block from "../Block.svelte" import Block from "../Block.svelte"
import { Button, Popover, Menu, MenuItem } from "@budibase/bbui" import {
Button,
Popover,
Menu,
MenuItem,
CollapsedButtonGroup,
} from "@budibase/bbui"
import { getContext } from "svelte" import { getContext } from "svelte"
export let buttons = [] export let buttons = []
@ -16,39 +22,26 @@
const component = getContext("component") const component = getContext("component")
const context = getContext("context") const context = getContext("context")
let popover $: collapsedButtons = collapsed ? makeCollapsed(buttons) : null
let anchor
const handleCollapsedClick = async button => { const makeCollapsed = buttons => {
const fn = enrichButtonActions(button.onClick, $context) return buttons.map(button => ({
await fn?.() ...button,
popover.hide() onClick: async () => {
const fn = enrichButtonActions(button.onClick, $context)
await fn?.()
},
}))
} }
</script> </script>
{#if collapsed} {#if collapsed}
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>
<Button <CollapsedButtonGroup
bind:ref={anchor} text={collapsedText || "Action"}
on:click={() => popover?.show()} buttons={collapsedButtons}
icon="ChevronDown" />
cta
>
{collapsedText || "Action"}
</Button>
</div> </div>
<Popover bind:this={popover} align="left" {anchor}>
<Menu>
{#each buttons as button}
<MenuItem
on:click={() => handleCollapsedClick(button)}
disabled={button.disabled}
>
{button.text || "Button"}
</MenuItem>
{/each}
</Menu>
</Popover>
{:else} {:else}
<Block> <Block>
<BlockComponent <BlockComponent

View File

@ -19,6 +19,8 @@
export let columns = null export let columns = null
export let onRowClick = null export let onRowClick = null
export let buttons = null export let buttons = null
export let buttonsCollapsed = false
export let buttonsCollapsedText = null
const context = getContext("context") const context = getContext("context")
const component = getContext("component") const component = getContext("component")
@ -115,15 +117,13 @@
text: settings.text, text: settings.text,
type: settings.type, type: settings.type,
icon: settings.icon, icon: settings.icon,
onClick: async (_, row, refresh) => { onClick: async row => {
// Create a fake, ephemeral context to run the buttons actions with // Create a fake, ephemeral context to run the buttons actions with
const id = get(component).id const id = get(component).id
const gridContext = createContextStore(context) const gridContext = createContextStore(context)
gridContext.actions.provideData(id, row) gridContext.actions.provideData(id, row)
const fn = enrichButtonActions(settings.onClick, get(gridContext)) const fn = enrichButtonActions(settings.onClick, get(gridContext))
const res = await fn?.({ row }) return await fn?.({ row })
await refresh()
return res
}, },
})) }))
} }
@ -183,6 +183,8 @@
notifySuccess={notificationStore.actions.success} notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error} notifyError={notificationStore.actions.error}
buttons={enrichedButtons} buttons={enrichedButtons}
{buttonsCollapsed}
{buttonsCollapsedText}
isCloud={$environmentStore.cloud} isCloud={$environmentStore.cloud}
on:rowclick={e => onRowClick?.({ row: e.detail })} on:rowclick={e => onRowClick?.({ row: e.detail })}
/> />

View File

@ -4,6 +4,7 @@
import GridCell from "../cells/GridCell.svelte" import GridCell from "../cells/GridCell.svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import { BlankRowID } from "../lib/constants" import { BlankRowID } from "../lib/constants"
import CollapsedButtonGroup from "../../../../../bbui/src/ButtonGroup/CollapsedButtonGroup.svelte"
const { const {
renderedRows, renderedRows,
@ -34,12 +35,16 @@
$: gridEnd = $width - $buttonColumnWidth - 1 $: gridEnd = $width - $buttonColumnWidth - 1
$: left = Math.min(columnEnd, gridEnd) $: left = Math.min(columnEnd, gridEnd)
const handleClick = async (e, button, row) => { const handleClick = async (button, row) => {
await button.onClick?.( await button.onClick?.(rows.actions.cleanRow(row))
e, await rows.actions.refreshRow(row._id)
rows.actions.cleanRow(row), }
async () => await rows.actions.refreshRow(row._id)
) const makeCollapsedButtons = (buttons, row) => {
return buttons.map(button => ({
...button,
onClick: () => handleClick(button, row),
}))
} }
onMount(() => { onMount(() => {
@ -80,26 +85,33 @@
class="buttons" class="buttons"
class:offset={$showVScrollbar && $showHScrollbar} class:offset={$showVScrollbar && $showHScrollbar}
> >
{#each buttons as button} {#if $props.buttonsCollapsed}
<!-- svelte-ignore a11y-click-events-have-key-events --> <CollapsedButtonGroup
<span on:click={e => handleClick(e, button, row)}> buttons={makeCollapsedButtons(buttons, row)}
text={$props.buttonsCollapsedText || "Action"}
align="right"
offset={5}
size="S"
/>
{:else}
{#each buttons as button}
<Button <Button
newStyles newStyles
size="S" size="S"
icon={button.icon}
cta={button.type === "cta"} cta={button.type === "cta"}
primary={button.type === "primary"} primary={button.type === "primary"}
secondary={button.type === "secondary"} secondary={button.type === "secondary"}
warning={button.type === "warning"} warning={button.type === "warning"}
overBackground={button.type === "overBackground"} overBackground={button.type === "overBackground"}
on:click={() => handleClick(button, row)}
> >
{#if button.icon} {#if button.icon}
<i class="{button.icon} S" /> <i class="{button.icon} S" />
{/if} {/if}
{button.text || "Button"} {button.text || "Button"}
</Button> </Button>
</span> {/each}
{/each} {/if}
</div> </div>
</GridCell> </GridCell>
</div> </div>

View File

@ -43,7 +43,9 @@
export let notifySuccess = null export let notifySuccess = null
export let notifyError = null export let notifyError = null
export let buttons = null export let buttons = null
export let darkMode export let buttonsCollapsed = false
export let buttonsCollapsedText = null
export let darkMode = false
export let isCloud = null export let isCloud = null
export let rowConditions = null export let rowConditions = null
@ -98,6 +100,8 @@
notifySuccess, notifySuccess,
notifyError, notifyError,
buttons, buttons,
buttonsCollapsed,
buttonsCollapsedText,
darkMode, darkMode,
isCloud, isCloud,
rowConditions, rowConditions,