import React, { type ReactNode } from 'react'; import { SortAscendingSmall, SortDescendingSmall, ContentFilterSmall, HiddenSmall, DeleteSmall, EditSmall, FlagSmall, DuplicateSmall, ArchiveSmall, } from '@wix/wix-ui-icons-common'; import { withoutDefaults } from '@wix/bex-core'; import { cairoCtaClicked } from '@wix/bex-core/bi'; import type { ReportBI, Translate, TranslationsKeys } from '@wix/bex-core'; import type { HeaderAction, FieldActionsMenuConfig } from './headerActionTypes'; export type FieldActionKey = | 'editField' | 'makePrimary' | 'hideColumn' | 'duplicateField' | 'duplicateFieldWithContent' | 'sortAscending' | 'sortDescending' | 'filter' | 'archive' | 'delete'; interface FieldActionDef { /** Fallback (English) label, used when no `labelKey` is set or no translate fn is passed. */ label: string; /** Translation key; when set and a translate fn is passed to buildFieldActionGroups, used instead of `label`. */ labelKey?: TranslationsKeys; /** Shared data-hook for the rendered menu item — the same for every source and surface. */ dataHook: string; /** BI name reported via `cairoCtaClicked`; set only on the field-management actions that report. */ ctaName?: string; icon: ReactNode; skin?: 'destructive'; } export const FIELD_ACTIONS: Record = { editField: { label: 'Edit Field', labelKey: 'cairo.customFields.edit.CTA', dataHook: 'field-action-edit', ctaName: 'Edit', icon: , }, makePrimary: { label: 'Use as Primary Field', dataHook: 'field-action-make-primary', ctaName: 'Make Primary', icon: , }, hideColumn: { label: 'Hide Column', dataHook: 'field-action-hide', icon: , }, duplicateField: { label: 'Duplicate Field', dataHook: 'field-action-duplicate', ctaName: 'Duplicate', icon: , }, duplicateFieldWithContent: { label: 'Duplicate Field with Content', dataHook: 'field-action-duplicate-with-content', ctaName: 'Duplicate with Content', icon: , }, sortAscending: { label: 'Sort Ascending', dataHook: 'field-action-sort-ascending', icon: , }, sortDescending: { label: 'Sort Descending', dataHook: 'field-action-sort-descending', icon: , }, filter: { label: 'Filter', dataHook: 'field-action-filter', icon: , }, archive: { label: 'Archive', labelKey: 'cairo.customFields.archive.CTA', dataHook: 'field-action-archive', ctaName: 'Archive', icon: , }, delete: { label: 'Delete', labelKey: 'cairo.delete.button', dataHook: 'field-action-delete', ctaName: 'Delete', icon: , skin: 'destructive', }, }; export type FieldActionHandlers = Partial< Record void> >; export interface FieldActionHandlersConfig { handlers: FieldActionHandlers; loading?: Partial>; /** Per-action disabled state, owned by the source: a key present here renders * that action disabled with the given tooltip. */ disabledTooltips?: Partial>; /** Menu-level disabled reason: when set and there are no actions, the menu * renders a disabled trigger with this tooltip (e.g. a read-only source). */ disabledTooltip?: string; } export interface FieldActionsBIContext { reportBi: ReportBI; location: string; listSize: number; } const MENU_OPEN_CTA_NAME = 'More Actions Menu'; const MENU_DATA_HOOKS = { menuDataHook: 'field-actions-menu', buttonDataHook: 'field-actions-menu-button', }; const reportFieldActionCta = ( bi: FieldActionsBIContext, cta: { ctaName: string; ctaIndex: number; moreActionsIndex?: number; numCtas: number; }, ) => bi.reportBi( withoutDefaults(cairoCtaClicked)({ ctaIndex: cta.ctaIndex, ctaName: cta.ctaName, filteredListSize: bi.listSize, isSelectAll: false, location: bi.location, moreActionsIndex: cta.moreActionsIndex, numCtas: cta.numCtas, numItems: 1, url: typeof window !== 'undefined' ? window.location.href : '', }), ); export function buildFieldActionGroups(args: { columnId: string; handlers: FieldActionHandlers; loading?: Partial>; /** Per-action disabled tooltips owned by the source (key present ⇒ disabled). */ disabledTooltips?: Partial>; groups: FieldActionKey[][]; /** When provided, labels use the registry's `labelKey` (falling back to `label`). */ translate?: Translate; /** When provided, actions with a registry `ctaName` report `cairoCtaClicked` on click. */ bi?: FieldActionsBIContext; }): HeaderAction[][] { const { columnId, handlers, loading, disabledTooltips, groups, translate, bi, } = args; // BI-eligible actions (have a handler + a ctaName), in menu order — gives a // stable ctaIndex / numCtas no matter which source built the menu. const biKeys = groups .flat() .filter((key) => handlers[key] && FIELD_ACTIONS[key].ctaName); return groups .map((group) => group.flatMap((key): HeaderAction[] => { const handler = handlers[key]; if (!handler) { return []; } const def = FIELD_ACTIONS[key]; const disabledTooltip = disabledTooltips?.[key]; const ctaIndex = bi && def.ctaName ? biKeys.indexOf(key) + 1 : undefined; return [ { label: translate && def.labelKey ? translate(def.labelKey) : def.label, dataHook: def.dataHook, icon: def.icon, skin: def.skin, onClick: () => { if (bi && def.ctaName && ctaIndex) { reportFieldActionCta(bi, { ctaName: def.ctaName, ctaIndex, moreActionsIndex: ctaIndex, numCtas: biKeys.length, }); } handler(columnId); }, loading: loading?.[key], disabled: disabledTooltip !== undefined, disabledTooltip, }, ]; }), ) .filter((g) => g.length > 0); } export function buildFieldActionsMenu(args: { fieldId: string; config: FieldActionHandlersConfig | null; groups: FieldActionKey[][]; translate: Translate; tooltipContent?: string; bi?: FieldActionsBIContext; }): FieldActionsMenuConfig | null { const { fieldId, config, groups, translate, tooltipContent, bi } = args; const actions = config ? buildFieldActionGroups({ columnId: fieldId, handlers: config.handlers, loading: config.loading, disabledTooltips: config.disabledTooltips, groups, translate, bi, }) : []; if (actions.length === 0) { return config?.disabledTooltip ? { actions: [], disabledTooltip: config.disabledTooltip, tooltipContent, ...MENU_DATA_HOOKS, } : null; } const numCtas = actions.flat().length; return { actions, tooltipContent, ...MENU_DATA_HOOKS, onShow: bi ? () => reportFieldActionCta(bi, { ctaName: MENU_OPEN_CTA_NAME, ctaIndex: 0, numCtas, }) : undefined, }; }