UNPKG

@adaptabletools/adaptable

Version:

Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

296 lines (295 loc) 22.7 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import ErrorBox from '../../../components/ErrorBox'; import FormLayout, { FormRow } from '../../../components/FormLayout'; import SimpleButton from '../../../components/SimpleButton'; import { ButtonNew } from '../../Components/Buttons/ButtonNew'; import { Tag } from '../../../components/Tag'; import { renderSummaryLabelValueTags, renderSummaryStringTags, } from '../../Wizard/SummaryColorTag'; import ObjectFactory from '../../../Utilities/ObjectFactory'; import { AdaptableIconSelector } from '../../Components/AdaptableIconSelector'; import { PredicateEditor } from '../../Components/PredicateEditor/PredicateEditor'; import { useOnePageAdaptableWizardContext } from '../../Wizard/OnePageAdaptableWizard'; import { Badge } from '../../Components/Badge'; import { AdaptableIconComponent } from '../../Components/AdaptableIconComponent'; import { DEFAULT_INTEGER_DISPLAY_VALUE, DEFAULT_STRING_DISPLAY_VALUE, } from '../../../Utilities/Constants/GeneralConstants'; import { CheckBox } from '../../../components/CheckBox'; import { Flex, Box } from '../../../components/Flex'; import Radio, { RadioGroup } from '../../../components/Radio'; import { NumberInput } from '../../../components/Input/NumberInput'; import { Toggle, ToggleGroup } from '../../../components/Toggle'; import { BadgePillStyleEditor, getBadgePillStyleSummaryItems } from './BadgePillStyleEditor'; import { getCellBoxStyleSummaryItems, getCellFontStyleSummaryItems, StyledColumnCellStyleEditor, } from './StyledColumnSliceStyleEditors'; import { Card } from '../../../components/Card'; import { useAdaptable } from '../../AdaptableContext'; import { CollapsibleWizardCard, CollapsibleWizardValueSummary, getWizardAccordionSectionClassName, useWizardCardAccordion, } from '../../Wizard/CollapsibleWizardCard'; import { SingleSelect } from '../../../components/NewSelect'; import { cn } from '../../../lib/utils'; const BADGE_STYLE_FORM_SIZES = ['200px', '1fr']; const SHAPE_CHOICES = [ { value: 'Pill', label: 'Pill' }, { value: 'Rounded', label: 'Rounded' }, { value: 'Square', label: 'Square' }, ]; const DENSITY_CHOICES = [ { value: 'Compact', label: 'Compact', hint: 'Tight padding for dense grids' }, { value: 'Normal', label: 'Normal', hint: 'Balanced — default' }, { value: 'Comfortable', label: 'Comfortable', hint: 'Looser, for low-density grids' }, ]; const OVERFLOW_CHOICES = [ { value: 'Truncate', label: 'Truncate', hint: 'Single line; cell clips overflow' }, { value: 'Wrap', label: 'Wrap', hint: 'Wrap onto subsequent lines' }, { value: 'Scroll', label: 'Scroll', hint: 'Single line; cell scrolls horizontally' }, ]; const badgeCardId = (index) => `badge-${index}`; const getBadgePreviewValue = (dataType) => dataType === 'number' || dataType === 'numberArray' ? `${DEFAULT_INTEGER_DISPLAY_VALUE}` : DEFAULT_STRING_DISPLAY_VALUE; const getBadgeDefinitionSummaryItems = (badge, api) => { const items = []; let rule = 'Always'; if (badge.Predicate) { rule = api.predicateApi.predicateToString(badge.Predicate); } else if (badge.Expression) { rule = badge.Expression.BooleanExpression; } items.push({ label: 'Rule', value: rule }); items.push({ label: 'Shape', value: badge.Shape ?? 'Rounded' }); items.push(...getBadgePillStyleSummaryItems(badge.PillStyle)); const iconProps = badge.IconProperties; if (iconProps?.Icon) { items.push({ label: 'Icon', value: 'name' in iconProps.Icon ? iconProps.Icon.name : 'Custom', icon: iconProps.Icon, }); } if (iconProps?.IconOnly) { items.push({ label: 'Icon Only', value: 'Yes' }); } if (iconProps?.Icon && iconProps.Position) { items.push({ label: 'Icon Position', value: iconProps.Position }); } if (typeof iconProps?.Gap === 'number') { items.push({ label: 'Icon Gap', value: `${iconProps.Gap}px` }); } return items; }; const BadgePreview = ({ badge, density, previewValue }) => (_jsx(Badge, { pillStyle: badge.PillStyle, icon: badge.IconProperties?.Icon, iconPosition: badge.IconProperties?.Position, shape: badge.Shape, density: density, iconGap: badge.IconProperties?.Gap, children: badge.IconProperties?.IconOnly ? '' : previewValue })); const BadgeCardSummary = ({ badge, api }) => (_jsx(CollapsibleWizardValueSummary, { value: renderSummaryLabelValueTags(getBadgeDefinitionSummaryItems(badge, api)) })); const BadgeEditorForm = (props) => { const { api } = useOnePageAdaptableWizardContext(); const { badge, columnId, onChange } = props; const predicateDefs = api.styledColumnApi.internalApi.getBadgePredicateDefsForColumn(columnId); const currentShape = badge.Shape ?? 'Rounded'; const iconProps = badge.IconProperties; const hasIcon = Boolean(iconProps?.Icon); const updateIconProperties = (patch) => { const next = { ...(iconProps ?? { Icon: undefined }), ...patch, }; if (!next.Icon) { const cleaned = { ...badge }; delete cleaned.IconProperties; onChange(cleaned); return; } onChange({ ...badge, IconProperties: next }); }; return (_jsxs(Flex, { flexDirection: "column", className: "twa:gap-3 twa:p-2", children: [_jsxs(Card, { shadow: false, children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Shape" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Define shape of the corners for this badge" })] }), _jsx(Card.Body, { className: "twa:p-1", children: _jsx(RadioGroup, { orientation: "horizontal", name: `ab-badge-shape-${columnId}`, value: currentShape, onRadioChange: (value) => onChange({ ...badge, Shape: value }), className: "twa:gap-3", children: SHAPE_CHOICES.map((choice) => (_jsx(Radio, { value: choice.value, children: choice.label }, choice.value))) }) })] }), _jsxs(Card, { shadow: false, children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Style" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Colours and font styling for the badge pill" })] }), _jsx(Card.Body, { className: "twa:p-1", children: _jsx(BadgePillStyleEditor, { embedded: true, api: api, value: badge.PillStyle, onChange: (next) => { if (next) { onChange({ ...badge, PillStyle: next }); } else { const cleaned = { ...badge }; delete cleaned.PillStyle; onChange(cleaned); } } }) })] }), _jsxs(Card, { shadow: false, children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Rule" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Optional rule for when this badge is displayed \u2014 leave empty for Badge to always show" })] }), _jsx(Card.Body, { className: "twa:p-1", children: columnId ? (_jsx(PredicateEditor, { columnId: columnId, predicate: badge.Predicate, predicateDefs: predicateDefs, placeholder: "No Predicate Selected", onChange: (predicate) => { onChange({ ...badge, Predicate: predicate, }); }, onClear: () => onChange({ ...badge, Predicate: undefined, }) })) : (_jsx(ErrorBox, { children: "Select a column first" })) })] }), _jsxs(Card, { shadow: false, children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Icon" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Optional icon inside the badge \u2014 position and spacing apply once an icon is selected" })] }), _jsx(Card.Body, { className: "twa:p-1", children: _jsxs(FormLayout, { children: [_jsx(FormRow, { label: "Icon", children: !iconProps?.Icon || 'name' in iconProps.Icon ? (_jsx(AdaptableIconSelector, { value: iconProps?.Icon && 'name' in iconProps.Icon ? iconProps.Icon.name : undefined, onChange: (iconName) => { if (iconName) { updateIconProperties({ Icon: { name: iconName }, }); } else { const cleaned = { ...badge }; delete cleaned.IconProperties; onChange(cleaned); } } })) : (_jsx(AdaptableIconComponent, { icon: iconProps.Icon })) }), _jsx(FormRow, { label: "Icon Position", children: _jsx(Box, { className: "twa:max-w-[160px]", children: _jsx(SingleSelect, { disabled: !hasIcon, className: "twa:w-full", value: iconProps?.Position ?? 'Start', onValueChange: (v) => updateIconProperties({ Position: v }), items: [ { value: 'Start', label: 'Start' }, { value: 'End', label: 'End' }, ] }) }) }), _jsx(FormRow, { label: "Show Icon Only", children: _jsx(CheckBox, { disabled: !hasIcon, checked: iconProps?.IconOnly, onClick: () => updateIconProperties({ IconOnly: !iconProps?.IconOnly }) }) }), _jsx(FormRow, { label: "Icon Gap", children: _jsxs(Flex, { alignItems: "center", className: "twa:gap-2", children: [_jsx(NumberInput, { disabled: !hasIcon || iconProps?.IconOnly, value: iconProps?.Gap ?? '', placeholder: "auto", step: 1, min: 0, style: { width: 70 }, onChange: (value) => { updateIconProperties({ Gap: typeof value === 'number' && value >= 0 ? value : undefined, }); } }), _jsx(Box, { className: "twa:text-xs twa:opacity-70", children: "px between icon and text \u2014 leave blank to follow Density" })] }) })] }) })] })] })); }; export const getStyledColumnBadgeDefinitionViewValueGroups = (styledColumn, api) => { const badgeStyle = styledColumn.BadgeStyle; if (!badgeStyle?.Badges?.length) { return []; } return badgeStyle.Badges.map((badge) => getBadgeDefinitionSummaryItems(badge, api).map(({ label, value }) => `${label}: ${value}`)); }; export const getStyledColumnBadgeDefinitionViewValues = (styledColumn, api) => { return getStyledColumnBadgeDefinitionViewValueGroups(styledColumn, api).flat(); }; const isBadgeArrayColumn = (styledColumn, api) => { if (!api || !styledColumn.ColumnId) { return false; } const dataType = api.columnApi.getColumnDataTypeForColumnId(styledColumn.ColumnId); return dataType === 'numberArray' || dataType === 'textArray'; }; export const getStyledColumnBadgeStyleViewValues = (styledColumn, api) => { const badgeStyle = styledColumn.BadgeStyle; if (!badgeStyle) { return []; } const items = [`Density: ${badgeStyle.Density ?? 'Normal'}`]; if (isBadgeArrayColumn(styledColumn, api)) { if (typeof badgeStyle.Spacing === 'number') { items.push(`Spacing: ${badgeStyle.Spacing}px`); } items.push(`Overflow: ${badgeStyle.OverflowMode ?? 'Truncate'}`); } getCellFontStyleSummaryItems(badgeStyle.Font).forEach(({ label, value }) => { items.push(`${label}: ${value}`); }); getCellBoxStyleSummaryItems(badgeStyle.Cell).forEach(({ label, value }) => { items.push(`${label}: ${value}`); }); return items; }; export const renderBadgeDefinitionSummaryTags = (styledColumn, api) => { const badges = styledColumn.BadgeStyle?.Badges; if (!badges?.length) { return _jsx(Tag, { children: "No Badges Defined" }); } return (_jsx(Flex, { flexDirection: "column", className: "twa:gap-2", children: badges.map((badge, index) => (_jsxs(Flex, { alignItems: "center", className: "twa:flex-wrap twa:gap-2", children: [_jsxs(Tag, { className: "twa:shrink-0", children: ["Badge ", index + 1] }), renderSummaryLabelValueTags(getBadgeDefinitionSummaryItems(badge, api))] }, index))) })); }; export const StyledColumnBadgeDefinitionsView = ({ data }) => { const { api } = useAdaptable(); return renderBadgeDefinitionSummaryTags(data, api); }; export const renderBadgeSummary = (styledColumn, api) => { return renderBadgeDefinitionSummaryTags(styledColumn, api); }; export const StyledColumnBadgeSection = (props) => { const { data, api } = useOnePageAdaptableWizardContext(); const badgeStyle = data?.BadgeStyle ?? {}; const badges = badgeStyle.Badges ?? []; const currentDensity = badgeStyle.Density ?? 'Normal'; const dataType = data.ColumnId ? api.columnApi.getColumnDataTypeForColumnId(data.ColumnId) : 'text'; const previewValue = getBadgePreviewValue(dataType); const { bindCard, hasExpandedCard, expandedFillsSpace, expandedId, setExpandedId } = useWizardCardAccordion(null); const handleAddBadge = () => { const newIndex = badges.length; props.onChange({ ...data, BadgeStyle: { ...badgeStyle, Badges: [...badges, ObjectFactory.CreateDefaultStyledColumnBadge()], }, }); setExpandedId(badgeCardId(newIndex)); }; const handleDeleteBadge = (index) => { const cardId = badgeCardId(index); const newBadges = [...badges]; newBadges.splice(index, 1); props.onChange({ ...data, BadgeStyle: { ...badgeStyle, Badges: newBadges, }, }); if (expandedId === cardId) { setExpandedId(null); return; } if (expandedId?.startsWith('badge-')) { const expandedIndex = Number(expandedId.replace('badge-', '')); if (!Number.isNaN(expandedIndex) && expandedIndex > index) { setExpandedId(badgeCardId(expandedIndex - 1)); } } }; return (_jsxs(Box, { className: cn(getWizardAccordionSectionClassName(hasExpandedCard, expandedFillsSpace), 'twa:p-2'), children: [_jsx(Flex, { className: "twa:justify-end twa:mb-2 twa:shrink-0", children: _jsx(ButtonNew, { onClick: handleAddBadge, children: "Add Badge" }) }), _jsx(Flex, { flexDirection: "column", className: "twa:gap-3 twa:min-h-0 twa:overflow-y-auto", children: badges.map((badge, index) => { const handleEditBadge = (nextBadge) => { const newBadges = [...badges]; newBadges[index] = nextBadge; props.onChange({ ...data, BadgeStyle: { ...badgeStyle, Badges: newBadges, }, }); }; return (_jsx(CollapsibleWizardCard, { ...bindCard(badgeCardId(index)), surface: "panel", "data-name": `styled-column-badge-${index}`, title: `Badge ${index + 1}`, collapsedHelp: false, headerVisual: _jsx(BadgePreview, { badge: badge, density: currentDensity, previewValue: previewValue }), headerActions: _jsx(SimpleButton, { icon: "delete", variant: "text", tooltip: "Delete badge", onClick: () => handleDeleteBadge(index) }), summary: _jsx(BadgeCardSummary, { badge: badge, api: api }), bodyClassName: "twa:!pt-0 twa:overflow-y-auto", children: _jsx(BadgeEditorForm, { badge: badge, columnId: data.ColumnId, onChange: handleEditBadge }) }, badgeCardId(index))); }) })] })); }; export const StyledColumnBadgeStyleSection = (props) => { const { data, api } = useOnePageAdaptableWizardContext(); const badgeStyle = data?.BadgeStyle ?? {}; const disabled = !data?.ColumnId; const dataType = data?.ColumnId ? api.columnApi.getColumnDataTypeForColumnId(data.ColumnId) : undefined; const isArrayColumn = dataType === 'numberArray' || dataType === 'textArray'; const updateBadgeStyle = (patch) => { props.onChange({ ...data, BadgeStyle: { ...badgeStyle, ...patch }, }); }; const currentDensity = badgeStyle.Density ?? 'Normal'; const currentOverflow = badgeStyle.OverflowMode ?? 'Truncate'; const currentAlignment = badgeStyle.Font?.Alignment; const updateBadgeRowAlignment = (alignment) => { const nextFont = alignment ? { Alignment: alignment } : undefined; if (nextFont) { updateBadgeStyle({ Font: nextFont }); } else { const cleaned = { ...badgeStyle }; delete cleaned.Font; props.onChange({ ...data, BadgeStyle: cleaned }); } }; return (_jsxs(Box, { children: [_jsxs(Card, { shadow: false, className: "twa:mb-3", children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Cell Density" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Padding in cells containing Badge" })] }), _jsx(Card.Body, { children: _jsx(RadioGroup, { orientation: "vertical", name: "ab-badgestyle-density", value: currentDensity, onRadioChange: (value) => updateBadgeStyle({ Density: value }), children: DENSITY_CHOICES.map((choice) => (_jsx(Radio, { value: choice.value, disabled: disabled, children: _jsxs(Flex, { alignItems: "center", children: [_jsx(Box, { className: "twa:min-w-[110px]", children: choice.label }), _jsx(Box, { className: "twa:text-xs twa:opacity-70", children: choice.hint })] }) }, choice.value))) }) })] }), isArrayColumn && (_jsxs(_Fragment, { children: [_jsxs(Card, { shadow: false, className: "twa:mb-3", children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Inter-badge spacing" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Set the spacing between adjacent badges" })] }), _jsx(Card.Body, { children: _jsx(FormLayout, { sizes: [...BADGE_STYLE_FORM_SIZES], children: _jsx(FormRow, { label: "Inter-badge spacing", children: _jsxs(Flex, { alignItems: "center", className: "twa:ml-2 twa:gap-2", children: [_jsx(NumberInput, { disabled: disabled, value: badgeStyle.Spacing ?? '', placeholder: "4", step: 1, min: 0, style: { width: 70 }, onChange: (value) => { updateBadgeStyle({ Spacing: typeof value === 'number' && value >= 0 ? value : undefined, }); } }), _jsx(Box, { className: "twa:text-xs twa:opacity-70", children: "px between adjacent badges" })] }) }) }) })] }), _jsxs(Card, { shadow: false, className: "twa:mb-3", children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Overflow" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Configure the overflow mode for badges" })] }), _jsx(Card.Body, { children: _jsx(FormLayout, { sizes: [...BADGE_STYLE_FORM_SIZES], children: _jsx(FormRow, { label: "Overflow mode", children: _jsx(Box, { className: "twa:ml-2", children: _jsx(RadioGroup, { orientation: "vertical", name: "ab-badgestyle-overflow", value: currentOverflow, onRadioChange: (value) => updateBadgeStyle({ OverflowMode: value }), children: OVERFLOW_CHOICES.map((choice) => (_jsx(Radio, { value: choice.value, disabled: disabled, children: _jsxs(Flex, { alignItems: "center", children: [_jsx(Box, { className: "twa:min-w-[110px]", children: choice.label }), _jsx(Box, { className: "twa:text-xs twa:opacity-70", children: choice.hint })] }) }, choice.value))) }) }) }) }) })] })] })), _jsxs(Card, { shadow: false, children: [_jsxs(Card.Title, { children: [_jsx(Box, { className: "twa:font-medium", children: "Cell Style" }), _jsx(Box, { className: "twa:text-xs twa:opacity-70 twa:font-normal twa:max-w-[520px]", children: "Styling and alignment to apply to Badge cells (overrides Format Column)" })] }), _jsxs(Card.Body, { children: [_jsx(FormLayout, { sizes: [...BADGE_STYLE_FORM_SIZES], children: _jsx(FormRow, { label: "Alignment:", children: _jsx(Flex, { alignItems: "center", className: "twa:gap-2", children: _jsxs(ToggleGroup, { children: [_jsx(Toggle, { icon: "align-left", pressed: currentAlignment === 'Left', onPressedChange: (pressed) => updateBadgeRowAlignment(pressed ? 'Left' : undefined) }), _jsx(Toggle, { icon: "align-center", pressed: currentAlignment === 'Center', onPressedChange: (pressed) => updateBadgeRowAlignment(pressed ? 'Center' : undefined) }), _jsx(Toggle, { icon: "align-right", pressed: currentAlignment === 'Right', onPressedChange: (pressed) => updateBadgeRowAlignment(pressed ? 'Right' : undefined) })] }) }) }) }), _jsx(Box, { className: "twa:mt-3", children: _jsx(StyledColumnCellStyleEditor, { api: api, disabled: disabled, value: badgeStyle.Cell, onChange: (next) => { if (next) { updateBadgeStyle({ Cell: next }); } else { const cleaned = { ...badgeStyle }; delete cleaned.Cell; props.onChange({ ...data, BadgeStyle: cleaned }); } } }) })] })] })] })); }; export const renderBadgeStyleSummary = (styledColumn, api) => { const items = getStyledColumnBadgeStyleViewValues(styledColumn, api); if (!items.length) { return _jsx(Tag, { children: "No Style Defined" }); } return renderSummaryStringTags(items); };