@oatfi/ui 0.5.0 → 0.7.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.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Theme as Theme$1 } from '@mui/material/styles';
2
2
  import * as react from 'react';
3
- import react__default from 'react';
3
+ import react__default, { FC } from 'react';
4
4
  import * as styled_components_dist_types from 'styled-components/dist/types';
5
5
  import * as styled_components from 'styled-components';
6
6
 
@@ -678,14 +678,17 @@ declare function UnderwritingStatus({ status, creditLimit, children, testId, }:
678
678
  * a trailing action.
679
679
  *
680
680
  * `$default` marks the selected method (accent border and tint); `$readonly` marks
681
- * one the borrower cannot edit here, such as an account managed through Plaid.
681
+ * one the borrower cannot edit here, such as an account managed through Plaid;
682
+ * `$edited` marks one carrying changes that have not been saved yet.
682
683
  */
683
- declare const MethodCard: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$default" | "$readonly"> & {
684
+ declare const MethodCard: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$default" | "$readonly" | "$edited"> & {
684
685
  $default?: boolean;
685
686
  $readonly?: boolean;
686
- }, never> & Partial<Pick<styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$default" | "$readonly"> & {
687
+ $edited?: boolean;
688
+ }, never> & Partial<Pick<styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$default" | "$readonly" | "$edited"> & {
687
689
  $default?: boolean;
688
690
  $readonly?: boolean;
691
+ $edited?: boolean;
689
692
  }, never>>> & string;
690
693
  declare const MethodBody: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
691
694
  /** `$tight` for a card with no form fields under the header. */
@@ -699,6 +702,15 @@ declare const MethodTitle: styled_components_dist_types.IStyledComponentBase<"we
699
702
  declare const FieldsGrid: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
700
703
  /** Accent-tinted note with a leading icon slot. */
701
704
  declare const Banner: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
705
+ /**
706
+ * Footer row for a step's actions.
707
+ *
708
+ * `space-between` is for the two-part footer a stepped flow has — Back on the
709
+ * left, status and the primary action on the right. A servicing screen that saves
710
+ * in place has no Back, and space-between then drops its lone action on the left,
711
+ * where it reads as the secondary half of a pair that isn't there. So a single
712
+ * child is pushed to the right instead; two or more keep the split.
713
+ */
702
714
  declare const FooterActions: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
703
715
  declare const FooterRight: styled_components_dist_types.IStyledComponentBase<"web", styled_components.FastOmit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
704
716
 
@@ -733,11 +745,30 @@ interface ManualMethodValue {
733
745
  account: string;
734
746
  routing: string;
735
747
  }
