@alixpartners/ui-components 2.2.0 → 2.3.1

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,30 @@
1
+ function u(r, e) {
2
+ const t = e.maxSize ? e.maxSize * 1024 * 1024 : Number.POSITIVE_INFINITY, o = new Set(e.fileExtensionsAllowed.map((l) => l.toLowerCase().replace(/^\./, ""))), s = r.size <= t, n = r.name.toLowerCase(), a = n.includes(".") ? n.split(".").pop() ?? "" : "", i = o.size === 0 || o.has(a);
3
+ return s ? i ? {
4
+ file: r
5
+ } : {
6
+ file: r,
7
+ error: `The file format is incorrect. Please make sure it is one of the extensions: ${e.fileExtensionsAllowed.join(", ")}.`,
8
+ errorCode: "INVALID_EXTENSION"
9
+ } : {
10
+ file: r,
11
+ error: `File size is too large. Must be up to ${e.maxSize}MB.`,
12
+ errorCode: "SIZE_TOO_LARGE"
13
+ };
14
+ }
15
+ function c(...r) {
16
+ return (e, t) => {
17
+ for (const o of r) {
18
+ const s = o(e, t);
19
+ if (s.error)
20
+ return s;
21
+ }
22
+ return {
23
+ file: e
24
+ };
25
+ };
26
+ }
27
+ export {
28
+ c,
29
+ u as d
30
+ };
@@ -1,9 +1,37 @@
1
1
  import { DataAttributes } from '../../types/data-attributes';
2
2
  import { DraggableEventHandlers } from '../../types/native-events';
3
+ import { ButtonProps } from '../Button/Button';
4
+ import { ApIcon } from '../../assets/ap-icons-types';
3
5
  export type UploadFile = {
4
6
  file: File;
5
7
  error?: string;
6
8
  };
