@mx-cartographer/experiences 9.4.16 → 9.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/common/components/dialog/Dialog.es.js +36 -36
  3. package/dist/common/components/dialog/Dialog.es.js.map +1 -1
  4. package/dist/core/types/localization/InsightsFeedCopy.d.ts +59 -0
  5. package/dist/core/types/localization/index.d.ts +1 -1
  6. package/dist/insights-v2/beatActions.d.ts +25 -0
  7. package/dist/insights-v2/beatActions.es.js.map +1 -1
  8. package/dist/insights-v2/components/BaseLayout.d.ts +3 -1
  9. package/dist/insights-v2/components/BaseLayout.es.js +67 -37
  10. package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
  11. package/dist/insights-v2/components/DismissInsightDialog.d.ts +18 -0
  12. package/dist/insights-v2/components/DismissInsightDialog.es.js +109 -0
  13. package/dist/insights-v2/components/DismissInsightDialog.es.js.map +1 -0
  14. package/dist/insights-v2/components/FeedbackBar.d.ts +20 -0
  15. package/dist/insights-v2/components/FeedbackBar.es.js +64 -0
  16. package/dist/insights-v2/components/FeedbackBar.es.js.map +1 -0
  17. package/dist/insights-v2/components/FeedbackDialog.d.ts +37 -0
  18. package/dist/insights-v2/components/FeedbackDialog.es.js +92 -0
  19. package/dist/insights-v2/components/FeedbackDialog.es.js.map +1 -0
  20. package/dist/insights-v2/components/HiddenConfirmationCard.d.ts +20 -0
  21. package/dist/insights-v2/components/HiddenConfirmationCard.es.js +123 -0
  22. package/dist/insights-v2/components/HiddenConfirmationCard.es.js.map +1 -0
  23. package/dist/insights-v2/hooks/useBeatFeedbackFlow.d.ts +44 -0
  24. package/dist/insights-v2/hooks/useBeatFeedbackFlow.es.js +73 -0
  25. package/dist/insights-v2/hooks/useBeatFeedbackFlow.es.js.map +1 -0
  26. package/dist/insights-v2/index.d.ts +5 -1
  27. package/dist/insights-v2/index.es.js +56 -48
  28. package/package.json +1 -1
