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