9
+ export type ValidationErrorCode = 'SIZE_TOO_LARGE' | 'INVALID_EXTENSION' | 'MAX_FILES' | 'SINGLE_FILE_ONLY';
10
+ export type ValidationResult = {
11
+ file: File;
12
+ error?: string;
13
+ errorCode?: ValidationErrorCode;
14
+ };
15
+ export type ValidationContext = {
16
+ maxSize: number;
17
+ fileExtensionsAllowed: string[];
18
+ type: 'single' | 'multiple';
19
+ maxFiles: number;
20
+ };
21
+ export type Validator = (file: File, context: ValidationContext) => ValidationResult;
22
+ export type DragAndDropTexts = {
23
+ browse?: string | ((type: 'single' | 'multiple') => string);
24
+ drag?: string | ((type: 'single' | 'multiple') => string);
25
+ constraints?: (params: {
26
+ extensions: string[];
27
+ maxSize: number;
28
+ type: 'single' | 'multiple';
29
+ }) => string;
30
+ errors?: {
31
+ sizeTooLarge?: string | ((maxSize: number) => string);
32
+ invalidExtension?: string | ((extensions: string[]) => string);
33
+ };
34
+ };
7
35
  type DragAndDropProps = {
8
36
  className?: string;
9
37
  type?: 'single' | 'multiple';
@@ -15,8 +43,35 @@ type DragAndDropProps = {
15
43
  maxFiles?: number;
16
44
  value?: File | File[];
17
45
  onUpload?: (files: UploadFile[]) => void;
46
+ /**
47
+ * Callback fired when a file is removed.
48
+ * @param fileName - Name of the file to remove
49
+ */
18
50
  onRemoveFile?: (fileName: string) => void;
19
51
  queueFiles?: boolean;
52
+ /**
53
+ * Customizable text content for the component.
54
+ * @note Should be memoized (e.g., using `useMemo`) to prevent unnecessary re-renders.
55
+ * If an inline object is passed, all memoized callbacks will recreate on every render.
56
+ */
57
+ texts?: DragAndDropTexts;
58
+ fileIcon?: ApIcon;
59
+ errorIcon?: ApIcon;
60
+ deleteIcon?: ApIcon;
61
+ browseButtonIcon?: ApIcon;
62
+ renderFileItem?: (file: UploadFile, index: number, onRemove: () => void) => React.ReactNode;
63
+ fileSizeFormatter?: (size: number) => string;
64
+ browseButtonProps?: Omit<Partial<ButtonProps>, 'onClick' | 'disabled'>;
65
+ validator?: Validator;
66
+ /**
67
+ * External errors to attach to files by filename.
68
+ * Useful for async/API validation results.
69
+ *
70
+ * NOTE: Error identity is filename-based. Duplicate filenames will collide.
71
+ * NOTE: The component does not clear external errors automatically;
72
+ * the parent owns that state.
73
+ */
74
+ errors?: Record<string, string>;
20
75
  } & DataAttributes & DraggableEventHandlers;
21
76
  /**
22
77
  * DragAndDrop provides a file upload surface that supports browsing files
@@ -35,5 +90,5 @@ type DragAndDropProps = {
35
90
  * @param {boolean} [props.queueFiles] - When true, newly selected files are appended to the existing list
36
91
  * @returns {JSX.Element} The rendered DragAndDrop component
37
92
  */
38
- export default function DragAndDrop({ label, type, disabled, required, fileExtensionsAllowed, maxSize, maxFiles, value, onUpload, onRemoveFile, queueFiles, className, ...props }: DragAndDropProps): import("react/jsx-runtime").JSX.Element;
93
+ export default function DragAndDrop({ label, type, disabled, required, fileExtensionsAllowed, maxSize, maxFiles, value, onUpload, onRemoveFile, queueFiles, className, texts, fileIcon, errorIcon, deleteIcon, browseButtonIcon, renderFileItem, fileSizeFormatter, browseButtonProps, validator, errors: externalErrors, ...props }: DragAndDropProps): import("react/jsx-runtime").JSX.Element;
39
94
  export {};
@@ -1,16 +1,16 @@
1
- import { jsxs as l, jsx as n } from "react/jsx-runtime";
2
- import { useId as J, useRef as w, useState as K, useCallback as M } from "react";
3
- import j from "../Button/Button.js";
4
- import { c as R } from "../../clsx-OuTLNxxd.js";
5
- import q from "../Icon/Icon.js";
6
- import '../../assets/DragAndDrop.css';const X = "DragAndDrop-module__disabled___h47do", U = "DragAndDrop-module__required___z3cHB", ee = "DragAndDrop-module__active___ZMuEx", a = {
1
+ import { jsxs as g, jsx as t } from "react/jsx-runtime";
2
+ import { useId as hr, useRef as Nr, useState as Q, useCallback as m, useMemo as A } from "react";
3
+ import R from "../Button/Button.js";
4
+ import { c as J } from "../../clsx-OuTLNxxd.js";
5
+ import K from "../Icon/Icon.js";
6
+ import '../../assets/DragAndDrop.css';const Ar = "DragAndDrop-module__disabled___h47do", vr = "DragAndDrop-module__required___z3cHB", Ir = "DragAndDrop-module__active___ZMuEx", n = {
7
7
  "drag-and-drop-container": "DragAndDrop-module__drag-and-drop-container___WHqGh",
8
- disabled: X,
9
- required: U,
8
+ disabled: Ar,
9
+ required: vr,
10
10
  "drag-and-drop-space-text-message": "DragAndDrop-module__drag-and-drop-space-text-message___fL-Ac",
11
11
  "drag-and-drop-space-text-constraints": "DragAndDrop-module__drag-and-drop-space-text-constraints___YrL7J",
12
12
  "drag-and-drop-space": "DragAndDrop-module__drag-and-drop-space___09a0I",
13
- active: ee,
13
+ active: Ir,
14
14
  "drag-and-drop-space-text": "DragAndDrop-module__drag-and-drop-space-text___-8rqC",
15
15
  "drag-and-drop-files-list": "DragAndDrop-module__drag-and-drop-files-list___7WNCk",
16
16
  "drag-and-drop-files-list-item": "DragAndDrop-module__drag-and-drop-files-list-item___vQO6M",
@@ -25,98 +25,159 @@ import '../../assets/DragAndDrop.css';const X = "DragAndDrop-module__disabled___
25
25
  "drag-and-drop-files-list-item-icon-error": "DragAndDrop-module__drag-and-drop-files-list-item-icon-error___17EE-",
26
26
  "drag-and-drop-files-list-item-icon-delete": "DragAndDrop-module__drag-and-drop-files-list-item-icon-delete___Z1l-B"
27
27
  };
28
- function ne({
28
+ function Or({
29
29
  label: k,
30
- type: c = "single",
31
- disabled: z,
32
- required: E,
33
- fileExtensionsAllowed: p,
34
- maxSize: g,
35
- maxFiles: m = Number.POSITIVE_INFINITY,
30
+ type: i = "single",
31
+ disabled: w,
32
+ required: F,
33
+ fileExtensionsAllowed: c,
34
+ maxSize: p,
35
+ maxFiles: l = Number.POSITIVE_INFINITY,
36
36
  value: f,
37
- onUpload: t,
38
- onRemoveFile: N,
39
- queueFiles: D,
40
- className: P,
41
- ...s
37
+ onUpload: _,
38
+ onRemoveFile: v,
39
+ queueFiles: q,
40
+ className: U,
41
+ texts: e,
42
+ fileIcon: x = "ap-icon-document",
43
+ errorIcon: rr = "ap-icon-alert",
44
+ deleteIcon: er = "ap-icon-delete",
45
+ browseButtonIcon: ar,
46
+ renderFileItem: j,
47
+ fileSizeFormatter: C,
48
+ browseButtonProps: nr,
49
+ validator: b,
50
+ errors: I,
51
+ ...ir
42
52
  }) {
43
- const B = J(), O = w(null), h = w(null), [A, x] = K([]), v = f !== void 0, C = p.join(", "), I = g ? `up to ${g}MB` : "", Y = p.map((e) => `.${e.replace(/^\./, "")}`).join(","), $ = () => {
44
- var e;
45
- u() || (e = O.current) == null || e.click();
46
- }, y = M((e) => {
47
- const i = g ? g * 1024 * 1024 : Number.POSITIVE_INFINITY, r = new Set(p.map((_) => _.toLowerCase().replace(/^\./, ""))), o = e.map((_) => {
48
- const L = _.size <= i, T = _.name.toLowerCase(), F = T.includes(".") ? T.split(".").pop() ?? "" : "", G = r.size === 0 || r.has(F);
49
- let b;
50
- return L || (b = `File size is too large. Must be ${I}.`), G || (b = `The file format is incorrect. Please make sure it is one of the extensions: ${C}.`), {
51
- file: _,
52
- error: b
53
+ const y = hr(), P = Nr(null), [u, S] = Q([]), [V, L] = Q(!1), D = f !== void 0, Y = c.join(", "), Z = p ? `up to ${p}MB` : "", dr = c.map((r) => `.${r.replace(/^\./, "")}`).join(","), G = m((r, a) => {
54
+ if (!r) return r || "";
55
+ if (e != null && e.errors && a) {
56
+ if (a === "SIZE_TOO_LARGE" && e.errors.sizeTooLarge)
57
+ return typeof e.errors.sizeTooLarge == "function" ? e.errors.sizeTooLarge(p) : e.errors.sizeTooLarge;
58
+ if (a === "INVALID_EXTENSION" && e.errors.invalidExtension)
59
+ return typeof e.errors.invalidExtension == "function" ? e.errors.invalidExtension(c) : e.errors.invalidExtension;
60
+ }
61
+ return r;
62
+ }, [e, p, c]), or = m(() => e != null && e.browse ? typeof e.browse == "function" ? e.browse(i) : e.browse : i === "single" ? "Browse file" : "Browse files", [e, i]), sr = m(() => e != null && e.drag ? typeof e.drag == "function" ? e.drag(i) : e.drag : i === "single" ? "or drag your file" : "or drag multiple files", [e, i]), tr = m(() => e != null && e.constraints ? e.constraints({
63
+ extensions: c,
64
+ maxSize: p,
65
+ type: i
66
+ }) : `${Y} ${i === "single" ? "file" : "files"} ${Z}`, [e, c, Y, p, Z, i]), lr = () => {
67
+ var r;
68
+ N || (r = P.current) == null || r.click();
69
+ }, B = A(() => new Set(c.map((r) => r.toLowerCase().replace(/^\./, ""))), [c]), H = m((r, a) => {
70
+ const s = a.maxSize ? a.maxSize * 1024 * 1024 : Number.POSITIVE_INFINITY, o = r.size <= s, h = r.name.toLowerCase(), ur = h.includes(".") ? h.split(".").pop() ?? "" : "", Dr = B.size === 0 || B.has(ur);
71
+ return o ? Dr ? {
72
+ file: r
73
+ } : {
74
+ file: r,
75
+ error: `The file format is incorrect. Please make sure it is one of the extensions: ${a.fileExtensionsAllowed.join(", ")}.`,
76
+ errorCode: "INVALID_EXTENSION"
77
+ } : {
78
+ file: r,
79
+ error: `File size is too large. Must be up to ${a.maxSize}MB.`,
80
+ errorCode: "SIZE_TOO_LARGE"
81
+ };
82
+ }, [B]), O = A(() => ({
83
+ maxSize: p,
84
+ fileExtensionsAllowed: c,
85
+ type: i,
86
+ maxFiles: l
87
+ }), [p, c, i, l]), T = m((r) => {
88
+ let a;
89
+ b ? a = r.map((o) => b(o, O)) : a = r.map((o) => H(o, O));
90
+ let s = a.map((o) => {
91
+ const h = o.error ? G(o.error, o.errorCode) : void 0;
92
+ return {
93
+ file: o.file,
94
+ // Don't merge external errors here - that happens in files useMemo
95
+ error: h
53
96
  };
54
97
  });
55
- return c === "single" ? o.slice(0, 1) : m && m > 0 ? o.slice(0, m) : o;
56
- }, [g, p, I, C, c, m]), d = v ? (() => {
57
- const e = Array.isArray(f) ? f : f ? [f] : [];
58
- return y(e);
59
- })() : A, S = (e) => (e / 1024).toFixed(2) + " KB", u = M(() => z || (d == null ? void 0 : d.length) >= m || c === "single" && (d == null ? void 0 : d.length) > 0, [z, d, m, c]), H = (e) => {
60
- if (!e.target.files) return;
61
- const i = Array.from(e.target.files), r = y(i);
62
- v ? t == null || t(r) : (x((o) => D ? [...o, ...r] : r), D ? t == null || t([...A, ...r]) : t == null || t(r)), e.target.value = "";
63
- }, Q = (e) => {
64
- if (!v) {
65
- const i = A.filter((r) => r.file.name !== e);
66
- x(i);
98
+ return i === "single" && s.length > 1 ? s.slice(0, 1) : (l && l > 0 && s.length > l && (s = s.map((o, h) => h < l ? o : {
99
+ ...o,
100
+ error: o.error ?? `Maximum ${l} file${l === 1 ? "" : "s"} allowed.`
101
+ })), s);
102
+ }, [i, l, b, O, G, H]), d = A(() => {
103
+ let r;
104
+ if (D) {
105
+ const a = Array.isArray(f) ? f : f ? [f] : [];
106
+ r = T(a);
107
+ } else
108
+ r = u;
109
+ return r.map((a) => ({
110
+ ...a,
111
+ // Validator errors take precedence over external errors
112
+ error: a.error ?? (I == null ? void 0 : I[a.file.name])
113
+ }));
114
+ }, [D, f, T, u, I]), cr = m((r) => C ? C(r) : (r / 1024).toFixed(2) + " KB", [C]), N = A(() => w || (d == null ? void 0 : d.length) >= l || i === "single" && (d == null ? void 0 : d.length) > 0, [w, d, l, i]), W = m((r) => {
115
+ const a = T(r);
116
+ if (D)
117
+ _ == null || _(a);
118
+ else {
119
+ const s = q ? [...u, ...a] : a;
120
+ S(s), _ == null || _(s);
121
+ }
122
+ }, [T, D, q, u, _]), gr = (r) => {
123
+ if (!r.target.files) return;
124
+ const a = Array.from(r.target.files);
125
+ W(a), r.target.value = "";
126
+ }, z = m((r) => {
127
+ if (!D) {
128
+ const a = u.filter((s) => s.file.name !== r);
129
+ S(a);
67
130
  }
68
- N == null || N(e);
69
- }, V = (e) => {
70
- var i, r;
71
- u() || (e.preventDefault(), (i = h.current) == null || i.classList.add(a.active), (r = s.onDragOver) == null || r.call(s, e));
72
- }, W = (e) => {
73
- var i, r;
74
- e.preventDefault(), (i = h.current) == null || i.classList.remove(a.active), (r = s.onDragLeave) == null || r.call(s, e);
75
- }, Z = (e) => {
76
- var o, _;
77
- if (u()) return;
78
- e.preventDefault();
79
- const i = Array.from(e.dataTransfer.files), r = y(i);
80
- v ? t == null || t(r) : (x((L) => D ? [...L, ...r] : r), D ? t == null || t([...A, ...r]) : t == null || t(r)), e.dataTransfer.clearData(), (o = h.current) == null || o.classList.remove(a.active), (_ = s.onDrop) == null || _.call(s, e);
131
+ v == null || v(r);
132
+ }, [D, u, v]), X = A(() => new Map(d.map((r) => [r.file.name, () => z(r.file.name)])), [d, z]), {
133
+ onDragOver: E,
134
+ onDragLeave: M,
135
+ onDrop: $,
136
+ ...mr
137
+ } = ir, pr = (r) => {
138
+ N || (r.preventDefault(), V || L(!0), E == null || E(r));
139
+ }, _r = (r) => {
140
+ r.preventDefault(), L(!1), M == null || M(r);
141
+ }, fr = (r) => {
142
+ if (N) return;
143
+ r.preventDefault();
144
+ const a = Array.from(r.dataTransfer.files);
145
+ W(a), r.dataTransfer.clearData(), L(!1), $ == null || $(r);
81
146
  };
82
- return /* @__PURE__ */ l("div", { className: R(a["drag-and-drop-container"], u() && a.disabled, P), ...s, children: [
83
- k && /* @__PURE__ */ l("label", { htmlFor: B, children: [
147
+ return /* @__PURE__ */ g("div", { className: J(n["drag-and-drop-container"], N && n.disabled, U), ...mr, children: [
148
+ k && /* @__PURE__ */ g("label", { htmlFor: y, children: [
84
149
  k,
85
- E && /* @__PURE__ */ n("span", { "aria-hidden": "true", className: a.required, children: "*" })
150
+ F && /* @__PURE__ */ t("span", { "aria-hidden": "true", className: n.required, children: "*" })
86
151
  ] }),
87
- /* @__PURE__ */ l("div", { ref: h, className: a["drag-and-drop-space"], onDragOver: V, onDragLeave: W, onDrop: Z, ...s, children: [
88
- /* @__PURE__ */ n(j, { type: "secondary", variant: "default", size: "sm", disabled: u(), onClick: $, children: c === "single" ? "Browse file" : "Browse files" }),
89
- /* @__PURE__ */ n("input", { id: B, ref: O, type: "file", style: {
152
+ /* @__PURE__ */ g("div", { className: J(n["drag-and-drop-space"], V && n.active), onDragOver: pr, onDragLeave: _r, onDrop: fr, children: [
153
+ /* @__PURE__ */ t(R, { type: "secondary", variant: "default", size: "sm", ...nr, disabled: N, onClick: lr, icon: ar, children: or() }),
154
+ /* @__PURE__ */ t("input", { id: y, ref: P, type: "file", style: {
90
155
  display: "none"
91
- }, multiple: c === "multiple", accept: Y, onChange: H }),
92
- /* @__PURE__ */ l("div", { className: a["drag-and-drop-space-text"], children: [
93
- /* @__PURE__ */ n("span", { className: a["drag-and-drop-space-text-message"], children: c === "single" ? "or drag your file" : "or drag multiple files" }),
94
- /* @__PURE__ */ l("span", { className: a["drag-and-drop-space-text-constraints"], children: [
95
- " ",
96
- C,
97
- " ",
98
- c === "single" ? "file" : "files",
156
+ }, multiple: i === "multiple", accept: dr, onChange: gr }),
157
+ /* @__PURE__ */ g("div", { className: n["drag-and-drop-space-text"], children: [
158
+ /* @__PURE__ */ t("span", { className: n["drag-and-drop-space-text-message"], children: sr() }),
159
+ /* @__PURE__ */ g("span", { className: n["drag-and-drop-space-text-constraints"], children: [
99
160
  " ",
100
- I
161
+ tr()
101
162
  ] })
102
163
  ] })
103
164
  ] }),
104
- (d == null ? void 0 : d.length) > 0 && /* @__PURE__ */ n("ul", { className: a["drag-and-drop-files-list"], children: d == null ? void 0 : d.map((e, i) => /* @__PURE__ */ n("li", { className: a["drag-and-drop-files-list-item"], children: /* @__PURE__ */ l("div", { className: a["drag-and-drop-files-list-item-content"], children: [
105
- /* @__PURE__ */ n("span", { className: a["drag-and-drop-files-list-item-icon-container"], children: /* @__PURE__ */ n(q, { icon: "ap-icon-document", className: a["drag-and-drop-files-list-item-icon"] }) }),
106
- /* @__PURE__ */ l("div", { className: a["drag-and-drop-files-list-item-content-data"], children: [
107
- /* @__PURE__ */ l("div", { className: a["drag-and-drop-files-list-item-text"], children: [
108
- /* @__PURE__ */ n("span", { className: a["drag-and-drop-files-list-item-name"], children: e.file.name }),
109
- /* @__PURE__ */ n("span", { className: a["drag-and-drop-files-list-item-size"], children: S(e.file.size) }),
110
- e.error && /* @__PURE__ */ l("span", { className: a["drag-and-drop-files-list-item-error"], children: [
111
- /* @__PURE__ */ n(q, { icon: "ap-icon-alert", className: a["drag-and-drop-files-list-item-icon-error"] }),
112
- e.error
165
+ (d == null ? void 0 : d.length) > 0 && /* @__PURE__ */ t("ul", { className: n["drag-and-drop-files-list"], children: d == null ? void 0 : d.map((r, a) => j ? /* @__PURE__ */ t("li", { children: j(r, a, X.get(r.file.name) ?? (() => z(r.file.name))) }, r.file.name) : /* @__PURE__ */ t("li", { className: n["drag-and-drop-files-list-item"], children: /* @__PURE__ */ g("div", { className: n["drag-and-drop-files-list-item-content"], children: [
166
+ /* @__PURE__ */ t("span", { className: n["drag-and-drop-files-list-item-icon-container"], children: /* @__PURE__ */ t(K, { icon: x, className: n["drag-and-drop-files-list-item-icon"] }) }),
167
+ /* @__PURE__ */ g("div", { className: n["drag-and-drop-files-list-item-content-data"], children: [
168
+ /* @__PURE__ */ g("div", { className: n["drag-and-drop-files-list-item-text"], children: [
169
+ /* @__PURE__ */ t("span", { className: n["drag-and-drop-files-list-item-name"], children: r.file.name }),
170
+ /* @__PURE__ */ t("span", { className: n["drag-and-drop-files-list-item-size"], children: cr(r.file.size) }),
171
+ r.error && /* @__PURE__ */ g("span", { className: n["drag-and-drop-files-list-item-error"], children: [
172
+ /* @__PURE__ */ t(K, { icon: rr, className: n["drag-and-drop-files-list-item-icon-error"] }),
173
+ r.error
113
174
  ] })
114
175
  ] }),
115
- /* @__PURE__ */ n(j, { type: "tertiary", variant: "default", size: "sm", onClick: () => Q(e.file.name), icon: "ap-icon-delete", iconClassName: a["drag-and-drop-files-list-item-icon-delete"] })
176
+ /* @__PURE__ */ t(R, { type: "tertiary", variant: "default", size: "sm", onClick: X.get(r.file.name), icon: er, iconClassName: n["drag-and-drop-files-list-item-icon-delete"], "aria-label": `Remove ${r.file.name}` })
116
177
  ] })
117
- ] }) }, i)) })
178
+ ] }) }, r.file.name)) })
118
179
  ] });
119
180
  }
120
181
  export {
121
- ne as default
182
+ Or as default
122
183
  };
@@ -0,0 +1 @@
1
+ export {};