748
+ /**
749
+ * Where the row stands against what is stored.
750
+ *
751
+ * `saved` — on file and unchanged. `edited` — on file, with changes not yet
752
+ * written. A row the borrower has just added is neither: omit the prop.
753
+ *
754
+ * Only `edited` is marked. `saved` deliberately renders the same as a new row for
755
+ * now — the card stays quiet until the borrower actually changes something — but
756
+ * it remains a distinct value because the caller already tells the two apart, and
757
+ * marking a stored row is a change of copy here rather than a change of API.
758
+ *
759
+ * The caller owns the comparison, because only it knows what was loaded.
760
+ */
761
+ type ManualMethodStatus = 'saved' | 'edited';
736
762
  interface ManualMethodCardProps {
737
763
  /** Zero-based; rendered as "Account {index + 1}". */
738
764
  index: number;
739
765
  value: ManualMethodValue;
740
766
  isDefault?: boolean;
767
+ /**
768
+ * Whether this account is already stored, and whether it has been changed
769
+ * since. Omit for a row the borrower has just added. Only `edited` is marked.
770
+ */
771
+ status?: ManualMethodStatus;
741
772
  /** Disables removal when this is the last remaining method. */
742
773
  trashDisabled?: boolean;
743
774
  disabled?: boolean;
@@ -755,8 +786,12 @@ interface ManualMethodCardProps {
755
786
  * Validation is the caller's: pass `accountError`/`routingError` and use
756
787
  * `onBlurField` to decide when to surface them. Onboarding shows errors on blur
757
788
  * and never while typing, which is worth keeping.
789
+ *
790
+ * A stored account is an ordinary editable field here — nothing about the inputs
791
+ * says the borrower is overwriting something real. `status` is what says it, and
792
+ * it is worth passing wherever saved accounts are rendered.
758
793
  */
759
- declare function ManualMethodCard({ index, value, isDefault, trashDisabled, disabled, accountError, routingError, onSelectDefault, onChangeField, onBlurField, onRemove, name, }: ManualMethodCardProps): react__default.JSX.Element;
794
+ declare function ManualMethodCard({ index, value, isDefault, status, trashDisabled, disabled, accountError, routingError, onSelectDefault, onChangeField, onBlurField, onRemove, name, }: ManualMethodCardProps): react__default.JSX.Element;
760
795
 
761
796
  interface AchVerificationConsentProps {
762
797
  consentGiven?: boolean;
@@ -776,6 +811,28 @@ interface AchVerificationConsentProps {
776
811
  */
777
812
  declare function AchVerificationConsent({ consentGiven, onChange, disabled, actionLabel, testId, }: AchVerificationConsentProps): react__default.JSX.Element;
778
813
 
814
+ interface EnvironmentTagProps {
815
+ /** The Vercel project the bundle was built for (`OATFI_ENV`), `local` under a dev server. */
816
+ bundleEnv?: string;
817
+ /** The host's `environment` attribute. */
818
+ apiEnv?: string;
819
+ /** Git ref the bundle was built from, for the tooltip. */
820
+ buildRef?: string;
821
+ /** Short commit SHA, for the tooltip. */
822
+ buildSha?: string;
823
+ size?: 'small' | 'medium';
824
+ /** `data-testid` on the chip. Override when several embeds share a page. */
825
+ testId?: string;
826
+ }
827
+ /**
828
+ * The environment chip for embedded components. Shows which API the component
829
+ * calls and, when it differs, which Vercel project the bundle came from:
830
+ * `DEV` on a match, `LOCAL | API DEV` for a local dev server, `BUNDLE DEV | API STG`
831
+ * (red-bordered) on a mismatch, a red `API ?` for an unrecognised API env, and
832
+ * nothing in production/production. See `resolveComponentEnvTag` for the rules.
833
+ */
834
+ declare const EnvironmentTag: FC<EnvironmentTagProps>;
835
+
779
836
  interface PreviewProps {
780
837
  /** Partner branding to render the sample with. Omit for the default theme. */
781
838
  branding?: Branding;
@@ -789,5 +846,78 @@ interface PreviewProps {
789
846
  */
790
847
  declare function Preview({ branding, className }: PreviewProps): react__default.JSX.Element;
791
848
 
792
- export { AchVerificationConsent, AsteriskMark, AsteriskNote, AutocompleteInput, Badge, Banner, BaseInput, Body100, Body200, Body400, Button, ButtonContainer, CheckboxInput, CollapsibleSource, Container, Content, CurrencyInput, DateInput, DetailElement, DetailRow, DetailsContainer, DotsMenu, DropDown, DropdownContainer, DropdownItem, DropdownList, EmailInput, FieldsGrid, FileInput, FlexColumnWrapper, FlexRowWrapper, FooterActions, FooterRight, Footnote, Form, FormActions, FormCheckboxLabel, FormColumn, FormRow, FormSubtitle, FormTitle, H1, H2, H3, HelperText, InputError, InputLabel, InputWrapper, Loader, ManualMethodCard, MethodBody, MethodCard, MethodHeader, MethodTitle, NoResults, PhoneInput, PlaidMethodCard, PlaidTile, Preview, RadioInput, SSNInput, SearchDropdown, SelectInput, StatusTag, Stepper, Tabs, TaxIdInput, TermsDocument, TermsList, TextInput, ThemeProvider, UnderwritingStatus, UploadZone, buildMuiTheme, defaultTheme, mergeBranding, useTheme };
793
- export type { AchVerificationConsentProps, BadgeVariant, Branding, CollapsibleSourceProps, CurrencyInputProps, DateInputProps, DotsMenuOption, DotsMenuProps, DropDownProps, EmailInputProps, FileInputProps, FootnoteLink, FootnoteProps, LoaderProps, ManualMethodCardProps, ManualMethodValue, Option, PhoneInputProps, PlaidConnection, PlaidMethodCardProps, PlaidTileProps, PreviewProps, SSNInputProps, SearchDropdownProps, StepperProps, StepperStep, Tab, TabsProps, TaxIdInputProps, TermsDocumentProps, TextInputProps, Theme, ThemeBorder, ThemeColors, ThemeProviderProps, ThemeTypography, UnderwritingDecision, UnderwritingStatusProps, UploadZoneProps, UploadedFile };
849
+ /**
850
+ * Environment chip logic for embedded components. Framework-free so the
851
+ * React-18 bundle (`mf-remote-react`), which can't take this package, can copy
852
+ * it verbatim.
853
+ *
854
+ * Ported from `oatfi-app-shell/src/lib/environment.ts`. The difference: a
855
+ * dashboard has one environment, a component has two — the Vercel project the
856
+ * bundle was built for (`bundleEnv`) and the API the host points it at
857
+ * (`apiEnv`, the `environment` attribute). They render as one chip that splits
858
+ * when they disagree.
859
+ */
860
+ type EnvironmentName = 'production' | 'staging' | 'qa' | 'development' | 'local';
861
+ interface EnvironmentTagInfo {
862
+ /** Short chip text, e.g. "STG". */
863
+ readonly label: string;
864
+ /** Full name, for the tooltip. */
865
+ readonly name: string;
866
+ /** Chip fill. */
867
+ readonly fill: string;
868
+ /** Text color on the fill. */
869
+ readonly ink: string;
870
+ }
871
+ type ComponentEnvTag =
872
+ /** Bundle and API agree (or the bundle env is unset). */
873
+ {
874
+ kind: 'single';
875
+ tag: EnvironmentTagInfo;
876
+ }
877
+ /** A local dev-server bundle on a non-prod API: informational, not a warning. */
878
+ | {
879
+ kind: 'local';
880
+ api: EnvironmentTagInfo;
881
+ }
882
+ /** Bundle and API disagree, or the bundle env is unrecognised (`bundleEnv: null`): a warning. */
883
+ | {
884
+ kind: 'split';
885
+ bundleEnv: EnvironmentName | null;
886
+ bundle: EnvironmentTagInfo;
887
+ api: EnvironmentTagInfo;
888
+ }
889
+ /** The API env is set but unrecognised, so the component calls the wrong API (or none). */
890
+ | {
891
+ kind: 'invalid-api';
892
+ apiEnv: string;
893
+ };
894
+ interface ComponentEnv {
895
+ /** The Vercel project the bundle was built for (`OATFI_ENV`), `local` under a dev server. */
896
+ bundleEnv?: string | null;
897
+ /** The host's `environment` attribute — which API the component calls. */
898
+ apiEnv?: string | null;
899
+ }
900
+ /**
901
+ * What the chip shows. Unset envs stay quiet; set-but-unrecognised ones are flagged:
902
+ * - API env unset → nothing (it means production)
903
+ * - API env unrecognised → `invalid-api`, an error (the host is calling the wrong API)
904
+ * - bundle env unset → treated as a match on the API env
905
+ * - bundle env unrecognised → a warning split against the API env
906
+ * - local bundle → informational `LOCAL | API X` on a non-prod API, a single `LOCAL` on
907
+ * the local API, and a warning against prod
908
+ * - prod/prod → nothing
909
+ */
910
+ declare const resolveComponentEnvTag: ({ bundleEnv, apiEnv, }: ComponentEnv) => ComponentEnvTag | null;
911
+ interface ComponentBuildInfo extends ComponentEnv {
912
+ /** Git ref the bundle was built from, e.g. `dev12` or `v1.4.0`. */
913
+ ref?: string | null;
914
+ /** Short commit SHA. */
915
+ sha?: string | null;
916
+ }
917
+ /** One-line debug string for the console: `bundle=development api=staging ref=dev12 sha=abc1234`. */
918
+ declare const describeComponentEnv: ({ bundleEnv, apiEnv, ref, sha }: ComponentBuildInfo) => string;
919
+ /** Tooltip text: `Bundle: Development (dev12, abc1234) · API: Staging`. */
920
+ declare const describeComponentEnvTitle: ({ bundleEnv, apiEnv, ref, sha, }: ComponentBuildInfo) => string;
921
+
922
+ export { AchVerificationConsent, AsteriskMark, AsteriskNote, AutocompleteInput, Badge, Banner, BaseInput, Body100, Body200, Body400, Button, ButtonContainer, CheckboxInput, CollapsibleSource, Container, Content, CurrencyInput, DateInput, DetailElement, DetailRow, DetailsContainer, DotsMenu, DropDown, DropdownContainer, DropdownItem, DropdownList, EmailInput, EnvironmentTag, FieldsGrid, FileInput, FlexColumnWrapper, FlexRowWrapper, FooterActions, FooterRight, Footnote, Form, FormActions, FormCheckboxLabel, FormColumn, FormRow, FormSubtitle, FormTitle, H1, H2, H3, HelperText, InputError, InputLabel, InputWrapper, Loader, ManualMethodCard, MethodBody, MethodCard, MethodHeader, MethodTitle, NoResults, PhoneInput, PlaidMethodCard, PlaidTile, Preview, RadioInput, SSNInput, SearchDropdown, SelectInput, StatusTag, Stepper, Tabs, TaxIdInput, TermsDocument, TermsList, TextInput, ThemeProvider, UnderwritingStatus, UploadZone, buildMuiTheme, defaultTheme, describeComponentEnv, describeComponentEnvTitle, mergeBranding, resolveComponentEnvTag, useTheme };
923
+ export type { AchVerificationConsentProps, BadgeVariant, Branding, CollapsibleSourceProps, ComponentBuildInfo, ComponentEnv, ComponentEnvTag, CurrencyInputProps, DateInputProps, DotsMenuOption, DotsMenuProps, DropDownProps, EmailInputProps, EnvironmentName, EnvironmentTagInfo, EnvironmentTagProps, FileInputProps, FootnoteLink, FootnoteProps, LoaderProps, ManualMethodCardProps, ManualMethodStatus, ManualMethodValue, Option, PhoneInputProps, PlaidConnection, PlaidMethodCardProps, PlaidTileProps, PreviewProps, SSNInputProps, SearchDropdownProps, StepperProps, StepperStep, Tab, TabsProps, TaxIdInputProps, TermsDocumentProps, TextInputProps, Theme, ThemeBorder, ThemeColors, ThemeProviderProps, ThemeTypography, UnderwritingDecision, UnderwritingStatusProps, UploadZoneProps, UploadedFile };
package/dist/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createTheme, ThemeProvider as ThemeProvider$2, useTheme as useTheme$2, alpha } from '@mui/material/styles';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import React, { useMemo, useId, useRef, useState } from 'react';
4
- import styled, { createGlobalStyle, ThemeProvider as ThemeProvider$1, useTheme as useTheme$1 } from 'styled-components';
4
+ import styled, { createGlobalStyle, ThemeProvider as ThemeProvider$1, useTheme as useTheme$1, css } from 'styled-components';
5
5
  import { TextField, InputAdornment, IconButton, FormControl, InputLabel as InputLabel$1, Select, MenuItem, FormHelperText, Autocomplete, Box, CircularProgress } from '@mui/material';
6
6
  import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
7
7
  import CloudUploadIcon from '@mui/icons-material/CloudUpload';
@@ -2100,6 +2100,17 @@ const MethodCard = styled.div`
2100
2100
  transition:
2101
2101
  border-color 0.15s ease,
2102
2102
  background 0.15s ease;
2103
+
2104
+ /**
2105
+ * The unsaved-edit bar. It thickens the left border rather than adding one, so
2106
+ * the padding gives back the 2px it takes and the card's contents do not shift
2107
+ * when it appears mid-keystroke. Declared last so it wins over the shorthand
2108
+ * above, including on a $default card that already has an accent border.
2109
+ */
2110
+ ${({ theme, $edited }) => $edited && css`
2111
+ border-left: 3px solid ${theme.colors.accent};
2112
+ padding-left: 14px;
2113
+ `}
2103
2114
  `;
2104
2115
  const MethodBody = styled.div`
2105
2116
  flex: 1;
@@ -2164,6 +2175,10 @@ const FooterActions = styled.div`
2164
2175
  button {
2165
2176
  margin-top: 0;
2166
2177
  }
2178
+
2179
+ > :only-child {
2180
+ margin-left: auto;
2181
+ }
2167
2182
  `;
2168
2183
  const FooterRight = styled.div`
2169
2184
  display: flex;
@@ -2214,6 +2229,7 @@ function ManualMethodCard({
2214
2229
  index,
2215
2230
  value,
2216
2231
  isDefault = false,
2232
+ status,
2217
2233
  trashDisabled = false,
2218
2234
  disabled = false,
2219
2235
  accountError,
@@ -2225,7 +2241,7 @@ function ManualMethodCard({
2225
2241
  name = "default-method"
2226
2242
  }) {
2227
2243
  const radioId = `default-${value.id}`;
2228
- return /* @__PURE__ */ jsxs(MethodCard, { $default: isDefault, children: [
2244
+ return /* @__PURE__ */ jsxs(MethodCard, { $default: isDefault, $edited: status === "edited", children: [
2229
2245
  /* @__PURE__ */ jsx(
2230
2246
  RadioInput,
2231
2247
  {
@@ -2244,7 +2260,8 @@ function ManualMethodCard({
2244
2260
  "Account ",
2245
2261
  index + 1
2246
2262
  ] }),
2247
- isDefault && /* @__PURE__ */ jsx(Badge, { $variant: "filled", children: "Default" })
2263
+ isDefault && /* @__PURE__ */ jsx(Badge, { $variant: "filled", children: "Default" }),
2264
+ status === "edited" && /* @__PURE__ */ jsx(Badge, { $variant: "info", children: "Unsaved" })
2248
2265
  ] }),
2249
2266
  /* @__PURE__ */ jsx(
2250
2267
  IconButton$1,
@@ -2348,6 +2365,167 @@ function AchVerificationConsent({
2348
2365
  ] });
2349
2366
  }
2350
2367
 
2368
+ const AMBER = "#F59E0B";
2369
+ const CYAN = "#22D3EE";
2370
+ const GREY = "#9CA3AF";
2371
+ const GREEN = "#22C55E";
2372
+ const WHITE = "#FFFFFF";
2373
+ const INK = "#141312";
2374
+ const RED = "#DC2626";
2375
+ const ENVIRONMENT_MISMATCH_BORDER = RED;
2376
+ const ENVIRONMENT_TAGS = Object.freeze({
2377
+ production: Object.freeze({ label: "PROD", name: "Production", fill: WHITE, ink: INK }),
2378
+ staging: Object.freeze({ label: "STG", name: "Staging", fill: AMBER, ink: INK }),
2379
+ qa: Object.freeze({ label: "QA", name: "QA", fill: CYAN, ink: INK }),
2380
+ development: Object.freeze({ label: "DEV", name: "Development", fill: GREY, ink: INK }),
2381
+ local: Object.freeze({ label: "LOCAL", name: "Local", fill: GREEN, ink: INK })
2382
+ });
2383
+ const UNKNOWN_ENVIRONMENT_TAG = Object.freeze({
2384
+ label: "?",
2385
+ name: "Unknown",
2386
+ fill: WHITE,
2387
+ ink: INK
2388
+ });
2389
+ const INVALID_API_TAG = Object.freeze({
2390
+ label: "?",
2391
+ name: "Unknown",
2392
+ fill: RED,
2393
+ ink: WHITE
2394
+ });
2395
+ const ALIASES = /* @__PURE__ */ new Map([
2396
+ ["production", "production"],
2397
+ ["prod", "production"],
2398
+ ["staging", "staging"],
2399
+ ["stg", "staging"],
2400
+ ["qa", "qa"],
2401
+ ["development", "development"],
2402
+ ["dev", "development"],
2403
+ ["local", "local"]
2404
+ ]);
2405
+ const normalizeEnvironment = (value) => {
2406
+ const key = value?.trim().toLowerCase();
2407
+ return key && ALIASES.get(key) || null;
2408
+ };
2409
+ const resolveComponentEnvTag = ({
2410
+ bundleEnv,
2411
+ apiEnv
2412
+ }) => {
2413
+ const apiValue = apiEnv?.trim();
2414
+ if (!apiValue) return null;
2415
+ const api = normalizeEnvironment(apiValue);
2416
+ if (!api) return { kind: "invalid-api", apiEnv: apiValue };
2417
+ const bundleValue = bundleEnv?.trim();
2418
+ const bundle = normalizeEnvironment(bundleValue);
2419
+ const apiTag = ENVIRONMENT_TAGS[api];
2420
+ if (bundleValue && !bundle) {
2421
+ return { kind: "split", bundleEnv: null, bundle: UNKNOWN_ENVIRONMENT_TAG, api: apiTag };
2422
+ }
2423
+ if (bundle === "local" && api !== "local" && api !== "production") {
2424
+ return { kind: "local", api: apiTag };
2425
+ }
2426
+ if (bundle && bundle !== api) {
2427
+ return { kind: "split", bundleEnv: bundle, bundle: ENVIRONMENT_TAGS[bundle], api: apiTag };
2428
+ }
2429
+ if (api === "production") return null;
2430
+ return { kind: "single", tag: apiTag };
2431
+ };
2432
+ const environmentDebug = (value) => {
2433
+ const env = normalizeEnvironment(value);
2434
+ if (env) return env;
2435
+ const raw = value?.trim();
2436
+ return raw ? `unrecognised:${raw}` : "unknown";
2437
+ };
2438
+ const describeComponentEnv = ({ bundleEnv, apiEnv, ref, sha }) => [
2439
+ `bundle=${environmentDebug(bundleEnv)}`,
2440
+ `api=${environmentDebug(apiEnv)}`,
2441
+ `ref=${ref || "unknown"}`,
2442
+ `sha=${sha || "unknown"}`
2443
+ ].join(" ");
2444
+ const environmentTitle = (value) => {
2445
+ const env = normalizeEnvironment(value);
2446
+ if (env) return ENVIRONMENT_TAGS[env].name;
2447
+ const raw = value?.trim();
2448
+ return raw ? `Unrecognised "${raw}"` : "Unknown";
2449
+ };
2450
+ const describeComponentEnvTitle = ({
2451
+ bundleEnv,
2452
+ apiEnv,
2453
+ ref,
2454
+ sha
2455
+ }) => {
2456
+ const build = [ref, sha].filter(Boolean).join(", ");
2457
+ return `Bundle: ${environmentTitle(bundleEnv)}${build ? ` (${build})` : ""} \xB7 API: ${environmentTitle(apiEnv)}`;
2458
+ };
2459
+
2460
+ const FONT_SIZE = { small: 9, medium: 11 };
2461
+ const PADDING_X = { small: 6, medium: 8 };
2462
+ const FONT_FAMILY = 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
2463
+ const BORDER_WIDTH = 1.5;
2464
+ const base = (size) => ({
2465
+ display: "inline-flex",
2466
+ alignItems: "stretch",
2467
+ boxSizing: "border-box",
2468
+ fontFamily: FONT_FAMILY,
2469
+ fontSize: FONT_SIZE[size],
2470
+ fontWeight: 800,
2471
+ letterSpacing: "0.1em",
2472
+ lineHeight: 1.4,
2473
+ whiteSpace: "nowrap",
2474
+ borderRadius: 0
2475
+ });
2476
+ const half = (tag, label, size) => /* @__PURE__ */ jsx("span", { style: { padding: `2px ${PADDING_X[size]}px`, background: tag.fill, color: tag.ink }, children: label });
2477
+ const EnvironmentTag = ({
2478
+ bundleEnv,
2479
+ apiEnv,
2480
+ buildRef,
2481
+ buildSha,
2482
+ size = "small",
2483
+ testId = "oatfi-env-tag"
2484
+ }) => {
2485
+ const result = resolveComponentEnvTag({ bundleEnv, apiEnv });
2486
+ if (!result) return null;
2487
+ const title = describeComponentEnvTitle({ bundleEnv, apiEnv, ref: buildRef, sha: buildSha });
2488
+ const chip = (border, children) => /* @__PURE__ */ jsx(
2489
+ "span",
2490
+ {
2491
+ role: "note",
2492
+ "aria-label": `Environment: ${title}`,
2493
+ "data-testid": testId,
2494
+ "data-env-kind": result.kind,
2495
+ title,
2496
+ style: { ...base(size), border: `${BORDER_WIDTH}px solid ${border}` },
2497
+ children
2498
+ }
2499
+ );
2500
+ switch (result.kind) {
2501
+ case "single":
2502
+ return chip(result.tag.fill, half(result.tag, result.tag.label, size));
2503
+ case "local":
2504
+ return chip(
2505
+ ENVIRONMENT_TAGS.local.fill,
2506
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2507
+ half(ENVIRONMENT_TAGS.local, ENVIRONMENT_TAGS.local.label, size),
2508
+ half(result.api, `API ${result.api.label}`, size)
2509
+ ] })
2510
+ );
2511
+ case "invalid-api":
2512
+ return chip(
2513
+ INVALID_API_TAG.fill,
2514
+ half(INVALID_API_TAG, `API ${INVALID_API_TAG.label}`, size)
2515
+ );
2516
+ case "split": {
2517
+ const bundleLabel = result.bundleEnv === "local" ? "LOCAL" : `BUNDLE ${result.bundle.label}`;
2518
+ return chip(
2519
+ ENVIRONMENT_MISMATCH_BORDER,
2520
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2521
+ half(result.bundle, bundleLabel, size),
2522
+ half(result.api, `API ${result.api.label}`, size)
2523
+ ] })
2524
+ );
2525
+ }
2526
+ }
2527
+ };
2528
+
2351
2529
  const PreviewCard = styled.div`
2352
2530
  display: flex;
2353
2531
  gap: 2rem;
@@ -2419,5 +2597,5 @@ function Preview({ branding, className }) {
2419
2597
  return /* @__PURE__ */ jsx(ThemeProvider, { branding, children: /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(PreviewContent, {}) }) });
2420
2598
  }
2421
2599
 
2422
- export { AchVerificationConsent, AsteriskMark, AsteriskNote, AutocompleteInput, Badge, Banner, BaseInput, Body100, Body200, Body400, Button, ButtonContainer, CheckboxInput, CollapsibleSource, Container, Content$1 as Content, CurrencyInput, DateInput, DetailElement, DetailRow, DetailsContainer, DotsMenu, DropDown, DropdownContainer, DropdownItem, DropdownList, EmailInput, FieldsGrid, FileInput, FlexColumnWrapper, FlexRowWrapper, FooterActions, FooterRight, Footnote, Form, FormActions, FormCheckboxLabel, FormColumn, FormRow, FormSubtitle, FormTitle, H1, H2, H3, HelperText, InputError, InputLabel, InputWrapper, Loader, ManualMethodCard, MethodBody, MethodCard, MethodHeader, MethodTitle, NoResults, PhoneInput, PlaidMethodCard, PlaidTile, Preview, RadioInput, SSNInput, SearchDropdown, SelectInput, StatusTag, Stepper, Tabs, TaxIdInput, TermsDocument, TermsList, TextInput, ThemeProvider, UnderwritingStatus, UploadZone, buildMuiTheme, defaultTheme, mergeBranding, useTheme };
2600
+ export { AchVerificationConsent, AsteriskMark, AsteriskNote, AutocompleteInput, Badge, Banner, BaseInput, Body100, Body200, Body400, Button, ButtonContainer, CheckboxInput, CollapsibleSource, Container, Content$1 as Content, CurrencyInput, DateInput, DetailElement, DetailRow, DetailsContainer, DotsMenu, DropDown, DropdownContainer, DropdownItem, DropdownList, EmailInput, EnvironmentTag, FieldsGrid, FileInput, FlexColumnWrapper, FlexRowWrapper, FooterActions, FooterRight, Footnote, Form, FormActions, FormCheckboxLabel, FormColumn, FormRow, FormSubtitle, FormTitle, H1, H2, H3, HelperText, InputError, InputLabel, InputWrapper, Loader, ManualMethodCard, MethodBody, MethodCard, MethodHeader, MethodTitle, NoResults, PhoneInput, PlaidMethodCard, PlaidTile, Preview, RadioInput, SSNInput, SearchDropdown, SelectInput, StatusTag, Stepper, Tabs, TaxIdInput, TermsDocument, TermsList, TextInput, ThemeProvider, UnderwritingStatus, UploadZone, buildMuiTheme, defaultTheme, describeComponentEnv, describeComponentEnvTitle, mergeBranding, resolveComponentEnvTag, useTheme };
2423
2601
  //# sourceMappingURL=index.esm.js.map