@fairmint/ui 0.2.2 → 0.2.4

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.
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import { TextInputProps } from '../../primitives/TextInput/TextInput';
3
+ /**
4
+ * Zod schema for Ethereum wallet address validation.
5
+ * Trims whitespace, then validates `0x` + 40 hex characters, rejecting the zero address.
6
+ */
7
+ export declare const ethereumAddressSchema: z.ZodEffects<z.ZodString, string, string>;
8
+ export interface EthereumAddressFieldProps extends Omit<TextInputProps, "type"> {
9
+ error?: string;
10
+ }
11
+ /** Ethereum wallet address input with monospace font and Zod-based validation via `ethereumAddressSchema`. */
12
+ export declare function EthereumAddressField({ error, ...rest }: EthereumAddressFieldProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=EthereumAddressField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EthereumAddressField.d.ts","sourceRoot":"","sources":["../../../../src/components/form/EthereumAddressField/EthereumAddressField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAG3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,2CAU/B,CAAC;AAEJ,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,8GAA8G;AAC9G,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,yBAAyB,2CAcjF"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import { TextInputProps } from '../../primitives/TextInput/TextInput';
3
+ export interface ZipSchemaOptions {
4
+ /** When true, accepts international postal codes (UK, Canada, Europe, etc.).
5
+ * Defaults to false — US ZIP codes only. */
6
+ international?: boolean;
7
+ }
8
+ /**
9
+ * Creates a Zod schema for postal/ZIP code validation.
10
+ *
11
+ * Default (US): accepts `94105` and `94105-1234` only.
12
+ * International: accepts `K1A 0B1`, `SW1A 2AA`, `12345`, etc.
13
+ * Input is trimmed before validation in both modes.
14
+ */
15
+ export declare function createZipSchema(options?: ZipSchemaOptions): z.ZodString;
16
+ /** Zod schema for US ZIP codes (`94105`, `94105-1234`). Use `createZipSchema({ international: true })` for international. */
17
+ export declare const zipSchema: z.ZodString;
18
+ export interface ZipFieldProps extends Omit<TextInputProps, "type"> {
19
+ error?: string;
20
+ /** When true, adjusts placeholder for international postal codes. */
21
+ international?: boolean;
22
+ }
23
+ /** Postal/ZIP code input. US ZIP only by default; pass `international` for global formats. */
24
+ export declare function ZipField({ error, international, ...rest }: ZipFieldProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=ZipField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZipField.d.ts","sourceRoot":"","sources":["../../../../src/components/form/ZipField/ZipField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAa3E,MAAM,WAAW,gBAAgB;IAC/B;iDAC6C;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,eAazD;AAED,6HAA6H;AAC7H,eAAO,MAAM,SAAS,aAAoB,CAAC;AAE3C,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,8FAA8F;AAC9F,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,aAAqB,EAAE,GAAG,IAAI,EAAE,EAAE,aAAa,2CAYhF"}
@@ -0,0 +1,43 @@
1
+ import { ReactNode } from 'react';
2
+ export interface EditableSectionProps {
3
+ /** Section heading shown in the header row. */
4
+ title?: ReactNode;
5
+ /** Content rendered in read (non-editing) mode. */
6
+ readView: ReactNode;
7
+ /**
8
+ * Called when Save is clicked. Resolve to close edit mode; throw to stay open
9
+ * (e.g. validation failure or API error — handle `notify.error` inside here).
10
+ */
11
+ onSave: () => Promise<void>;
12
+ /** Called when Cancel is clicked, before the edit mode closes. Use to reset form state. */
13
+ onCancel?: () => void;
14
+ /**
15
+ * When true the edit button is hidden and any open edit session is closed.
16
+ * Pass `isViewingAs` to prevent edits during admin impersonation.
17
+ */
18
+ disabled?: boolean;
19
+ /**
20
+ * Called when `onSave` throws. Use for logging or error tracking. The
21
+ * component stays in edit mode regardless — this is purely observational.
22
+ */
23
+ onSaveError?: (error: unknown) => void;
24
+ /**
25
+ * Icon rendered inside the edit `ActionIcon`. When omitted a plain text
26
+ * "Edit" button is shown instead.
27
+ */
28
+ editIcon?: ReactNode;
29
+ /** Save button label. @default "Save" */
30
+ saveLabel?: string;
31
+ /** Cancel button label. @default "Cancel" */
32
+ cancelLabel?: string;
33
+ /** Children rendered in edit mode (form fields, etc.). */
34
+ children: ReactNode;
35
+ }
36
+ /**
37
+ * Wraps a section of the page with a pencil-icon toggle between a read view
38
+ * and an edit view. Manages `isEditing` and `isSaving` state internally so
39
+ * consumers only need to supply `readView`, the edit fields as `children`,
40
+ * and the async `onSave` callback.
41
+ */
42
+ export declare function EditableSection({ title, readView, onSave, onCancel, disabled, onSaveError, editIcon, saveLabel, cancelLabel, children, }: EditableSectionProps): import("react/jsx-runtime").JSX.Element;
43
+ //# sourceMappingURL=EditableSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditableSection.d.ts","sourceRoot":"","sources":["../../../../src/components/generic/EditableSection/EditableSection.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAM5D,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAgB,EAChB,WAAW,EACX,QAAQ,EACR,SAAkB,EAClB,WAAsB,EACtB,QAAQ,GACT,EAAE,oBAAoB,2CAwFtB"}
@@ -23751,6 +23751,46 @@ function EmailField({ error: error2, ...rest }) {
23751
23751
  }
23752
23752
  );
23753
23753
  }
