@verifiedinc-public/shared-ui-elements 9.15.0 → 10.0.0

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.
@@ -3,11 +3,32 @@ import { SxProps } from '@mui/material';
3
3
  import { AreaSeriesChartData } from '../AreaChart';
4
4
  import { BrandFilter } from '../../BrandFilterInput';
5
5
  export type { AreaSeriesChartData } from '../AreaChart';
6
+ type ViewMode = 'absolute' | 'percent';
6
7
  export interface BrandIntervalData {
7
8
  brandUuid: string;
8
9
  brandName: string;
9
10
  interval?: Array<Record<string, number | string>>;
10
11
  }
12
+ /**
13
+ * A single entry in the chart's toggle button group. Selecting it swaps the
14
+ * data, series, and display configuration the chart renders.
15
+ */
16
+ export interface ConversionOverTimeChartView {
17
+ key: string;
18
+ label: string;
19
+ /** Raw bucketed data; will be mapped via {@link mapBrandIntervalData}. */
20
+ chartData?: BrandIntervalData[];
21
+ /** Pre-mapped data points (alternative to `chartData`). */
22
+ data?: Array<Record<string, number | string>>;
23
+ /** Series definitions for pre-mapped `data`. */
24
+ series?: AreaSeriesChartData[];
25
+ /** Series config used to map `chartData`. */
26
+ seriesConfig?: AreaSeriesChartData[];
27
+ /** Display mode for this view. Defaults to 'absolute'. */
28
+ mode?: ViewMode;
29
+ /** Stacking mode for this view. Defaults to the top-level `stackMode`. */
30
+ stackMode?: 'stack' | 'none';
31
+ }
11
32
  export interface ConversionOverTimeChartLegendBrand {
12
33
  uuid: string;
13
34
  value: string;
@@ -37,5 +58,17 @@ export interface ConversionOverTimeChartProps {
37
58
  legendBrand?: ConversionOverTimeChartLegendBrand;
38
59
  /** Whether the legend displays the copyable UUID. Defaults to true. */
39
60
  showLegendUuid?: boolean;
61
+ /**
62
+ * Configurable toggle button group. Each view defines its own data, series,
63
+ * and display mode. Consumers control the order — including inserting custom
64
+ * views (e.g. 'Data Providers') between the built-in Numbers / Percentages.
65
+ *
66
+ * When omitted, the chart falls back to the built-in
67
+ * `[Numbers, Percentages]` toggles driven by the top-level
68
+ * `chartData`/`seriesConfig`.
69
+ */
70
+ views?: ConversionOverTimeChartView[];
71
+ /** Key of the initially selected view. Defaults to the first view. */
72
+ defaultViewKey?: string;
40
73
  }
41
- export declare function ConversionOverTimeChart({ data, series, chartData, seriesConfig, stackMode: stackModeProp, isLoading, isFetching, isSuccess, filter, sx, legendBrand, showLegendUuid, }: Readonly<ConversionOverTimeChartProps>): React.ReactNode;
74
+ export declare function ConversionOverTimeChart({ data, series, chartData, seriesConfig, stackMode: stackModeProp, isLoading, isFetching, isSuccess, filter, sx, legendBrand, showLegendUuid, views, defaultViewKey, }: Readonly<ConversionOverTimeChartProps>): React.ReactNode;
@@ -1 +1 @@
1
- "use strict";import{useState as T}from"react";import{Stack as A,Box as v,ToggleButtonGroup as E,ToggleButton as N}from"@mui/material";import{EmptyChartSection as U}from"../EmptyChartSection.mjs";import{LoadingChartSection as B}from"../LoadingChartSection.mjs";import{AreaChart as j}from"../AreaChart/index.mjs";import{SeriesPercentageChartLegend as Z}from"../SeriesPercentageChartLegend/index.mjs";import{useStyle as $}from"../styles.mjs";import{formatExtendedDate as z,formatDateMMYY as O}from"../../../utils/date.mjs";import{DEFAULT_TIMEZONE as W}from"../../form/TimezoneInput/timezones.mjs";import{jsx as n,jsxs as b}from"react/jsx-runtime";function _({brands:g,data:y,seriesConfig:d}){const c=new Map,u=g?new Set(g.map(a=>a._raw.brandUuid)):null;for(const a of y)if(!(u&&!u.has(a.brandUuid)))for(const l of a.interval??[]){const f=+new Date(l.date);let t=c.get(f);if(!t){t={date:f};for(const i of d)t[i.dataKey]=0;c.set(f,t)}for(const i of d)t[i.dataKey]=t[i.dataKey]+Number(l[i.dataKey]||0)}return{series:d,data:Array.from(c.values()).sort((a,l)=>a.date-l.date)}}function I({data:g,series:y,chartData:d,seriesConfig:c,stackMode:u="stack",isLoading:a,isFetching:l,isSuccess:f,filter:t,sx:i,legendBrand:r,showLegendUuid:K=!0}){const x=$(),C=t.timezone??W,[p,k]=T("absolute"),o=d!==void 0?a?{series:[],data:[]}:_({brands:t.brands,data:d,seriesConfig:c??[]}):{series:y??[],data:g??[]};if(!o.data.length&&a)return n(B,{});if(!o.data.length||!f)return n(U,{});const S=u==="none"&&p==="percent"?o.data.map(e=>{const m=Math.max(0,...o.series.map(s=>Number(e[s.dataKey])||0)),h={date:e.date};for(const s of o.series)h[s.dataKey]=m===0?0:(Number(e[s.dataKey])||0)/m;return h}):null,F=u==="stack"?p==="absolute"?"stack":"expand":"none",D=p==="absolute"?{tickFormatter:e=>Number(e).toLocaleString(),allowDecimals:!1}:{tickFormatter:e=>`${(e*100).toFixed(0)}%`,domain:[0,1]},M=p==="absolute"?e=>Number(e).toLocaleString():u==="stack"?(e,m,h)=>{const s=o.series.reduce((w,L)=>w+(Number(h.payload[L.dataKey])||0),0);return s===0?"0.0%":`${(Number(e)/s*100).toFixed(1)}%`}:e=>`${(Number(e)*100).toFixed(1)}%`;return b(A,{children:[b(v,{sx:{display:"flex",flexDirection:"column",gap:1,height:x.regularChartWrapper.height},children:[n(v,{sx:{display:"flex",justifyContent:"flex-end"},children:b(E,{value:p,exclusive:!0,onChange:(e,m)=>{m!==null&&k(m)},size:"small",children:[n(N,{value:"absolute",children:"Numbers"}),n(N,{value:"percent",children:"Percentages"})]})}),n(j,{data:S??o.data,series:o.series,stackMode:F,isAnimationActive:!0,xAxis:{dataKey:"date",type:"number",domain:["dataMin","dataMax"],tickFormatter:e=>O(e,{timeZone:C,hour12:!1,hour:"numeric"}),allowDuplicatedCategory:!1},yAxis:D,tooltip:{formatter:M,labelFormatter:e=>z(e,{timeZone:C,hour12:!1})},sx:{...x.regularChartWrapper,height:"unset",flex:1,minHeight:0,opacity:l?.4:1,...i}})]}),r&&n(Z,{payload:[{uuid:r.uuid,value:r.value,color:r.color,dataKey:r.dataKey??r.uuid,brandName:r.brandName,integrationType:r.integrationType}],showUuid:K})]})}export{I as ConversionOverTimeChart};
1
+ "use strict";import{useState as I}from"react";import{Stack as V,Box as M,ToggleButtonGroup as Y,ToggleButton as q}from"@mui/material";import{EmptyChartSection as A}from"../EmptyChartSection.mjs";import{LoadingChartSection as T}from"../LoadingChartSection.mjs";import{AreaChart as G}from"../AreaChart/index.mjs";import{SeriesPercentageChartLegend as J}from"../SeriesPercentageChartLegend/index.mjs";import{useStyle as Q}from"../styles.mjs";import{formatExtendedDate as R,formatDateMMYY as X}from"../../../utils/date.mjs";import{DEFAULT_TIMEZONE as ee}from"../../form/TimezoneInput/timezones.mjs";import{jsx as o,jsxs as L}from"react/jsx-runtime";function ae({brands:p,data:x,seriesConfig:m}){const c=new Map,g=p?new Set(p.map(t=>t._raw.brandUuid)):null;for(const t of x)if(!(g&&!g.has(t.brandUuid)))for(const l of t.interval??[]){const f=+new Date(l.date);let r=c.get(f);if(!r){r={date:f};for(const s of m)r[s.dataKey]=0;c.set(f,r)}for(const s of m)r[s.dataKey]=r[s.dataKey]+Number(l[s.dataKey]||0)}return{series:m,data:Array.from(c.values()).sort((t,l)=>t.date-l.date)}}function te({data:p,series:x,chartData:m,seriesConfig:c,stackMode:g="stack",isLoading:t,isFetching:l,isSuccess:f,filter:r,sx:s,legendBrand:i,showLegendUuid:E=!0,views:k,defaultViewKey:B}){var C;const K=Q(),N=r.timezone??ee,h=k??[{key:"absolute",label:"Numbers",mode:"absolute"},{key:"percent",label:"Percentages",mode:"percent"}],[w,U]=I(B??((C=h[0])==null?void 0:C.key)??"absolute"),a=h.find(e=>e.key===w)??h[0],y=a?.mode??"absolute",v=a?.stackMode??g,S=a?.chartData??m,j=a?.seriesConfig??c,Z=a?.data??p,$=a?.series??x,n=S!==void 0?t?{series:[],data:[]}:ae({brands:r.brands,data:S,seriesConfig:j??[]}):{series:$??[],data:Z??[]},D=!n.data.length||!f,F=!n.data.length&&t;if(!k){if(F)return o(T,{});if(D)return o(A,{})}const z=v==="none"&&y==="percent"?n.data.map(e=>{const u=Math.max(0,...n.series.map(d=>Number(e[d.dataKey])||0)),b={date:e.date};for(const d of n.series)b[d.dataKey]=u===0?0:(Number(e[d.dataKey])||0)/u;return b}):null,O=v==="stack"?y==="absolute"?"stack":"expand":"none",P=y==="absolute"?{tickFormatter:e=>Number(e).toLocaleString(),allowDecimals:!1}:{tickFormatter:e=>`${(e*100).toFixed(0)}%`,domain:[0,1]},W=y==="absolute"?e=>Number(e).toLocaleString():v==="stack"?(e,u,b)=>{const d=n.series.reduce((_,H)=>_+(Number(b.payload[H.dataKey])||0),0);return d===0?"0.0%":`${(Number(e)/d*100).toFixed(1)}%`}:e=>`${(Number(e)*100).toFixed(1)}%`;return L(V,{children:[L(M,{sx:{display:"flex",flexDirection:"column",gap:1,height:K.regularChartWrapper.height},children:[o(M,{sx:{display:"flex",justifyContent:"flex-end"},children:o(Y,{value:w,exclusive:!0,onChange:(e,u)=>{u!==null&&U(u)},size:"small",children:h.map(e=>o(q,{value:e.key,children:e.label},e.key))})}),F?o(T,{}):D?o(A,{}):o(G,{data:z??n.data,series:n.series,stackMode:O,isAnimationActive:!0,xAxis:{dataKey:"date",type:"number",domain:["dataMin","dataMax"],tickFormatter:e=>X(e,{timeZone:N,hour12:!1,hour:"numeric"}),allowDuplicatedCategory:!1},yAxis:P,tooltip:{formatter:W,labelFormatter:e=>R(e,{timeZone:N,hour12:!1})},sx:{...K.regularChartWrapper,height:"unset",flex:1,minHeight:0,opacity:l?.4:1,...s}})]}),i&&o(J,{payload:[{uuid:i.uuid,value:i.value,color:i.color,dataKey:i.dataKey??i.uuid,brandName:i.brandName,integrationType:i.integrationType}],showUuid:E})]})}export{te as ConversionOverTimeChart};
@@ -1 +1 @@
1
- "use strict";import{useState as k,useCallback as i,useContext as A,createContext as K}from"react";import{jsx as T}from"react/jsx-runtime";const x=K(null),W=()=>{const m=A(x);if(!m)throw new Error("useForm must be used within a FormProvider");return m},I=({children:m,form:y,onSubmit:h})=>{const[n,c]=k({form:y,isSubmitting:!1,isSubmitSuccess:!1}),s=i(e=>{var o;if(!n.form)return;const r=e.split(".");if(r.length===1){const u=r[0],S=n.form.fields[u];if(S)return S;for(const g of Object.values(n.form.fields))if(g.children){for(const j of Object.values(g.children))if(j.id===u)return j}return}const[t,...l]=r,a=n.form.fields[t];if(!(a!=null&&a.children))return;let f=a;for(const u of l){if(!((o=f?.children)!=null&&o[u]))return;f=f.children[u]}return f},[n.form]),b=i(()=>n.form?n.form.isValid:!1,[n.form]),O=i(e=>{c(o=>({...o,form:e}))},[]),F=i(e=>{if(!n.form)return null;const o=e.split(".");if(o.length<2)return null;const r=o.slice(0,-1).join("."),t=o[o.length-1],l=s(r);if(!l||l.schema.characteristics.inputType!=="composite")throw new Error(`Parent field not found: ${r}`);return{parentField:l,childKey:t}},[n.form,s]),w=i(e=>{if(!e.children)return{};const o={};return Object.entries(e.children).forEach(([r,t])=>{o[r]=t.value}),o},[]),v=i(e=>{const o=F(e);if(o){const{parentField:r}=o,t=w(r);r.value=Object.keys(t).length>0?t:{};const l=e.split(".");if(l.length>2){const a=l.slice(0,-1).join(".");v(a)}}},[F,w]),E=i((e,o)=>{c(r=>{if(!r.form)return console.warn("No form instance available"),r;const t=s(e);return t?t.value===o?r:(t.value=o,v(e),{...r,form:r.form}):(console.warn(`Attempted to update non-existent field: ${e}`),r)})},[s,v]),P=i((e,o)=>{c(r=>{if(!r.form)return console.warn("No form instance available"),r;const t=s(e);return t?t.touched===o?r:(t.touched=o,{...r,form:r.form}):(console.warn(`Attempted to set touched state for non-existent field: ${e}`),r)})},[s]),V=i(()=>{c(e=>{if(!e.form)return e;const o=r=>{r.value=r.defaultValue,r.touched=!1,r.children&&Object.values(r.children).forEach(o)};return Object.values(e.form.fields).forEach(o),{...e,isSubmitting:!1,isSubmitSuccess:!1}})},[]),p=i(e=>{c(o=>({...o,isSubmitting:e}))},[]),d=i(e=>{c(o=>({...o,isSubmitSuccess:e}))},[]),$=i(async()=>{if(!(!b()||!n.form)){p(!0),d(!1);try{h&&(await h(n.form),d(!0))}catch(e){console.error("Form submission failed:",e),d(!1)}finally{p(!1)}}},[n.form,h,b,p,d]),C=i((e,o)=>{c(r=>{if(!r.form)return console.warn("No form instance available"),r;const t=s(e);return t?(t.replaceWithVariant(o),{...r,form:r.form}):(console.warn(`Field ${e} not found`),r)})},[s]),N={state:n,setForm:O,updateFieldValue:E,setFieldTouched:P,getField:s,validateForm:b,resetForm:V,submitForm:$,replaceFieldWithVariant:C};return T(x.Provider,{value:N,children:m})};export{I as FormProvider,W as useForm};
1
+ "use strict";import{useState as A,useCallback as i,useContext as T,createContext as K}from"react";import{jsx as N}from"react/jsx-runtime";const x=K(null),W=()=>{const m=T(x);if(!m)throw new Error("useForm must be used within a FormProvider");return m},L=({children:m,form:O,onSubmit:h})=>{const[n,c]=A({form:O,isSubmitting:!1,isSubmitSuccess:!1}),s=i(e=>{var t;if(!n.form)return;const r=e.split(".");if(r.length===1){const a=r[0],w=n.form.fields[a];if(w)return w;for(const g of Object.values(n.form.fields))if(g.children){for(const j of Object.values(g.children))if(j.id===a)return j}return}const[o,...l]=r,f=n.form.fields[o];if(!(f!=null&&f.children))return;let u=f;for(const a of l){if(!((t=u?.children)!=null&&t[a]))return;u=u.children[a]}return u},[n.form]),b=i(()=>n.form?n.form.isValid:!1,[n.form]),y=i(e=>{c(t=>({...t,form:e}))},[]),F=i(e=>{if(!n.form)return null;const t=e.split(".");if(t.length<2)return null;const r=t.slice(0,-1).join("."),o=t[t.length-1],l=s(r);if(!l||l.schema.characteristics.inputType!=="composite")throw new Error(`Parent field not found: ${r}`);return{parentField:l,childKey:o}},[n.form,s]),S=i(e=>{if(!e.children)return{};const t={};return Object.entries(e.children).forEach(([r,o])=>{t[r]=o.value}),t},[]),p=i(e=>{const t=F(e);if(t){const{parentField:r}=t,o=S(r);r.value=Object.keys(o).length>0?o:{};const l=e.split(".");if(l.length>2){const f=l.slice(0,-1).join(".");p(f)}}},[F,S]),E=i((e,t)=>{const r=s(e);if(!r){console.warn(`Attempted to update non-existent field: ${e}`);return}r.value!==t&&(r.value=t,p(e),c(o=>o.form?{...o,form:o.form}:o))},[s,p]),V=i((e,t)=>{const r=s(e);if(!r){console.warn(`Attempted to set touched state for non-existent field: ${e}`);return}r.touched!==t&&(r.touched=t,c(o=>o.form?{...o,form:o.form}:o))},[s]),P=i(()=>{c(e=>{if(!e.form)return e;const t=r=>{r.value=r.defaultValue,r.touched=!1,r.children&&Object.values(r.children).forEach(t)};return Object.values(e.form.fields).forEach(t),{...e,isSubmitting:!1,isSubmitSuccess:!1}})},[]),v=i(e=>{c(t=>({...t,isSubmitting:e}))},[]),d=i(e=>{c(t=>({...t,isSubmitSuccess:e}))},[]),$=i(async()=>{if(!(!b()||!n.form)){v(!0),d(!1);try{h&&(await h(n.form),d(!0))}catch(e){console.error("Form submission failed:",e),d(!1)}finally{v(!1)}}},[n.form,h,b,v,d]),C=i((e,t)=>{c(r=>{if(!r.form)return console.warn("No form instance available"),r;const o=s(e);return o?(o.replaceWithVariant(t),{...r,form:r.form}):(console.warn(`Field ${e} not found`),r)})},[s]),k={state:n,setForm:y,updateFieldValue:E,setFieldTouched:V,getField:s,validateForm:b,resetForm:P,submitForm:$,replaceFieldWithVariant:C};return N(x.Provider,{value:k,children:m})};export{L as FormProvider,W as useForm};
@@ -0,0 +1 @@
1
+ export * from './formatters';
@@ -0,0 +1 @@
1
+ "use strict";import{formatCurrency as r,formatNumberRounded as a,formatPercentage as e}from"./formatters.mjs";export{r as formatCurrency,a as formatNumberRounded,e as formatPercentage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verifiedinc-public/shared-ui-elements",
3
- "version": "9.15.0",
3
+ "version": "10.0.0",
4
4
  "description": "A set of UI components, utilities that is shareable with the core apps.",
5
5
  "private": false,
6
6
  "sideEffects": false,