/** * WordPress Imports */ import { __, sprintf } from "@wordpress/i18n"; import { alignNone, positionLeft, positionCenter, positionRight, stretchFullWidth, stretchWide, cog, styles as stylesIcon, caption as captionIcon, table, tableRowBefore, arrowDown, arrowRight, arrowUp, justifyLeft, justifyCenter, justifyRight, } from "@wordpress/icons"; import { InspectorControls, BlockControls, useBlockEditContext, store as blockEditorStore, } from "@wordpress/block-editor"; import { ToggleControl, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, PanelBody, ToolbarButton, ToolbarGroup, SelectControl, TabPanel, } from "@wordpress/components"; import { ReactNode, useState } from "react"; /** * Internal Imports */ import { useDispatch, useRegistry, useSelect } from "@wordpress/data"; import { SpacingControl, SpacingControlSingle, ColorControl, ToolbarWithDropdown, SizeControl, BorderControl, BorderRadiusControl, TypographyControls, } from "@tableberg/components"; import { defaultTypography, getTypography, } from "@tableberg/shared/utils/typography"; import LockedControl from "../../components/LockedControl"; import { useTableStore } from "../../store"; import TablebergIcon from "@tableberg/shared/icons/tableberg"; import { DuplicateRowIcon } from "@tableberg/shared/icons/enhancements"; import { areAllMergeable, mergeCells } from "../../merge"; import { attrDefaults, Cell, CellKey, CellSpacing, PaginationConfig, SortableType, TableAlignment, } from "../../attributes"; import { hasSortableColumns, tableHasMergedCells } from "../../sorting"; import { tableHasRowSpanningCells } from "../../pagination"; import { useBackgroundColorHelpers, useCellStyleControl } from "../../hooks"; import { ResponsiveControl } from "../../components/ResponsiveControl"; import { ElementAlignment, getUniformElementsAlignment, setElementAlignment, } from "../../alignment"; import { isProAvailable } from "../../pro-status"; import { UpsellEnhancedModal } from "../../components/UpsellModal"; import { AdvancedCustomClassControl } from "../../components/AdvancedCustomClassControl"; import { textAttributeDefaults } from "../text/element"; const cellDefaultsStyles = attrDefaults.cellDefaults.styles; const TABLE_ALIGNMENT_TOOLBAR_CONTROLS: { value: TableAlignment; icon: JSX.Element; title: string; }[] = [ { value: "left", icon: positionLeft, title: __("Align left", "tableberg"), }, { value: "center", icon: positionCenter, title: __("Align center", "tableberg"), }, { value: "right", icon: positionRight, title: __("Align right", "tableberg"), }, ]; type TableWidthMode = "auto" | "fixed" | "wide" | "full"; type SidebarTab = "settings" | "styles" | "datatable"; const DEFAULT_FIXED_TABLE_WIDTH = "350px"; const getCellEntries = (cells: Record) => Object.entries(cells).map( ([key, cell]) => [key as CellKey, cell.elements || []] as const ); const resetToolsPanelFilters = (filters: (() => unknown)[] = []) => { filters.forEach(filter => { filter(); }); }; const TABLE_WIDTH_PRESET_VALUES = ["auto", "wide", "full"] as const; const isTableWidthPreset = ( value: string ): value is (typeof TABLE_WIDTH_PRESET_VALUES)[number] => (TABLE_WIDTH_PRESET_VALUES as readonly string[]).includes(value); const CELL_ORIENTATION_OPTIONS = [ { value: "vertical", icon: arrowDown, label: __("Vertical", "tableberg"), }, { value: "horizontal", icon: arrowRight, label: __("Horizontal", "tableberg"), }, ] as const; const CELL_ALIGNMENT_OPTIONS = [ { value: "left", icon: justifyLeft, label: __("Left", "tableberg"), }, { value: "center", icon: justifyCenter, label: __("Center", "tableberg"), }, { value: "right", icon: justifyRight, label: __("Right", "tableberg"), }, ] as const; const CELL_VERTICAL_ALIGN_OPTIONS = [ { value: "top", icon: arrowUp, label: __("Top", "tableberg"), }, { value: "middle", icon: positionCenter, label: __("Middle", "tableberg"), }, { value: "bottom", icon: arrowDown, label: __("Bottom", "tableberg"), }, ] as const; type FourSidesSpacing = { top: string; right: string; bottom: string; left: string; }; const tableConfigDefaults = attrDefaults.table; function getDefaultCellSpacing(): CellSpacing { const defaultCellSpacing = tableConfigDefaults.cellSpacing!!; return { horizontal: defaultCellSpacing.horizontal, vertical: defaultCellSpacing.vertical, }; } function toCellSpacingPaddingValue(cellSpacing: CellSpacing) { return { top: cellSpacing.vertical, right: cellSpacing.horizontal, bottom: cellSpacing.vertical, left: cellSpacing.horizontal, }; } function fromCellSpacingPaddingValue( newPadding: FourSidesSpacing, previousCellSpacing: CellSpacing, defaultCellSpacing: CellSpacing ) { const previousPadding = toCellSpacingPaddingValue(previousCellSpacing); let horizontal = previousCellSpacing.horizontal; if (newPadding.right !== previousPadding.right) { horizontal = newPadding.right; } else if (newPadding.left !== previousPadding.left) { horizontal = newPadding.left; } let vertical = previousCellSpacing.vertical; if (newPadding.top !== previousPadding.top) { vertical = newPadding.top; } else if (newPadding.bottom !== previousPadding.bottom) { vertical = newPadding.bottom; } return { horizontal: horizontal || defaultCellSpacing.horizontal, vertical: vertical || defaultCellSpacing.vertical, }; } /** Everything pro's column-sorting UI needs, gathered from the store. */ export interface ColumnSortingContext { columnNumbers: number[]; getColumnConfig: (column: number) => ColumnConfig | undefined; isColumnSortableAllowed: (column: number) => boolean; setColumnSortable: ( column: number, sortable: SortableType | undefined ) => void; hasSortableCols: boolean; sortPreviewMode: boolean; enterSortPreviewMode: () => void; } function ColumnSortingControl({ ProColumnSortingContent, }: { // Injected by the pro plugin; undefined when pro is not installed. A // render function rather than a plain node: the merged-cells check and // the live preview toggle both need store state pro cannot reach from // outside (it runs above TableStoreProvider), so free gathers the // context and pro only renders from it. ProColumnSortingContent?: (ctx: ColumnSortingContext) => ReactNode; }) { const cells = useTableStore(state => state.cells); const columns = useTableStore(state => state.columns); const tableConfig = useTableStore(state => state.table); const sortPreviewMode = useTableStore(state => state.sortPreviewMode); const getColumnConfig = useTableStore(state => state.getColumnConfig); const setColumnSortable = useTableStore(state => state.setColumnSortable); const isColumnSortableAllowed = useTableStore( state => state.isColumnSortableAllowed ); const enterSortPreviewMode = useTableStore( state => state.enterSortPreviewMode ); const { headerEnabled } = tableConfig; if (!headerEnabled) { return (

{__( "To enable column sorting, make the top row a header in the Header & Footer Settings panel.", "tableberg" )}

); } if (tableHasMergedCells(cells)) { return (

{__( "Column sorting is not available for tables with merged cells.", "tableberg" )}

); } const columnNumbers = Array.from( { length: tableConfig.cols }, (_, index) => index ); const hasSortableCols = hasSortableColumns(columns); if (!ProColumnSortingContent) { return ( {columnNumbers.map(column => ( null} /> ))} ); } return ( {ProColumnSortingContent({ columnNumbers, getColumnConfig, isColumnSortableAllowed, setColumnSortable, hasSortableCols, sortPreviewMode, enterSortPreviewMode, })} ); } /** Everything pro's pagination UI needs, gathered from the store. */ export interface PaginationContext { paginationConfig: PaginationConfig; updatePaginationConfig: (patch: Partial) => void; } function PaginationControl({ ProPaginationContent, }: { // Injected by the pro plugin; undefined when pro is not installed. A // render function rather than a plain node: the row-span check needs // block-tree `cells` pro cannot reach from outside (it runs above // TableStoreProvider), and setPaginationConfig also clamps the store's // ephemeral currentPage, so free gathers the context and pro only // renders from it. ProPaginationContent?: (ctx: PaginationContext) => ReactNode; }) { const cells = useTableStore(state => state.cells); const paginationConfig = useTableStore(state => state.table.pagination!); const setPaginationConfig = useTableStore( state => state.setPaginationConfig ); if (!ProPaginationContent) { return ( null} /> ); } if (tableHasRowSpanningCells(cells)) { return (

{__( "Pagination is not available for tables with row-spanning cells (cells that span multiple rows).", "tableberg" )}

); } return ( {ProPaginationContent({ paginationConfig, updatePaginationConfig: setPaginationConfig, })} ); } /** * Only the table-wide switch lives here. The per-column width and per-row * height are set on the cell you select, from the cell block's sidebar — * that is where the selection actually is in the block editor. */ function ColumnAndRowDimensionsControl() { const tableConfig = useTableStore(state => state.table); const updateTableConfig = useTableStore(state => state.updateTable); if (tableConfig.cols < 1 && tableConfig.rows < 1) { return null; } return ( { updateTableConfig({ fixedColumnWidths: enabled }); }} />

{__( "Select a cell to set the width of its column and the height of its row.", "tableberg" )}

); } interface TablebergControlsProps { // Injected by the pro plugin via NativeTableEdit's generic Pro*-prop // forwarding (same mechanism the element bridge uses for cell elements). // Undefined when pro is not installed. ProStickyHeaderControl?: ReactNode; ProStickyFirstColControl?: ReactNode; ProCellOrientationControl?: ReactNode; ProBorderModeControls?: ReactNode; ProColumnSortingContent?: (ctx: ColumnSortingContext) => ReactNode; ProSearchControl?: ReactNode; ProPaginationContent?: (ctx: PaginationContext) => ReactNode; } function TablebergControls({ ProStickyHeaderControl, ProStickyFirstColControl, ProCellOrientationControl, ProBorderModeControls, ProColumnSortingContent, ProSearchControl, ProPaginationContent, }: TablebergControlsProps = {}) { const isPro = isProAvailable(); const { clientId } = useBlockEditContext(); const tableConfig = useTableStore(state => state.table); const cells = useTableStore(state => state.cells); const updateConfig = useTableStore(state => state.updateTable); const setCells = useTableStore(state => state.setCells); const selectedCells = useTableStore(state => state.selectedCells); const tableState = useTableStore(state => state); const sortPreviewMode = useTableStore(state => state.sortPreviewMode); const selectedRibbonCell = useTableStore(state => state.selectedRibbonCell); const showCaption = useTableStore(state => state.showCaption); const setShowCaption = useTableStore(state => state.setShowCaption); const showRowColumnControls = useTableStore( state => state.showRowColumnControls ); const showDuplicateMoveControls = useTableStore( state => state.showDuplicateMoveControls ); const toggleRowColumnControls = useTableStore( state => state.toggleRowColumnControls ); const toggleDuplicateMoveControls = useTableStore( state => state.toggleDuplicateMoveControls ); const caption = tableConfig.caption || ""; const [activeTab, setActiveTab] = useState("settings"); const [showDuplicateMoveUpsell, setShowDuplicateMoveUpsell] = useState(false); const isRibbonSelected = selectedRibbonCell !== null; const tableAlignment: TableAlignment = tableConfig.tableAlignment || "left"; const tableWidth = (tableConfig.tableWidth || "auto").trim(); const tableWidthMode: TableWidthMode = isTableWidthPreset(tableWidth) ? tableWidth : "fixed"; const isFixedWidthMode = tableWidthMode === "fixed"; const fixedTableWidthValue = isFixedWidthMode ? tableWidth || DEFAULT_FIXED_TABLE_WIDTH : DEFAULT_FIXED_TABLE_WIDTH; const defaultCellSpacing = getDefaultCellSpacing(); const typography = getTypography(tableConfig.typography); const cellSpacing = tableConfig.cellSpacing || defaultCellSpacing; const horizontalCellSpacing = cellSpacing.horizontal || defaultCellSpacing.horizontal; const verticalCellSpacing = cellSpacing.vertical || defaultCellSpacing.vertical; const defaultTableBorder = tableConfigDefaults.tableBorder!!; const tableBorder = tableConfig.tableBorder || defaultTableBorder; const tableBorderControlProps = { label: __("Table Border", "tableberg"), value: tableBorder, hasValue: () => !!tableBorder.top || !!tableBorder.right || !!tableBorder.bottom || !!tableBorder.left, onChange: (newBorder: typeof tableBorder) => { updateConfig({ tableBorder: newBorder }); }, onDeselect: () => { updateConfig({ tableBorder: { ...defaultTableBorder } }); }, }; const defaultBlockMargin = tableConfigDefaults.margin!!; const blockMargin = tableConfig.margin || defaultBlockMargin; const blockMarginControlProps = { label: __("Block Margin", "tableberg"), value: blockMargin, hasValue: () => !!blockMargin.top || !!blockMargin.right || !!blockMargin.bottom || !!blockMargin.left, onChange: (newMargin: typeof blockMargin) => { updateConfig({ margin: newMargin }); }, onDeselect: () => { updateConfig({ margin: { ...defaultBlockMargin } }); }, }; const defaultBlockPadding = tableConfigDefaults.padding!!; const blockPadding = tableConfig.padding || defaultBlockPadding; const blockPaddingControlProps = { label: __("Block Padding", "tableberg"), value: blockPadding, hasValue: () => !!blockPadding.top || !!blockPadding.right || !!blockPadding.bottom || !!blockPadding.left, onChange: (newPadding: typeof blockPadding) => { updateConfig({ padding: newPadding }); }, onDeselect: () => { updateConfig({ padding: { ...defaultBlockPadding } }); }, }; const borderControlProps = useCellStyleControl({ styleKey: "border", defaultValue: cellDefaultsStyles.border, label: __("Cell Border", "tableberg"), labelSelected: __("Common Cell Border", "tableberg"), hasValue: border => !!border.top || !!border.right || !!border.bottom || !!border.left, }); const borderRadiusControlProps = useCellStyleControl({ styleKey: "borderRadius", defaultValue: cellDefaultsStyles.borderRadius, label: __("Table Border Radius", "tableberg"), labelSelected: __("Cell Border Radius", "tableberg"), hasValue: borderRadius => !!borderRadius.topLeft || !!borderRadius.topRight || !!borderRadius.bottomRight || !!borderRadius.bottomLeft, }); const paddingControlProps = useCellStyleControl({ styleKey: "padding", defaultValue: cellDefaultsStyles.padding, hasValue: padding => !!padding.top || !!padding.right || !!padding.bottom || !!padding.left, label: __("Common Cell Padding", "tableberg"), labelSelected: __("Cell Padding", "tableberg"), }); const backgroundColorControlProps = useCellStyleControl({ styleKey: "backgroundColor", defaultValue: cellDefaultsStyles.backgroundColor, hasValue: bg => !!bg, label: __("Common Cell Background Color", "tableberg"), labelSelected: __("Cell Background Color", "tableberg"), }); const elementGapControlProps = useCellStyleControl({ styleKey: "elementGap", defaultValue: cellDefaultsStyles.elementGap, hasValue: elementGap => elementGap !== cellDefaultsStyles.elementGap, label: __("Common Element Spacing", "tableberg"), labelSelected: __("Element Spacing", "tableberg"), }); const selectedCellKeys = new Set(selectedCells); const cellEntries = getCellEntries(cells); const scopedCellEntries = selectedCells.length > 0 ? cellEntries.filter(([cellKey]) => selectedCellKeys.has(cellKey)) : cellEntries; const scopedElements = scopedCellEntries.flatMap( ([, elements]) => elements ); const fontOptionsRegistry = useRegistry() as any; // Element alignment lives on the element BLOCKS in the tree now. const forEachElementBlock = ( visit: (elBlock: any, dispatch: any) => void ) => { const be = fontOptionsRegistry.select(blockEditorStore); const beDispatch = fontOptionsRegistry.dispatch(blockEditorStore); const rowBlocks = be.getBlock(clientId)?.innerBlocks ?? []; fontOptionsRegistry.batch(() => { for (const rowBlock of rowBlocks) { for (const cellBlock of rowBlock.innerBlocks ?? []) { for (const elBlock of cellBlock.innerBlocks ?? []) { visit(elBlock, beDispatch); } } } }); }; const elementsAlignmentValue = (() => { const be = fontOptionsRegistry.select(blockEditorStore); const rowBlocks = be.getBlock(clientId)?.innerBlocks ?? []; let uniform: ElementAlignment | undefined; for (const rowBlock of rowBlocks) { for (const cellBlock of rowBlock.innerBlocks ?? []) { for (const elBlock of cellBlock.innerBlocks ?? []) { const align = elBlock.attributes?.align as | ElementAlignment | undefined; if (!align) { continue; } if (uniform === undefined) { uniform = align; } else if (uniform !== align) { return undefined; } } } } return uniform; })(); // The colour every text/list element shares, or undefined when they // differ, so the bulk colour rows below show what is applied. Selected // rather than read straight from the registry, so the rows follow the // elements as they change. const uniformElementColors = useSelect( select => { const be = select(blockEditorStore) as any; const rowBlocks = be.getBlock(clientId)?.innerBlocks ?? []; const seen = { textColor: new Set(), linkColor: new Set(), }; for (const rowBlock of rowBlocks) { for (const cellBlock of rowBlock.innerBlocks ?? []) { for (const elBlock of cellBlock.innerBlocks ?? []) { if ( elBlock.name !== "tableberg/text" && elBlock.name !== "tableberg/list" ) { continue; } const styles = elBlock.attributes?.styles ?? {}; seen.textColor.add(styles.textColor ?? ""); seen.linkColor.add(styles.linkColor ?? ""); } } } const shared = (values: Set) => values.size === 1 ? Array.from(values)[0] || undefined : undefined; return { textColor: shared(seen.textColor), linkColor: shared(seen.linkColor), }; }, [clientId] ); const elementsAlignmentControlProps = { label: __("Common Elements Alignment", "tableberg"), value: elementsAlignmentValue, onChange: (newAlignment: ElementAlignment) => { forEachElementBlock((elBlock, dispatch) => { dispatch.updateBlockAttributes(elBlock.clientId, { align: newAlignment, }); }); }, }; // Bulk-updates every text/list ELEMENT BLOCK in the table's tree (the // legacy store path below only worked when cell content lived in attrs). const updateElementFontOptions = (updates: { textColor?: string; linkColor?: string; fontSize?: string; }) => { const be = fontOptionsRegistry.select(blockEditorStore); const beDispatch = fontOptionsRegistry.dispatch(blockEditorStore); const rowBlocks = be.getBlock(clientId)?.innerBlocks ?? []; fontOptionsRegistry.batch(() => { for (const rowBlock of rowBlocks) { for (const cellBlock of rowBlock.innerBlocks ?? []) { for (const elBlock of cellBlock.innerBlocks ?? []) { if ( elBlock.name !== "tableberg/text" && elBlock.name !== "tableberg/list" ) { continue; } beDispatch.updateBlockAttributes(elBlock.clientId, { styles: { ...(elBlock.attributes?.styles ?? {}), ...(updates.textColor !== undefined ? { textColor: updates.textColor } : {}), ...(updates.linkColor !== undefined ? { linkColor: updates.linkColor } : {}), ...(updates.fontSize !== undefined ? { fontSize: updates.fontSize } : {}), }, }); } } } }); }; const legacyUpdateElementFontOptions = (updates: { textColor?: string; linkColor?: string; fontSize?: string; }) => { const nextCells = Object.fromEntries( cellEntries.map(([key, elements]) => { if (selectedCells.length > 0 && !selectedCellKeys.has(key)) { return [key, cells[key]]; } return [ key, { ...(cells[key] || {}), elements: elements.map(element => { if ( element.name === "text" || element.name === "list" ) { return { ...element, attributes: { ...element.attributes, styles: { ...element.attributes.styles, ...(updates.textColor !== undefined ? { textColor: updates.textColor, } : {}), ...(updates.linkColor !== undefined ? { linkColor: updates.linkColor, } : {}), ...(updates.fontSize !== undefined ? { fontSize: updates.fontSize } : {}), }, }, }; } if (element.name === "star-rating") { return { ...element, attributes: { ...element.attributes, ...(updates.textColor !== undefined ? { reviewTextColor: updates.textColor, } : {}), ...(updates.linkColor !== undefined ? { reviewTextLinkColor: updates.linkColor, } : {}), ...(updates.fontSize !== undefined ? { reviewTextFontSize: updates.fontSize, } : {}), }, }; } return element; }), }, ]; }) ) as Record; setCells(nextCells); }; const verticalAlignControlProps = useCellStyleControl({ styleKey: "verticalAlign", defaultValue: cellDefaultsStyles.verticalAlign, hasValue: verticalAlign => verticalAlign !== cellDefaultsStyles.verticalAlign, label: __("Common Elements Vertical Alignment", "tableberg"), labelSelected: __("Elements Vertical Alignment", "tableberg"), }); const { headerBackgroundColorControl, evenRowBackgroundColorControl, oddRowBackgroundColorControl, footerBackgroundColorControl, } = useBackgroundColorHelpers(); if (sortPreviewMode) { return null; } return ( <> {/* * Width and alignment in one control, the way the core blocks * put them: None is the table's normal width, wide and full * stretch it, and the three alignments only mean something * once the table has a width of its own. */} ({ ...control, isDisabled: !isFixedWidthMode, })), { icon: stretchWide, title: __("Wide width", "tableberg"), value: "wide", }, { icon: stretchFullWidth, title: __("Full width", "tableberg"), value: "full", }, ]} onChange={(newValue?: string) => { if (newValue === "wide" || newValue === "full") { updateConfig({ tableWidth: newValue }); return; } if (!newValue) { updateConfig({ tableWidth: "auto" }); return; } if (!isFixedWidthMode) { return; } updateConfig({ tableAlignment: newValue as TableAlignment, }); }} /> { const nextShowCaption = !showCaption; setShowCaption(nextShowCaption); if (!nextShowCaption && caption.trim() !== "") { updateConfig({ caption: "" }); } }} icon={captionIcon} isPressed={showCaption} label={ showCaption ? __("Remove caption", "tableberg") : __("Add caption", "tableberg") } /> { mergeCells(selectedCells, tableState); }} title="Merge Cells" icon={table} disabled={ !areAllMergeable(selectedCells, tableState.cells) } /> { if ( selectedTab === "settings" || selectedTab === "styles" || selectedTab === "datatable" ) { setActiveTab(selectedTab); } }} tabs={[ { name: "settings", title: __("Settings", "tableberg"), icon: cog, }, { name: "styles", title: __("Styles", "tableberg"), icon: stylesIcon, }, { name: "datatable", title: __("Datatable", "tableberg"), icon: table, }, ]} > {() => null} {selectedCells.length === 0 ? ( { updateConfig({ className: className || "", }); }} /> ) : ( (cells[key]?.className || "") === (cells[selectedCells[0]]?.className || "") ) ? cells[selectedCells[0]]?.className : "" } onChange={className => { const nextClassName = className || ""; setCells( Object.fromEntries( Object.entries(cells).map(([key, cell]) => { if ( !selectedCells.includes(key as CellKey) ) { return [key, cell]; } return [ key, { ...cell, className: nextClassName, }, ]; }) ) as Record ); }} /> )} {activeTab === "settings" && ( <> { if ( typeof newValue !== "string" || !newValue ) { return; } if (newValue === "fixed") { updateConfig({ tableAlignment, tableWidth: tableWidthMode === "fixed" ? tableWidth : DEFAULT_FIXED_TABLE_WIDTH, }); return; } if ( newValue === "auto" || newValue === "wide" || newValue === "full" ) { updateConfig({ tableAlignment, tableWidth: newValue, }); } }} isBlock > {isFixedWidthMode && ( { updateConfig({ tableWidth: newValue ? newValue.trim() : DEFAULT_FIXED_TABLE_WIDTH, }); }} /> )} {/* * Horizontal cell layout — and the wrap choice * that only matters with it — is a pro feature: * pro hands the whole control down as one prop. * Free has no implementation of its own, so * without pro the locked placeholder below is * all there is. */} {ProCellOrientationControl ?? ( null} > {CELL_ORIENTATION_OPTIONS.map( ({ value, icon, label }) => ( ) )} )} { elementGapControlProps.onChange(value); }} /> { if ( newAlignment !== "left" && newAlignment !== "center" && newAlignment !== "right" ) { return; } elementsAlignmentControlProps.onChange( newAlignment ); }} > {CELL_ALIGNMENT_OPTIONS.map( ({ value, icon, label }) => ( ) )} { if ( newVerticalAlign !== "top" && newVerticalAlign !== "middle" && newVerticalAlign !== "bottom" ) { return; } verticalAlignControlProps.onChange( newVerticalAlign ); }} > {CELL_VERTICAL_ALIGN_OPTIONS.map( ({ value, icon, label }) => ( ) )} { updateConfig({ headerEnabled }); }} /> { updateConfig({ footerEnabled }); }} /> {/* * Sticky header/first column are pro features: * pro hands the real toggles down as props. Free * has no implementation of its own, so without * pro the locked placeholders below are all * there is. */} {ProStickyHeaderControl ?? ( null} /> )} {ProStickyFirstColControl ?? ( null} /> )} )} {activeTab === "styles" && ( <> {selectedCells.length > 0 && !isPro ? ( ) : ( )} {headerBackgroundColorControl && ( )} {footerBackgroundColorControl && ( )} {/* * These two write the colour into every text and * list element rather than storing it on the * table, so they show the shared value when the * elements agree on one. */} { updateElementFontOptions({ textColor: textColor || "", }); }} onDeselect={() => { updateElementFontOptions({ textColor: textAttributeDefaults.styles .textColor, }); }} /> { updateElementFontOptions({ linkColor: linkColor || "", }); }} onDeselect={() => { updateElementFontOptions({ linkColor: "", }); }} /> { updateConfig({ typography: { ...defaultTypography, }, }); updateElementFontOptions({ fontSize: "", }); }} > { updateConfig({ typography: { ...typography, ...updates, }, }); // Tables saved before the table owned // its typography have a font size on // every text and list element, which // would win over the table's. Clearing // it lets the table's size through. if (updates.fontSize !== undefined) { updateElementFontOptions({ fontSize: "", }); } }} />
{ProBorderModeControls ?? ( <> null} /> null} /> )}
horizontalCellSpacing !== defaultCellSpacing.horizontal || verticalCellSpacing !== defaultCellSpacing.vertical } onChange={newValue => { updateConfig({ cellSpacing: fromCellSpacingPaddingValue( newValue, { horizontal: horizontalCellSpacing, vertical: verticalCellSpacing, }, defaultCellSpacing ), }); }} onDeselect={() => { updateConfig({ cellSpacing: { ...defaultCellSpacing }, }); }} />
)} {activeTab === "datatable" && ( <> {ProSearchControl ?? ( null} > false} onDeselect={() => null} isShownByDefault > null} /> )} )} {showDuplicateMoveUpsell && ( setShowDuplicateMoveUpsell(false)} selected="duplicate-row-col" /> )} ); } export default TablebergControls;