23754
+ function truncateAddress(address, chars = 4) {
23755
+ if (address.length <= chars * 2 + 2) return address;
23756
+ return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
23757
+ }
23758
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
23759
+ const ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
23760
+ function isEthereumAddress(value) {
23761
+ return ETH_ADDRESS_RE.test(value) && value.toLowerCase() !== ZERO_ADDRESS;
23762
+ }
23763
+ function validateEthereumAddress(value) {
23764
+ if (!ETH_ADDRESS_RE.test(value))
23765
+ return "Must be a valid Ethereum address (0x + 40 hex characters)";
23766
+ if (value.toLowerCase() === ZERO_ADDRESS) return "Cannot use the zero address";
23767
+ return null;
23768
+ }
23769
+ const ETHEREUM_ADDRESS_REGEX = ETH_ADDRESS_RE;
23770
+ const ZERO_ETHEREUM_ADDRESS = ZERO_ADDRESS;
23771
+ const ethereumAddressSchema = stringType().trim().regex(
23772
+ ETHEREUM_ADDRESS_REGEX,
23773
+ "Must be a valid Ethereum address (0x followed by 40 hex characters)"
23774
+ ).refine(
23775
+ (v) => v.toLowerCase() !== ZERO_ETHEREUM_ADDRESS.toLowerCase(),
23776
+ "Cannot use the zero address"
23777
+ );
23778
+ function EthereumAddressField({ error: error2, ...rest }) {
23779
+ return /* @__PURE__ */ jsxRuntime.jsx(
23780
+ TextInput,
23781
+ {
23782
+ type: "text",
23783
+ label: rest.label ?? "Wallet Address",
23784
+ placeholder: rest.placeholder ?? "0x...",
23785
+ autoComplete: "off",
23786
+ inputMode: "text",
23787
+ spellCheck: false,
23788
+ styles: { input: { fontFamily: "monospace" } },
23789
+ error: error2,
23790
+ ...rest
23791
+ }
23792
+ );
23793
+ }
23754
23794
  function validateFormName(name) {
23755
23795
  if (!/^[0-9a-zA-Z-]+$/.test(name)) {
23756
23796
  throw new Error(
@@ -24840,6 +24880,29 @@ function PhoneField({ error: error2, ...rest }) {
24840
24880
  }
24841
24881
  );
24842
24882
  }
24883
+ const US_ZIP_REGEX = /^\d{5}(-\d{4})?$/;
24884
+ const INTL_POSTAL_REGEX = /^[A-Z0-9]+(?:[ -][A-Z0-9]+)*$/i;
24885
+ function createZipSchema(options) {
24886
+ if (options?.international) {
24887
+ return stringType().trim().min(3, "Please enter a valid postal code").max(10, "Please enter a valid postal code").regex(INTL_POSTAL_REGEX, "Please enter a valid postal code");
24888
+ }
24889
+ return stringType().trim().regex(US_ZIP_REGEX, "Please enter a valid ZIP code (e.g. 94105 or 94105-1234)");
24890
+ }
24891
+ const zipSchema = createZipSchema();
24892
+ function ZipField({ error: error2, international = false, ...rest }) {
24893
+ return /* @__PURE__ */ jsxRuntime.jsx(
24894
+ TextInput,
24895
+ {
24896
+ type: "text",
24897
+ label: rest.label ?? "ZIP / Postal Code",
24898
+ placeholder: rest.placeholder ?? (international ? "SW1A 2AA" : "94105"),
24899
+ autoComplete: "postal-code",
24900
+ inputMode: "text",
24901
+ error: error2,
24902
+ ...rest
24903
+ }
24904
+ );
24905
+ }
24843
24906
  const ANIMATION_MAP = {
24844
24907
  up: "fadeUp",
24845
24908
  down: "fadeDown",
@@ -25072,6 +25135,75 @@ function DetailDrawer({
25072
25135
  }
25073
25136
  );
25074
25137
  }
25138
+ function EditableSection({
25139
+ title,
25140
+ readView,
25141
+ onSave,
25142
+ onCancel,
25143
+ disabled = false,
25144
+ onSaveError,
25145
+ editIcon,
25146
+ saveLabel = "Save",
25147
+ cancelLabel = "Cancel",
25148
+ children
25149
+ }) {
25150
+ const [isEditing, setIsEditing] = React.useState(false);
25151
+ const [isSaving, setIsSaving] = React.useState(false);
25152
+ React.useEffect(() => {
25153
+ if (disabled && isEditing) {
25154
+ onCancel?.();
25155
+ setIsEditing(false);
25156
+ }
25157
+ }, [disabled, isEditing, onCancel]);
25158
+ const handleEdit = () => setIsEditing(true);
25159
+ const handleSave = async () => {
25160
+ setIsSaving(true);
25161
+ try {
25162
+ await onSave();
25163
+ setIsEditing(false);
25164
+ } catch (err) {
25165
+ onSaveError?.(err);
25166
+ } finally {
25167
+ setIsSaving(false);
25168
+ }
25169
+ };
25170
+ const handleCancel = () => {
25171
+ onCancel?.();
25172
+ setIsEditing(false);
25173
+ };
25174
+ return /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "md", children: [
25175
+ (title || !disabled && !isEditing) && /* @__PURE__ */ jsxRuntime.jsxs(Group, { justify: "space-between", align: "center", children: [
25176
+ title && /* @__PURE__ */ jsxRuntime.jsx(Text, { fw: 500, size: "sm", children: title }),
25177
+ !disabled && !isEditing && (editIcon ? /* @__PURE__ */ jsxRuntime.jsx(
25178
+ ActionIcon,
25179
+ {
25180
+ variant: "subtle",
25181
+ size: "sm",
25182
+ onClick: handleEdit,
25183
+ "aria-label": `Edit ${typeof title === "string" ? title : "section"}`,
25184
+ children: editIcon
25185
+ }
25186
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "subtle", size: "xs", onClick: handleEdit, children: "Edit" }))
25187
+ ] }),
25188
+ isEditing ? /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "sm", children: [
25189
+ children,
25190
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { gap: "xs", justify: "flex-end", children: [
25191
+ /* @__PURE__ */ jsxRuntime.jsx(
25192
+ Button,
25193
+ {
25194
+ variant: "subtle",
25195
+ size: "xs",
25196
+ color: colors.neutral,
25197
+ onClick: handleCancel,
25198
+ disabled: isSaving,
25199
+ children: cancelLabel
25200
+ }
25201
+ ),
25202
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "xs", onClick: handleSave, loading: isSaving, children: saveLabel })
25203
+ ] })
25204
+ ] }) : readView
25205
+ ] });
25206
+ }
25075
25207
  const DefaultChevronIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
