@fairmint/ui 0.2.3 → 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,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(
@@ -25095,6 +25135,75 @@ function DetailDrawer({
25095
25135
  }
25096
25136
  );
25097
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
+ }
25098
25207
  const DefaultChevronIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
25099
25208
  "svg",
25100
25209
  {
@@ -25556,23 +25665,6 @@ function InvestorProfileCard({
25556
25665
  children
25557
25666
  ] });
25558
25667
  }
25559
- function truncateAddress(address, chars = 4) {
25560
- if (address.length <= chars * 2 + 2) return address;
25561
- return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25562
- }
25563
- const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
25564
- const ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
25565
- function isEthereumAddress(value) {
25566
- return ETH_ADDRESS_RE.test(value) && value.toLowerCase() !== ZERO_ADDRESS;
25567
- }
25568
- function validateEthereumAddress(value) {
25569
- if (!ETH_ADDRESS_RE.test(value))
25570
- return "Must be a valid Ethereum address (0x + 40 hex characters)";
25571
- if (value.toLowerCase() === ZERO_ADDRESS) return "Cannot use the zero address";
25572
- return null;
25573
- }
25574
- const ETHEREUM_ADDRESS_REGEX = ETH_ADDRESS_RE;
25575
- const ZERO_ETHEREUM_ADDRESS = ZERO_ADDRESS;
25576
25668
  const DEFAULT_TYPE_LABELS = {
25577
25669
  fairmint: "Fairmint Wallet",
25578
25670
  privy: "Fairmint Wallet",
@@ -25816,10 +25908,12 @@ exports.Divider = Divider;
25816
25908
  exports.DocumentRow = DocumentRow;
25817
25909
  exports.Drawer = Drawer;
25818
25910
  exports.ETHEREUM_ADDRESS_REGEX = ETHEREUM_ADDRESS_REGEX;
25911
+ exports.EditableSection = EditableSection;
25819
25912
  exports.EmailField = EmailField;
25820
25913
  exports.EmptyState = EmptyState;
25821
25914
  exports.EquityGrantCard = EquityGrantCard;
25822
25915
  exports.ErrorFallback = ErrorFallback;
25916
+ exports.EthereumAddressField = EthereumAddressField;
25823
25917
  exports.FairmintLogo = FairmintLogo;
25824
25918
  exports.FairmintProvider = FairmintProvider;
25825
25919
  exports.Flex = Flex;
@@ -25866,6 +25960,7 @@ exports.brandColors = brandColors;
25866
25960
  exports.colors = colors;
25867
25961
  exports.createZipSchema = createZipSchema;
25868
25962
  exports.emailSchema = emailSchema;
25963
+ exports.ethereumAddressSchema = ethereumAddressSchema;
25869
25964
  exports.formatCurrency = formatCurrency;
25870
25965
  exports.formatCurrencyPrecise = formatCurrencyPrecise;
25871
25966
  exports.formatDate = formatDate;