@@ -0,0 +1,20 @@
1
+ export interface FeedbackBarProps {
2
+ /** Controls the Grow transition; the host owns visibility. */
3
+ show: boolean;
4
+ /** Invoked when the user taps the follow-up survey action. */
5
+ onTellUsMore: () => void;
6
+ }
7
+ /**
8
+ * Confirmation bar shown after a user submits feedback on an insight. A success
9
+ * Alert that grows into view with a "Tell us more?" survey action.
10
+ *
11
+ * Presentational — the host owns `show` and handles the follow-up via
12
+ * `onTellUsMore`. Copy comes from `insights_feed.feedback_bar`.
13
+ *
14
+ * Kept mounted so the Grow transition plays on both enter and exit; the padded
15
+ * wrapper animates with it and `unmountOnExit` reclaims the space once hidden.
16
+ */
17
+ export declare const FeedbackBar: (({ show, onTellUsMore }: FeedbackBarProps) => import("react/jsx-runtime").JSX.Element | null) & {
18
+ displayName: string;
19
+ };
20
+ export default FeedbackBar;
@@ -0,0 +1,64 @@
1
+ import { useGlobalCopyStore as i } from "../../common/context/hooks.es.js";
2
+ import { observer as l } from "mobx-react-lite";
3
+ import { Text as n } from "@mxenabled/mxui";
4
+ import c from "@mui/material/Button";
5
+ import { CheckCircle as s } from "@mxenabled/mx-icons";
6
+ import { jsx as e } from "react/jsx-runtime";
7
+ import a from "@mui/material/Box";
8
+ import m from "@mui/material/Alert";
9
+ import p from "@mui/material/Grow";
10
+ var v = l(({ show: o, onTellUsMore: t }) => {
11
+ const r = i().insights_feed?.feedback_bar;
12
+ return r ? /* @__PURE__ */ e(p, {
13
+ appear: !0,
14
+ in: o,
15
+ unmountOnExit: !0,
16
+ children: /* @__PURE__ */ e(a, {
17
+ sx: {
18
+ pb: 16,
19
+ px: 16
20
+ },
21
+ children: /* @__PURE__ */ e(m, {
22
+ action: /* @__PURE__ */ e(c, {
23
+ "aria-label": r.tell_us_more,
24
+ color: "inherit",
25
+ onClick: t,
26
+ sx: {
27
+ color: "common.white",
28
+ fontWeight: 600,
29
+ lineHeight: "20px",
30
+ ":hover": { bgcolor: "grey.600" }
31
+ },
32
+ children: r.tell_us_more
33
+ }),
34
+ icon: /* @__PURE__ */ e(s, {
35
+ filled: !0,
36
+ sx: { color: "success.light" }
37
+ }),
38
+ severity: "success",
39
+ sx: {
40
+ alignItems: "center",
41
+ bgcolor: "grey.700",
42
+ border: "none",
43
+ display: "flex",
44
+ height: 56,
45
+ justifyContent: "space-between",
46
+ "& .MuiAlert-action": { height: 44 }
47
+ },
48
+ children: /* @__PURE__ */ e(n, {
49
+ "aria-live": "assertive",
50
+ color: "white",
51
+ sx: { lineHeight: "20px" },
52
+ truncate: !1,
53
+ children: r.feedback_sent
54
+ })
55
+ })
56
+ })
57
+ }) : null;
58
+ });
59
+ export {
60
+ v as FeedbackBar,
61
+ v as default
62
+ };
63
+
64
+ //# sourceMappingURL=FeedbackBar.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeedbackBar.es.js","names":[],"sources":["../../../src/insights-v2/components/FeedbackBar.tsx"],"sourcesContent":["import Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Grow from '@mui/material/Grow'\n\nimport { observer } from 'mobx-react-lite'\n\nimport { CheckCircle } from '@mxenabled/mx-icons'\nimport { Text } from '@mxenabled/mxui'\n\nimport { useGlobalCopyStore } from '../../common'\n\nexport interface FeedbackBarProps {\n /** Controls the Grow transition; the host owns visibility. */\n show: boolean\n /** Invoked when the user taps the follow-up survey action. */\n onTellUsMore: () => void\n}\n\n/**\n * Confirmation bar shown after a user submits feedback on an insight. A success\n * Alert that grows into view with a \"Tell us more?\" survey action.\n *\n * Presentational — the host owns `show` and handles the follow-up via\n * `onTellUsMore`. Copy comes from `insights_feed.feedback_bar`.\n *\n * Kept mounted so the Grow transition plays on both enter and exit; the padded\n * wrapper animates with it and `unmountOnExit` reclaims the space once hidden.\n */\nexport const FeedbackBar = observer(({ show, onTellUsMore }: FeedbackBarProps) => {\n const copy = useGlobalCopyStore().insights_feed?.feedback_bar\n\n if (!copy) return null\n\n return (\n <Grow appear in={show} unmountOnExit>\n <Box sx={{ pb: 16, px: 16 }}>\n <Alert\n action={\n <Button\n aria-label={copy.tell_us_more}\n color=\"inherit\"\n onClick={onTellUsMore}\n sx={{\n color: 'common.white',\n fontWeight: 600,\n lineHeight: '20px',\n ':hover': { bgcolor: 'grey.600' },\n }}\n >\n {copy.tell_us_more}\n </Button>\n }\n icon={<CheckCircle filled sx={{ color: 'success.light' }} />}\n severity=\"success\"\n sx={{\n alignItems: 'center',\n bgcolor: 'grey.700',\n border: 'none',\n display: 'flex',\n height: 56,\n justifyContent: 'space-between',\n '& .MuiAlert-action': { height: 44 },\n }}\n >\n <Text aria-live=\"assertive\" color=\"white\" sx={{ lineHeight: '20px' }} truncate={false}>\n {copy.feedback_sent}\n </Text>\n </Alert>\n </Box>\n </Grow>\n )\n})\n\nexport default FeedbackBar\n"],"mappings":";;;;;;;;;AA6BA,IAAa,IAAc,EAAA,CAAU,EAAE,MAAA,GAAM,cAAA,EAAA,MAAqC;AAChF,QAAM,IAAO,EAAmB,EAAE,eAAe;AAEjD,SAAK,IAGH,gBAAA,EAAC,GAAD;AAAA,IAAM,QAAA;AAAA,IAAO,IAAI;AAAA,IAAM,eAAA;AAAA,IACrB,UAAA,gBAAA,EAAC,GAAD;AAAA,MAAK,IAAI;AAAA,QAAE,IAAI;AAAA,QAAI,IAAI;AAAA,MAAG;AAAA,MACxB,UAAA,gBAAA,EAAC,GAAD;AAAA,QACE,QACE,gBAAA,EAAC,GAAD;AAAA,UACE,cAAY,EAAK;AAAA,UACjB,OAAM;AAAA,UACN,SAAS;AAAA,UACT,IAAI;AAAA,YACF,OAAO;AAAA,YACP,YAAY;AAAA,YACZ,YAAY;AAAA,YACZ,UAAU,EAAE,SAAS,WAAW;AAAA,UAClC;AAAA,UAEC,UAAA,EAAK;AAAA,QACA,CAAA;AAAA,QAEV,MAAM,gBAAA,EAAC,GAAD;AAAA,UAAa,QAAA;AAAA,UAAO,IAAI,EAAE,OAAO,gBAAgB;AAAA,QAAI,CAAA;AAAA,QAC3D,UAAS;AAAA,QACT,IAAI;AAAA,UACF,YAAY;AAAA,UACZ,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,gBAAgB;AAAA,UAChB,sBAAsB,EAAE,QAAQ,GAAG;AAAA,QACrC;AAAA,QAEA,UAAA,gBAAA,EAAC,GAAD;AAAA,UAAM,aAAU;AAAA,UAAY,OAAM;AAAA,UAAQ,IAAI,EAAE,YAAY,OAAO;AAAA,UAAG,UAAU;AAAA,UAC7E,UAAA,EAAK;AAAA,QACF,CAAA;AAAA,MACD,CAAA;AAAA,IACJ,CAAA;AAAA,EACD,CAAA,IAtCU;AAwCpB,CAAC"}
@@ -0,0 +1,37 @@
1
+ export interface FeedbackDialogProps {
2
+ /** Dialog title. */
3
+ title: string;
4
+ /** Available feedback reasons. The last option reveals the detail field. */
5
+ options: string[];
6
+ /** Max characters for the detail text field (enforced on the input). */
7
+ characterLimit?: number;
8
+ /** Localized helper text shown under the detail field (e.g. "250 Character Limit"). */
9
+ characterLimitLabel?: string;
10
+ /** Whether the dialog is open. */
11
+ isOpen: boolean;
12
+ /** Invoked on primary action (Submit); parent owns analytics + dispatch. */
13
+ onSubmit: (option: string, detail?: string) => void;
14
+ /** Invoked on close (X, Cancel, or Submit); parent clears dialog state. */
15
+ onClose: () => void;
16
+ /** Button text for secondary (Back/Cancel) action. */
17
+ secondaryText: string;
18
+ /** Button text for primary (Submit) action. */
19
+ primaryText: string;
20
+ /** ARIA label for close button. */
21
+ closeAriaLabel: string;
22
+ }
23
+ /**
24
+ * Generic feedback survey dialog for collecting reasons and optional detail.
25
+ * Presentational — the parent owns all copy, option definitions, and callback logic.
26
+ *
27
+ * This component can be used by any feature that needs to ask users for feedback.
28
+ * Examples: rating an insight, dismissing a notification, rating a recommendation.
29
+ *
30
+ * The dialog is a single-select radio list of reasons; selecting the last option
31
+ * ("Other") reveals a multiline text field. Submit is disabled until a valid
32
+ * answer exists.
33
+ *
34
+ * Figma: 287:20484 (options only) / 287:20566 (with text field).
35
+ */
36
+ declare const FeedbackDialog: ({ isOpen, onClose, onSubmit, title, options, characterLimit, characterLimitLabel, secondaryText, primaryText, closeAriaLabel, }: FeedbackDialogProps) => import("react/jsx-runtime").JSX.Element | null;
37
+ export default FeedbackDialog;
@@ -0,0 +1,92 @@
1
+ import S from "../../common/components/dialog/Dialog.es.js";
2
+ import { useState as c } from "react";
3
+ import y from "@mui/material/Stack";
4
+ import { Text as D } from "@mxenabled/mxui";
5
+ import { jsx as e, jsxs as d } from "react/jsx-runtime";
6
+ import M from "@mui/material/FormControlLabel";
7
+ import F from "@mui/material/TextField";
8
+ import R from "@mui/material/Radio";
9
+ import k from "@mui/material/FormControl";
10
+ import I from "@mui/material/RadioGroup";
11
+ var P = 250, W = ({ isOpen: p, onClose: g, onSubmit: b, title: i, options: r, characterLimit: f = P, characterLimitLabel: n, secondaryText: h, primaryText: x, closeAriaLabel: v }) => {
12
+ const [t, m] = c(""), [l, s] = c("");
13
+ if (r.length === 0) return null;
14
+ const a = t === r[r.length - 1], C = !t || a && !l.trim(), u = () => {
15
+ m(""), s("");
16
+ };
17
+ return /* @__PURE__ */ d(S, {
18
+ copy: {
19
+ title: i,
20
+ close_aria: v
21
+ },
22
+ disablePrimaryButton: C,
23
+ isOpen: p,
24
+ onClose: () => {
25
+ u(), g();
26
+ },
27
+ onPrimaryAction: () => {
28
+ b(t, a ? l.trim() : void 0), u();
29
+ },
30
+ primaryText: x,
31
+ secondaryText: h,
32
+ sx: {
33
+ "& .MuiDialogContent-root": { py: 0 },
34
+ "& .MuiDialog-paperScrollPaper": { bgcolor: "background.paper" },
35
+ "& .MuiDialog-paper": {
36
+ outline: "none",
37
+ "&:focus-visible": {
38
+ outline: "2px solid",
39
+ outlineColor: "primary.main"
40
+ }
41
+ },
42
+ "& .MuiButton-outlined": {
43
+ border: "none",
44
+ "&:hover": {
45
+ bgcolor: "transparent",
46
+ border: "none"
47
+ }
48
+ }
49
+ },
50
+ children: [/* @__PURE__ */ e(k, { children: /* @__PURE__ */ e(I, {
51
+ "aria-label": i,
52
+ name: "feedback-radio-group",
53
+ onChange: (o) => m(o.target.value),
54
+ value: t,
55
+ children: r.map((o) => /* @__PURE__ */ e(M, {
56
+ control: /* @__PURE__ */ e(R, { sx: { "& .MuiSvgIcon-root": { fontSize: 22 } } }),
57
+ label: o,
58
+ sx: { "& .MuiFormControlLabel-label": {
59
+ color: "text.primary",
60
+ fontSize: 15
61
+ } },
62
+ value: o
63
+ }, o))
64
+ }) }), a && /* @__PURE__ */ d(y, {
65
+ sx: {
66
+ gap: 3,
67
+ mt: 4
68
+ },
69
+ children: [/* @__PURE__ */ e(F, {
70
+ autoFocus: !0,
71
+ maxRows: 4,
72
+ minRows: 4,
73
+ multiline: !0,
74
+ onChange: (o) => s(o.target.value),
75
+ slotProps: { htmlInput: { maxLength: f } },
76
+ value: l
77
+ }), n && /* @__PURE__ */ e(D, {
78
+ sx: {
79
+ color: "text.secondary",
80
+ fontWeight: 400
81
+ },
82
+ variant: "subtitle2",
83
+ children: n
84
+ })]
85
+ })]
86
+ });
87
+ };
88
+ export {
89
+ W as default
90
+ };
91
+
92
+ //# sourceMappingURL=FeedbackDialog.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeedbackDialog.es.js","names":[],"sources":["../../../src/insights-v2/components/FeedbackDialog.tsx"],"sourcesContent":["import { useState } from 'react'\n\nimport FormControl from '@mui/material/FormControl'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\n\nimport { Text } from '@mxenabled/mxui'\n\nimport { Dialog } from '../../common'\n\nconst CHARACTER_LIMIT = 250\n\nexport interface FeedbackDialogProps {\n /** Dialog title. */\n title: string\n /** Available feedback reasons. The last option reveals the detail field. */\n options: string[]\n /** Max characters for the detail text field (enforced on the input). */\n characterLimit?: number\n /** Localized helper text shown under the detail field (e.g. \"250 Character Limit\"). */\n characterLimitLabel?: string\n /** Whether the dialog is open. */\n isOpen: boolean\n /** Invoked on primary action (Submit); parent owns analytics + dispatch. */\n onSubmit: (option: string, detail?: string) => void\n /** Invoked on close (X, Cancel, or Submit); parent clears dialog state. */\n onClose: () => void\n /** Button text for secondary (Back/Cancel) action. */\n secondaryText: string\n /** Button text for primary (Submit) action. */\n primaryText: string\n /** ARIA label for close button. */\n closeAriaLabel: string\n}\n\n/**\n * Generic feedback survey dialog for collecting reasons and optional detail.\n * Presentational — the parent owns all copy, option definitions, and callback logic.\n *\n * This component can be used by any feature that needs to ask users for feedback.\n * Examples: rating an insight, dismissing a notification, rating a recommendation.\n *\n * The dialog is a single-select radio list of reasons; selecting the last option\n * (\"Other\") reveals a multiline text field. Submit is disabled until a valid\n * answer exists.\n *\n * Figma: 287:20484 (options only) / 287:20566 (with text field).\n */\nconst FeedbackDialog = ({\n isOpen,\n onClose,\n onSubmit,\n title,\n options,\n characterLimit = CHARACTER_LIMIT,\n characterLimitLabel,\n secondaryText,\n primaryText,\n closeAriaLabel,\n}: FeedbackDialogProps) => {\n const [selected, setSelected] = useState('')\n const [detail, setDetail] = useState('')\n\n if (options.length === 0) return null\n\n const isOther = selected === options[options.length - 1]\n const isSubmitDisabled = !selected || (isOther && !detail.trim())\n\n const reset = () => {\n setSelected('')\n setDetail('')\n }\n\n const handleClose = () => {\n reset()\n onClose()\n }\n\n const handleSubmit = () => {\n onSubmit(selected, isOther ? detail.trim() : undefined)\n reset()\n }\n\n return (\n <Dialog\n copy={{ title, close_aria: closeAriaLabel }}\n disablePrimaryButton={isSubmitDisabled}\n isOpen={isOpen}\n onClose={handleClose}\n onPrimaryAction={handleSubmit}\n primaryText={primaryText}\n secondaryText={secondaryText}\n sx={{\n // Radio section has no vertical padding per Figma; keep horizontal.\n '& .MuiDialogContent-root': { py: 0 },\n // White surface, overriding the shared Dialog's default grey\n // (background.default) for this dialog only — the shared component is untouched.\n '& .MuiDialog-paperScrollPaper': { bgcolor: 'background.paper' },\n // Paper receives focus on open; hide the browser's default outline\n // while keeping focus for keyboard / screen-reader accessibility.\n '& .MuiDialog-paper': {\n outline: 'none',\n '&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main' },\n },\n // The shared Dialog footer defaults the secondary (Back) button to\n // variant=\"outlined\"; Figma shows it as a plain text button.\n '& .MuiButton-outlined': {\n border: 'none',\n '&:hover': { bgcolor: 'transparent', border: 'none' },\n },\n }}\n >\n <FormControl>\n <RadioGroup\n aria-label={title}\n name=\"feedback-radio-group\"\n onChange={(e) => setSelected(e.target.value)}\n value={selected}\n >\n {options.map((option) => (\n <FormControlLabel\n control={<Radio sx={{ '& .MuiSvgIcon-root': { fontSize: 22 } }} />}\n key={option}\n label={option}\n sx={{\n '& .MuiFormControlLabel-label': {\n color: 'text.primary',\n fontSize: 15,\n },\n }}\n value={option}\n />\n ))}\n </RadioGroup>\n </FormControl>\n\n {isOther && (\n <Stack sx={{ gap: 3, mt: 4 }}>\n <TextField\n autoFocus\n maxRows={4}\n minRows={4}\n multiline={true}\n onChange={(e) => setDetail(e.target.value)}\n slotProps={{ htmlInput: { maxLength: characterLimit } }}\n value={detail}\n />\n {characterLimitLabel && (\n <Text sx={{ color: 'text.secondary', fontWeight: 400 }} variant=\"subtitle2\">\n {characterLimitLabel}\n </Text>\n )}\n </Stack>\n )}\n </Dialog>\n )\n}\n\nexport default FeedbackDialog\n"],"mappings":";;;;;;;;;;AAaA,IAAM,IAAkB,KAsClB,IAAA,CAAkB,EACtB,QAAA,GACA,SAAA,GACA,UAAA,GACA,OAAA,GACA,SAAA,GACA,gBAAA,IAAiB,GACjB,qBAAA,GACA,eAAA,GACA,aAAA,GACA,gBAAA,EAAA,MACyB;AACzB,QAAM,CAAC,GAAU,CAAA,IAAe,EAAS,EAAE,GACrC,CAAC,GAAQ,CAAA,IAAa,EAAS,EAAE;AAEvC,MAAI,EAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,IAAU,MAAa,EAAQ,EAAQ,SAAS,CAAA,GAChD,IAAmB,CAAC,KAAa,KAAW,CAAC,EAAO,KAAK,GAEzD,IAAA,MAAc;AAClB,IAAA,EAAY,EAAE,GACd,EAAU,EAAE;AAAA,EACd;AAYA,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,MAAM;AAAA,MAAE,OAAA;AAAA,MAAO,YAAY;AAAA,IAAe;AAAA,IAC1C,sBAAsB;AAAA,IACd,QAAA;AAAA,IACR,SAfE,MAAoB;AACxB,MAAA,EAAM,GACN,EAAQ;AAAA,IACV;AAAA,IAaI,iBAXE,MAAqB;AACzB,MAAA,EAAS,GAAU,IAAU,EAAO,KAAK,IAAI,MAAS,GACtD,EAAM;AAAA,IACR;AAAA,IASiB,aAAA;AAAA,IACE,eAAA;AAAA,IACf,IAAI;AAAA,MAEF,4BAA4B,EAAE,IAAI,EAAE;AAAA,MAGpC,iCAAiC,EAAE,SAAS,mBAAmB;AAAA,MAG/D,sBAAsB;AAAA,QACpB,SAAS;AAAA,QACT,mBAAmB;AAAA,UAAE,SAAS;AAAA,UAAa,cAAc;AAAA,QAAe;AAAA,MAC1E;AAAA,MAGA,yBAAyB;AAAA,QACvB,QAAQ;AAAA,QACR,WAAW;AAAA,UAAE,SAAS;AAAA,UAAe,QAAQ;AAAA,QAAO;AAAA,MACtD;AAAA,IACF;AAAA,IA1BF,UAAA,CA4BE,gBAAA,EAAC,GAAD,EAAA,UACE,gBAAA,EAAC,GAAD;AAAA,MACE,cAAY;AAAA,MACZ,MAAK;AAAA,MACL,UAAA,CAAW,MAAM,EAAY,EAAE,OAAO,KAAK;AAAA,MAC3C,OAAO;AAAA,MAEN,UAAA,EAAQ,IAAA,CAAK,MACZ,gBAAA,EAAC,GAAD;AAAA,QACE,SAAS,gBAAA,EAAC,GAAD,EAAO,IAAI,EAAE,sBAAsB,EAAE,UAAU,GAAG,EAAE,EAAI,CAAA;AAAA,QAEjE,OAAO;AAAA,QACP,IAAI,EACF,gCAAgC;AAAA,UAC9B,OAAO;AAAA,UACP,UAAU;AAAA,QACZ,EACF;AAAA,QACA,OAAO;AAAA,MACR,GATM,CASN,CACF;AAAA,IACS,CAAA,EACD,CAAA,GAEZ,KACC,gBAAA,EAAC,GAAD;AAAA,MAAO,IAAI;AAAA,QAAE,KAAK;AAAA,QAAG,IAAI;AAAA,MAAE;AAAA,MAA3B,UAAA,CACE,gBAAA,EAAC,GAAD;AAAA,QACE,WAAA;AAAA,QACA,SAAS;AAAA,QACT,SAAS;AAAA,QACT,WAAW;AAAA,QACX,UAAA,CAAW,MAAM,EAAU,EAAE,OAAO,KAAK;AAAA,QACzC,WAAW,EAAE,WAAW,EAAE,WAAW,EAAe,EAAE;AAAA,QACtD,OAAO;AAAA,MACR,CAAA,GACA,KACC,gBAAA,EAAC,GAAD;AAAA,QAAM,IAAI;AAAA,UAAE,OAAO;AAAA,UAAkB,YAAY;AAAA,QAAI;AAAA,QAAG,SAAQ;AAAA,QAC7D,UAAA;AAAA,MACG,CAAA,CAEH;AAAA,IAEH,CAAA,CAAA;AAAA;AAEZ"}
@@ -0,0 +1,20 @@
1
+ export interface HiddenConfirmationCardProps {
2
+ onClose: () => void;
3
+ onUndo: () => void;
4
+ onLeaveFeedback: () => void;
5
+ }
6
+ /**
7
+ * Inline card that replaces a beat once it has been hidden, prompting the user
8
+ * to undo or leave feedback. Presentational — the host owns visibility and
9
+ * wires the callbacks. Copy comes from `insights_feed.hidden_confirmation`.
10
+ *
11
+ * The close IconButton is absolutely positioned in the top-right corner (top: 4
12
+ * + a 40px button centers its icon on the title line), and the action row is
13
+ * indented under the text column.
14
+ *
15
+ * Figma: 287:20383 — "Template | Hidden Card".
16
+ */
17
+ export declare const HiddenConfirmationCard: (({ onClose, onUndo, onLeaveFeedback }: HiddenConfirmationCardProps) => import("react/jsx-runtime").JSX.Element | null) & {
18
+ displayName: string;
19
+ };
20
+ export default HiddenConfirmationCard;
@@ -0,0 +1,123 @@
1
+ import { useGlobalCopyStore as c } from "../../common/context/hooks.es.js";
2
+ import { observer as x } from "mobx-react-lite";
3
+ import t from "@mui/material/Stack";
4
+ import { P as o, Text as n } from "@mxenabled/mxui";
5
+ import l from "@mui/material/Button";
6
+ import { Close as p, VisibilityOff as m } from "@mxenabled/mx-icons";
7
+ import { jsx as i, jsxs as r } from "react/jsx-runtime";
8
+ import h from "@mui/material/Box";
9
+ import f from "@mui/material/IconButton";
10
+ import b from "@mui/material/Card";
11
+ var _ = x(({ onClose: a, onUndo: s, onLeaveFeedback: d }) => {
12
+ const e = c().insights_feed?.hidden_confirmation;
13
+ return e ? /* @__PURE__ */ r(b, {
14
+ elevation: 1,
15
+ sx: {
16
+ position: "relative",
17
+ bgcolor: "action.disabledBackground",
18
+ borderRadius: 2,
19
+ pt: 16,
20
+ px: 16,
21
+ pb: 8
22
+ },
23
+ children: [/* @__PURE__ */ i(f, {
24
+ "aria-label": e.close_aria,
25
+ onClick: a,
26
+ sx: {
27
+ minHeight: 40,
28
+ minWidth: 40,
29
+ position: "absolute",
30
+ right: 4,
31
+ top: 4
32
+ },
33
+ children: /* @__PURE__ */ i(p, {
34
+ size: 20,
35
+ sx: { color: "text.secondary" }
36
+ })
37
+ }), /* @__PURE__ */ r(t, {
38
+ sx: { gap: 6 },
39
+ children: [/* @__PURE__ */ r(t, {
40
+ sx: {
41
+ flexDirection: "row",
42
+ gap: 12
43
+ },
44
+ children: [/* @__PURE__ */ i(h, {
45
+ sx: {
46
+ flexShrink: 0,
47
+ width: 32,
48
+ height: 32,
49
+ borderRadius: 1,
50
+ bgcolor: "grey.300",
51
+ display: "flex",
52
+ alignItems: "center",
53
+ justifyContent: "center"
54
+ },
55
+ children: /* @__PURE__ */ i(m, {
56
+ size: 20,
57
+ sx: { color: "text.secondary" }
58
+ })
59
+ }), /* @__PURE__ */ r(t, {
60
+ sx: { gap: 6 },
61
+ children: [/* @__PURE__ */ i(o, {
62
+ "aria-live": "assertive",
63
+ bold: !0,
64
+ sx: { lineHeight: "16px" },
65
+ variant: "body1",
66
+ children: e.title
67
+ }), /* @__PURE__ */ i(o, {
68
+ sx: {
69
+ color: "text.primary",
70
+ fontSize: 13,
71
+ lineHeight: "20px"
72
+ },
73
+ variant: "subtitle2",
74
+ children: e.description
75
+ })]
76
+ })]
77
+ }), /* @__PURE__ */ r(t, {
78
+ sx: {
79
+ alignItems: "center",
80
+ flexDirection: "row",
81
+ gap: 4,
82
+ height: 28,
83
+ pb: 6,
84
+ pl: 36
85
+ },
86
+ children: [/* @__PURE__ */ i(l, {
87
+ onClick: s,
88
+ size: "small",
89
+ sx: {
90
+ minWidth: 0,
91
+ px: 8
92
+ },
93
+ variant: "text",
94
+ children: /* @__PURE__ */ i(n, {
95
+ bold: !0,
96
+ variant: "body2",
97
+ children: e.undo
98
+ })
99
+ }), /* @__PURE__ */ i(l, {
100
+ onClick: d,
101
+ size: "small",
102
+ sx: {
103
+ minWidth: 0,
104
+ px: 8
105
+ },
106
+ variant: "text",
107
+ children: /* @__PURE__ */ i(n, {
108
+ bold: !0,
109
+ sx: { color: "text.secondary" },
110
+ variant: "body2",
111
+ children: e.leave_feedback
112
+ })
113
+ })]
114
+ })]
115
+ })]
116
+ }) : null;
117
+ });
118
+ export {
119
+ _ as HiddenConfirmationCard,
120
+ _ as default
121
+ };
122
+
123
+ //# sourceMappingURL=HiddenConfirmationCard.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HiddenConfirmationCard.es.js","names":[],"sources":["../../../src/insights-v2/components/HiddenConfirmationCard.tsx"],"sourcesContent":["import Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport IconButton from '@mui/material/IconButton'\nimport Stack from '@mui/material/Stack'\n\nimport { observer } from 'mobx-react-lite'\n\nimport { Close, VisibilityOff } from '@mxenabled/mx-icons'\nimport { P, Text } from '@mxenabled/mxui'\n\nimport { useGlobalCopyStore } from '../../common'\n\nexport interface HiddenConfirmationCardProps {\n onClose: () => void\n onUndo: () => void\n onLeaveFeedback: () => void\n}\n\n/**\n * Inline card that replaces a beat once it has been hidden, prompting the user\n * to undo or leave feedback. Presentational — the host owns visibility and\n * wires the callbacks. Copy comes from `insights_feed.hidden_confirmation`.\n *\n * The close IconButton is absolutely positioned in the top-right corner (top: 4\n * + a 40px button centers its icon on the title line), and the action row is\n * indented under the text column.\n *\n * Figma: 287:20383 — \"Template | Hidden Card\".\n */\nexport const HiddenConfirmationCard = observer(\n ({ onClose, onUndo, onLeaveFeedback }: HiddenConfirmationCardProps) => {\n const copy = useGlobalCopyStore().insights_feed?.hidden_confirmation\n\n if (!copy) return null\n\n return (\n <Card\n elevation={1}\n sx={{\n position: 'relative',\n bgcolor: 'action.disabledBackground',\n borderRadius: 2,\n pt: 16,\n px: 16,\n pb: 8,\n }}\n >\n <IconButton\n aria-label={copy.close_aria}\n onClick={onClose}\n sx={{ minHeight: 40, minWidth: 40, position: 'absolute', right: 4, top: 4 }}\n >\n <Close size={20} sx={{ color: 'text.secondary' }} />\n </IconButton>\n\n <Stack sx={{ gap: 6 }}>\n {/* Icon + title/description */}\n <Stack sx={{ flexDirection: 'row', gap: 12 }}>\n <Box\n sx={{\n flexShrink: 0,\n width: 32,\n height: 32,\n borderRadius: 1,\n bgcolor: 'grey.300',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <VisibilityOff size={20} sx={{ color: 'text.secondary' }} />\n </Box>\n\n <Stack sx={{ gap: 6 }}>\n <P aria-live=\"assertive\" bold sx={{ lineHeight: '16px' }} variant=\"body1\">\n {copy.title}\n </P>\n\n <P\n sx={{ color: 'text.primary', fontSize: 13, lineHeight: '20px' }}\n variant=\"subtitle2\"\n >\n {copy.description}\n </P>\n </Stack>\n </Stack>\n\n {/* Actions — indented under the text column via pl: 36 (avatar 32 + gap\n 12), so the first label sits flush under the title/description.\n Gap 4 between buttons per Figma. */}\n <Stack\n sx={{\n alignItems: 'center',\n flexDirection: 'row',\n gap: 4,\n height: 28,\n pb: 6,\n pl: 36,\n }}\n >\n <Button onClick={onUndo} size=\"small\" sx={{ minWidth: 0, px: 8 }} variant=\"text\">\n <Text bold variant=\"body2\">\n {copy.undo}\n </Text>\n </Button>\n <Button\n onClick={onLeaveFeedback}\n size=\"small\"\n sx={{ minWidth: 0, px: 8 }}\n variant=\"text\"\n >\n <Text bold sx={{ color: 'text.secondary' }} variant=\"body2\">\n {copy.leave_feedback}\n </Text>\n </Button>\n </Stack>\n </Stack>\n </Card>\n )\n },\n)\n\nexport default HiddenConfirmationCard\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { Beat } from '../../core';
2
+ import { BeatActions, InsightRating } from '../beatActions';
3
+ import { DismissType } from '../components/DismissInsightDialog';
4
+ /** Which flow opened the survey dialog (null = closed): a rating or the hide flow. */
5
+ export type FeedbackVariant = InsightRating | 'hide';
6
+ /**
7
+ * What the card slot shows:
8
+ * - `visible` the original beat card
9
+ * - `confirming` the hidden-confirmation card (grace period after a hide)
10
+ * - `dismissed` nothing (beat stays hidden; the host removes it on refresh)
11
+ */
12
+ export type CardState = 'visible' | 'confirming' | 'dismissed';
13
+ /**
14
+ * Owns the post-action UI state machine for a beat card: the feedback bar shown
15
+ * after a rating, the "hide this insight" dismiss dialog, the hidden-confirmation
16
+ * card, and the shared feedback survey dialog.
17
+ *
18
+ * Experiences owns this UI; the host only receives callbacks (rate / dismiss /
19
+ * undo / submit-feedback) via `beatActions`. Keeping the machine here leaves
20
+ * BaseLayout as pure layout.
21
+ */
22
+ export declare const useBeatFeedbackFlow: (beat: Beat, beatActions: BeatActions) => {
23
+ cardState: CardState;
24
+ isDismissOpen: boolean;
25
+ isSurveyOpen: boolean;
26
+ isFeedbackBarShown: boolean;
27
+ survey: {
28
+ characterLimitLabel: string;
29
+ closeAriaLabel: string;
30
+ submitText: string;
31
+ backText: string;
32
+ title: string;
33
+ options: string[];
34
+ };
35
+ wrappedActions: BeatActions;
36
+ openRatingSurvey: () => void;
37
+ openHideSurvey: () => void;
38
+ closeSurvey: () => void;
39
+ submitSurvey: (option: string, detail?: string) => void;
40
+ confirmDismiss: (type: DismissType) => void;
41
+ undoDismiss: () => void;
42
+ collapseConfirmation: () => void;
43
+ closeDismissDialog: () => void;
44
+ };
@@ -0,0 +1,73 @@
1
+ import { useGlobalCopyStore as S } from "../../common/context/hooks.es.js";
2
+ import { useMemo as y, useState as u } from "react";
3
+ var x = (r, o) => {
4
+ const a = S(), [c, n] = u(null), [i, t] = u(null), [f, d] = u(!1), [p, l] = u("visible"), h = (() => {
5
+ const e = a.insights_feed?.feedback, s = a.insights_feed?.hide_feedback, m = {
6
+ characterLimitLabel: (i === "hide" ? s : e)?.character_limit,
7
+ closeAriaLabel: (i === "hide" ? s : e)?.close_aria ?? "Close",
8
+ submitText: (i === "hide" ? s : e)?.submit ?? "Submit",
9
+ backText: (i === "hide" ? s : e)?.back ?? "Back"
10
+ };
11
+ if (i === "hide" && s) return {
12
+ title: s.title,
13
+ options: Object.values(s.options),
14
+ ...m
15
+ };
16
+ const v = Object.values(i === "not_helpful" ? e?.not_helpful_options ?? {} : e?.helpful_options ?? {});
17
+ return {
18
+ title: e?.tell_us_more_title ?? "Tell us more",
19
+ options: v,
20
+ ...m
21
+ };
22
+ })(), b = y(() => {
23
+ const e = o.onMenuAction;
24
+ return e ? {
25
+ ...o,
26
+ onMenuAction: {
27
+ ...e,
28
+ onAction: (s) => {
29
+ s === "helpful" || s === "not_helpful" ? n(s) : s === "hide" && d(!0), e.onAction(s);
30
+ }
31
+ }
32
+ } : o;
33
+ }, [o]);
34
+ return {
35
+ cardState: p,
36
+ isDismissOpen: f,
37
+ isSurveyOpen: i !== null,
38
+ isFeedbackBarShown: c !== null,
39
+ survey: h,
40
+ wrappedActions: b,
41
+ openRatingSurvey: () => {
42
+ c && t(c);
43
+ },
44
+ openHideSurvey: () => t("hide"),
45
+ closeSurvey: () => {
46
+ i !== "hide" && n(null), t(null);
47
+ },
48
+ submitSurvey: (e, s) => {
49
+ i && (o.onSubmitFeedback?.({
50
+ beatGuid: r.guid,
51
+ source: i,
52
+ option: e,
53
+ detail: s
54
+ }), t(null), i === "hide" ? l("dismissed") : n(null));
55
+ },
56
+ confirmDismiss: (e) => {
57
+ o.onDismissInsight?.({
58
+ beatGuid: r.guid,
59
+ type: e
60
+ }), l("confirming");
61
+ },
62
+ undoDismiss: () => {
63
+ o.onUndoDismiss?.({ beatGuid: r.guid }), n(null), l("visible");
64
+ },
65
+ collapseConfirmation: () => l("dismissed"),
66
+ closeDismissDialog: () => d(!1)
67
+ };
68
+ };
69
+ export {
70
+ x as useBeatFeedbackFlow
71
+ };
72
+
73
+ //# sourceMappingURL=useBeatFeedbackFlow.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBeatFeedbackFlow.es.js","names":[],"sources":["../../../src/insights-v2/hooks/useBeatFeedbackFlow.ts"],"sourcesContent":["import { useMemo, useState } from 'react'\n\nimport { useGlobalCopyStore } from '../../common'\nimport type { Beat } from '../../core'\nimport type { BeatActions, InsightRating } from '../beatActions'\nimport type { DismissType } from '../components/DismissInsightDialog'\n\n/** Which flow opened the survey dialog (null = closed): a rating or the hide flow. */\nexport type FeedbackVariant = InsightRating | 'hide'\n\n/**\n * What the card slot shows:\n * - `visible` the original beat card\n * - `confirming` the hidden-confirmation card (grace period after a hide)\n * - `dismissed` nothing (beat stays hidden; the host removes it on refresh)\n */\nexport type CardState = 'visible' | 'confirming' | 'dismissed'\n\n/**\n * Owns the post-action UI state machine for a beat card: the feedback bar shown\n * after a rating, the \"hide this insight\" dismiss dialog, the hidden-confirmation\n * card, and the shared feedback survey dialog.\n *\n * Experiences owns this UI; the host only receives callbacks (rate / dismiss /\n * undo / submit-feedback) via `beatActions`. Keeping the machine here leaves\n * BaseLayout as pure layout.\n */\nexport const useBeatFeedbackFlow = (beat: Beat, beatActions: BeatActions) => {\n const copyStore = useGlobalCopyStore()\n\n // Rating that surfaced the feedback bar (null = hidden). The bar confirms the\n // vote and offers a follow-up survey.\n const [feedbackRating, setFeedbackRating] = useState<InsightRating | null>(null)\n // Which flow opened the survey dialog (null = closed).\n const [feedbackVariant, setFeedbackVariant] = useState<FeedbackVariant | null>(null)\n // The \"hide this insight\" confirmation dialog (pick once / always).\n const [isDismissOpen, setIsDismissOpen] = useState(false)\n // Drives the card slot through the hide flow (see CardState).\n const [cardState, setCardState] = useState<CardState>('visible')\n\n // Survey title + reasons for the active variant. FeedbackDialog is generic, so\n // the variant-to-copy mapping lives here: hide uses the dismiss reasons; a\n // rating uses its matching helpful / not-helpful reasons.\n //\n // Not memoized on purpose: it reads observable copy fields, and the consumer\n // (BaseLayout) is an observer, so deriving on each render keeps it in sync when\n // copy reloads (a useMemo keyed on the stable store ref would go stale). The\n // work is a few lookups + Object.values on small arrays — cheap.\n const resolveSurvey = () => {\n const feedbackCopy = copyStore.insights_feed?.feedback\n const hideCopy = copyStore.insights_feed?.hide_feedback\n\n // Buttons + labels come from the same block as the reasons, so the hide and\n // rating surveys stay independently localizable (no cross-variant leakage).\n const chrome = {\n characterLimitLabel: (feedbackVariant === 'hide' ? hideCopy : feedbackCopy)?.character_limit,\n closeAriaLabel: (feedbackVariant === 'hide' ? hideCopy : feedbackCopy)?.close_aria ?? 'Close',\n submitText: (feedbackVariant === 'hide' ? hideCopy : feedbackCopy)?.submit ?? 'Submit',\n backText: (feedbackVariant === 'hide' ? hideCopy : feedbackCopy)?.back ?? 'Back',\n }\n\n if (feedbackVariant === 'hide' && hideCopy) {\n return { title: hideCopy.title, options: Object.values(hideCopy.options), ...chrome }\n }\n\n const options =\n feedbackVariant === 'not_helpful'\n ? Object.values(feedbackCopy?.not_helpful_options ?? {})\n : Object.values(feedbackCopy?.helpful_options ?? {})\n\n return { title: feedbackCopy?.tell_us_more_title ?? 'Tell us more', options, ...chrome }\n }\n\n const survey = resolveSurvey()\n\n // Wrap the host's menu handler so experiences owns the post-action UI (feedback\n // bar for a vote, dismiss dialog for hide) while the host still sees the action.\n const wrappedActions = useMemo<BeatActions>(() => {\n const menu = beatActions.onMenuAction\n if (!menu) return beatActions\n return {\n ...beatActions,\n onMenuAction: {\n ...menu,\n onAction: (action) => {\n if (action === 'helpful' || action === 'not_helpful') {\n setFeedbackRating(action)\n } else if (action === 'hide') {\n setIsDismissOpen(true)\n }\n menu.onAction(action)\n },\n },\n }\n }, [beatActions])\n\n // --- Feedback survey ------------------------------------------------------\n\n /** Open the survey from the feedback bar's \"Tell us more\" (rating flow only). */\n const openRatingSurvey = () => {\n if (feedbackRating) setFeedbackVariant(feedbackRating)\n }\n\n /** Open the hide survey from the confirmation card's \"Leave Feedback\". */\n const openHideSurvey = () => setFeedbackVariant('hide')\n\n /** Close the survey (X / Back). A rating survey also clears the feedback bar. */\n const closeSurvey = () => {\n // Intentional asymmetry (matches the host): closing a rating survey also\n // clears the \"Feedback sent\" bar, while the hide flow keeps its confirmation\n // card up (Undo stays reachable). The card guards a destructive hide; the\n // rating bar is just a non-destructive vote confirmation.\n if (feedbackVariant !== 'hide') setFeedbackRating(null)\n setFeedbackVariant(null)\n }\n\n const submitSurvey = (option: string, detail?: string) => {\n if (!feedbackVariant) return\n beatActions.onSubmitFeedback?.({ beatGuid: beat.guid, source: feedbackVariant, option, detail })\n setFeedbackVariant(null)\n // Rating surveys clear the confirmation bar; the hide survey collapses the\n // confirmation card. The beat stays hidden either way — the host removes it.\n if (feedbackVariant === 'hide') {\n setCardState('dismissed')\n } else {\n setFeedbackRating(null)\n }\n }\n\n // --- Hide flow ------------------------------------------------------------\n\n /** Confirm a hide from the dismiss dialog; swaps in the confirmation card. */\n const confirmDismiss = (type: DismissType) => {\n beatActions.onDismissInsight?.({ beatGuid: beat.guid, type })\n setCardState('confirming')\n }\n\n /** Undo a hide from the confirmation card; restores the original card. */\n const undoDismiss = () => {\n beatActions.onUndoDismiss?.({ beatGuid: beat.guid })\n setFeedbackRating(null) // Clear any stale rating-bar state from before the hide\n setCardState('visible')\n }\n\n /** Close the confirmation card without undoing; the beat stays hidden. */\n const collapseConfirmation = () => setCardState('dismissed')\n\n const closeDismissDialog = () => setIsDismissOpen(false)\n\n return {\n cardState,\n isDismissOpen,\n isSurveyOpen: feedbackVariant !== null,\n isFeedbackBarShown: feedbackRating !== null,\n survey,\n wrappedActions,\n // survey actions\n openRatingSurvey,\n openHideSurvey,\n closeSurvey,\n submitSurvey,\n // hide-flow actions\n confirmDismiss,\n undoDismiss,\n collapseConfirmation,\n closeDismissDialog,\n }\n}\n"],"mappings":";;AA2BA,IAAa,IAAA,CAAuB,GAAY,MAA6B;AAC3E,QAAM,IAAY,EAAmB,GAI/B,CAAC,GAAgB,CAAA,IAAqB,EAA+B,IAAI,GAEzE,CAAC,GAAiB,CAAA,IAAsB,EAAiC,IAAI,GAE7E,CAAC,GAAe,CAAA,IAAoB,EAAS,EAAK,GAElD,CAAC,GAAW,CAAA,IAAgB,EAAoB,SAAS,GAmCzD,KAzBA,MAAsB;AAC1B,UAAM,IAAe,EAAU,eAAe,UACxC,IAAW,EAAU,eAAe,eAIpC,IAAS;AAAA,MACb,sBAAsB,MAAoB,SAAS,IAAW,IAAe;AAAA,MAC7E,iBAAiB,MAAoB,SAAS,IAAW,IAAe,cAAc;AAAA,MACtF,aAAa,MAAoB,SAAS,IAAW,IAAe,UAAU;AAAA,MAC9E,WAAW,MAAoB,SAAS,IAAW,IAAe,QAAQ;AAAA,IAC5E;AAEA,QAAI,MAAoB,UAAU,EAChC,QAAO;AAAA,MAAE,OAAO,EAAS;AAAA,MAAO,SAAS,OAAO,OAAO,EAAS,OAAO;AAAA,MAAG,GAAG;AAAA,IAAO;AAGtF,UAAM,IAEA,OAAO,OADX,MAAoB,gBACF,GAAc,uBAAuB,CAAC,IACtC,GAAc,mBAAmB,CAAC,CADK;AAG3D,WAAO;AAAA,MAAE,OAAO,GAAc,sBAAsB;AAAA,MAAgB,SAAA;AAAA,MAAS,GAAG;AAAA,IAAO;AAAA,EACzF,GAE6B,GAIvB,IAAiB,EAAA,MAA2B;AAChD,UAAM,IAAO,EAAY;AACzB,WAAK,IACE;AAAA,MACL,GAAG;AAAA,MACH,cAAc;AAAA,QACZ,GAAG;AAAA,QACH,UAAA,CAAW,MAAW;AACpB,UAAI,MAAW,aAAa,MAAW,gBACrC,EAAkB,CAAM,IACf,MAAW,UACpB,EAAiB,EAAI,GAEvB,EAAK,SAAS,CAAM;AAAA,QACtB;AAAA,MACF;AAAA,IACF,IAdkB;AAAA,EAepB,GAAG,CAAC,CAAW,CAAC;AAuDhB,SAAO;AAAA,IACL,WAAA;AAAA,IACA,eAAA;AAAA,IACA,cAAc,MAAoB;AAAA,IAClC,oBAAoB,MAAmB;AAAA,IACvC,QAAA;AAAA,IACA,gBAAA;AAAA,IAEA,kBA1DI,MAAyB;AAC7B,MAAI,KAAgB,EAAmB,CAAc;AAAA,IACvD;AAAA,IAyDE,gBAtDI,MAAuB,EAAmB,MAAM;AAAA,IAuDpD,aApDI,MAAoB;AAKxB,MAAI,MAAoB,UAAQ,EAAkB,IAAI,GACtD,EAAmB,IAAI;AAAA,IACzB;AAAA,IA8CE,cA5CI,CAAgB,GAAgB,MAAoB;AACxD,MAAK,MACL,EAAY,mBAAmB;AAAA,QAAE,UAAU,EAAK;AAAA,QAAM,QAAQ;AAAA,QAAiB,QAAA;AAAA,QAAQ,QAAA;AAAA,MAAO,CAAC,GAC/F,EAAmB,IAAI,GAGnB,MAAoB,SACtB,EAAa,WAAW,IAExB,EAAkB,IAAI;AAAA,IAE1B;AAAA,IAmCE,gBA9BI,CAAkB,MAAsB;AAC5C,MAAA,EAAY,mBAAmB;AAAA,QAAE,UAAU,EAAK;AAAA,QAAM,MAAA;AAAA,MAAK,CAAC,GAC5D,EAAa,YAAY;AAAA,IAC3B;AAAA,IA4BE,aAzBI,MAAoB;AACxB,MAAA,EAAY,gBAAgB,EAAE,UAAU,EAAK,KAAK,CAAC,GACnD,EAAkB,IAAI,GACtB,EAAa,SAAS;AAAA,IACxB;AAAA,IAsBE,sBAnBI,MAA6B,EAAa,WAAW;AAAA,IAoBzD,oBAlBI,MAA2B,EAAiB,EAAK;AAAA,EAmBvD;AACF"}
@@ -9,7 +9,11 @@ export { default as InsightTransactionRow, type InsightTransactionRowProps, } fr
9
9
  export { default as LogoClusterCard } from './components/LogoClusterCard';
10
10
  export { default as OverflowMenu, type OverflowMenuItem, type OverflowMenuProps, } from './components/OverflowMenu';
11
11
  export { default as InsightOptionsMenu } from './components/InsightOptionsMenu';
12
- export { useBeatActions, type BeatActions, type MenuActionHandlers, type UpdateParams, type InsightMenuAction, } from './beatActions';
12
+ export { default as DismissInsightDialog, type DismissInsightDialogProps, type DismissType, } from './components/DismissInsightDialog';
13
+ export { default as HiddenConfirmationCard, type HiddenConfirmationCardProps, } from './components/HiddenConfirmationCard';
14
+ export { default as FeedbackDialog, type FeedbackDialogProps } from './components/FeedbackDialog';
15
+ export { default as FeedbackBar, type FeedbackBarProps } from './components/FeedbackBar';
16
+ export { useBeatActions, type BeatActions, type MenuActionHandlers, type UpdateParams, type FeedbackParams, type DismissParams, type InsightMenuAction, type InsightRating, } from './beatActions';
13
17
  export { default as LogoTile } from './components/LogoTile';
14
18
  export { default as MerchantHighlightCard } from './components/MerchantHighlightCard';
15
19
  export { default as P2PLogoStack } from './components/P2PLogoStack';