25076
25208
  "svg",
25077
25209
  {
@@ -25533,23 +25665,6 @@ function InvestorProfileCard({
25533
25665
  children
25534
25666
  ] });
25535
25667
  }
25536
- function truncateAddress(address, chars = 4) {
25537
- if (address.length <= chars * 2 + 2) return address;
25538
- return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25539
- }
25540
- const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
25541
- const ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
25542
- function isEthereumAddress(value) {
25543
- return ETH_ADDRESS_RE.test(value) && value.toLowerCase() !== ZERO_ADDRESS;
25544
- }
25545
- function validateEthereumAddress(value) {
25546
- if (!ETH_ADDRESS_RE.test(value))
25547
- return "Must be a valid Ethereum address (0x + 40 hex characters)";
25548
- if (value.toLowerCase() === ZERO_ADDRESS) return "Cannot use the zero address";
25549
- return null;
25550
- }
25551
- const ETHEREUM_ADDRESS_REGEX = ETH_ADDRESS_RE;
25552
- const ZERO_ETHEREUM_ADDRESS = ZERO_ADDRESS;
25553
25668
  const DEFAULT_TYPE_LABELS = {
25554
25669
  fairmint: "Fairmint Wallet",
25555
25670
  privy: "Fairmint Wallet",
@@ -25793,10 +25908,12 @@ exports.Divider = Divider;
25793
25908
  exports.DocumentRow = DocumentRow;
25794
25909
  exports.Drawer = Drawer;
25795
25910
  exports.ETHEREUM_ADDRESS_REGEX = ETHEREUM_ADDRESS_REGEX;
25911
+ exports.EditableSection = EditableSection;
25796
25912
  exports.EmailField = EmailField;
25797
25913
  exports.EmptyState = EmptyState;
25798
25914
  exports.EquityGrantCard = EquityGrantCard;
25799
25915
  exports.ErrorFallback = ErrorFallback;
25916
+ exports.EthereumAddressField = EthereumAddressField;
25800
25917
  exports.FairmintLogo = FairmintLogo;
25801
25918
  exports.FairmintProvider = FairmintProvider;
25802
25919
  exports.Flex = Flex;
@@ -25838,9 +25955,12 @@ exports.Tooltip = Tooltip;
25838
25955
  exports.UserMenu = UserMenu;
25839
25956
  exports.WalletList = WalletList;
25840
25957
  exports.ZERO_ETHEREUM_ADDRESS = ZERO_ETHEREUM_ADDRESS;
25958
+ exports.ZipField = ZipField;
25841
25959
  exports.brandColors = brandColors;
25842
25960
  exports.colors = colors;
25961
+ exports.createZipSchema = createZipSchema;
25843
25962
  exports.emailSchema = emailSchema;
25963
+ exports.ethereumAddressSchema = ethereumAddressSchema;
25844
25964
  exports.formatCurrency = formatCurrency;
25845
25965
  exports.formatCurrencyPrecise = formatCurrencyPrecise;
25846
25966
  exports.formatDate = formatDate;
@@ -25871,4 +25991,5 @@ exports.useScrollIntoView = useScrollIntoView;
25871
25991
  exports.useViewportSize = useViewportSize;
25872
25992
  exports.validateEthereumAddress = validateEthereumAddress;
25873
25993
  exports.z = z;
25994
+ exports.zipSchema = zipSchema;
25874
25995
  //# sourceMappingURL=fairmint-ui.cjs.map