@opengovsg/oui 0.0.50 → 0.0.51

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,11 @@
3
3
  'use strict';
4
4
 
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDropzone = require('react-dropzone');
6
7
  var $670gB$react = require('react');
7
8
  var form = require('@react-stately/form');
8
9
  var reactAria = require('react-aria');
9
10
  var reactAriaComponents = require('react-aria-components');
10
- var reactDropzone = require('react-dropzone');
11
11
  var ouiTheme = require('@opengovsg/oui-theme');
12
12
  var field = require('../field/field.cjs');
13
13
  var useControllableState = require('../hooks/use-controllable-state.cjs');
@@ -27,6 +27,8 @@ const FileDropzone = (originalProps) => {
27
27
  allowedMimeTypes = [],
28
28
  fileSizeBase = "binary",
29
29
  maxFileSize = Number.POSITIVE_INFINITY,
30
+ maxFileSizeByType = [],
31
+ fileSizeText: fileSizeTextOverride,
30
32
  minFileSize = 0,
31
33
  showFileSizeText = true,
32
34
  maxFiles = 1,
@@ -67,13 +69,48 @@ const FileDropzone = (originalProps) => {
67
69
  const slots = ouiTheme.fileDropzoneStyles(variantProps);
68
70
  const fileSizeTextId = reactAria.useId();
69
71
  const formatError = $670gB$react.useCallback(
70
- (error) => utils$1.formatErrorMessage(error, {
71
- maxFileSize,
72
- minFileSize,
73
- maxFiles,
74
- fileSizeBase
75
- }),
76
- [fileSizeBase, maxFileSize, maxFiles, minFileSize]
72
+ (error) => {
73
+ if (maxFileSizeByType.length > 0 && error.code === reactDropzone.ErrorCode.FileTooLarge) {
74
+ return error.message;
75
+ }
76
+ return utils$1.formatErrorMessage(error, {
77
+ maxFileSize,
78
+ minFileSize,
79
+ maxFiles,
80
+ fileSizeBase
81
+ });
82
+ },
83
+ [fileSizeBase, maxFileSize, maxFileSizeByType, maxFiles, minFileSize]
84
+ );
85
+ const effectiveMaxSize = $670gB$react.useMemo(() => {
86
+ if (maxFileSizeByType.length > 0) return Number.POSITIVE_INFINITY;
87
+ return maxFileSize;
88
+ }, [maxFileSize, maxFileSizeByType]);
89
+ const composedValidator = $670gB$react.useCallback(
90
+ (file) => {
91
+ const errors = [];
92
+ if (maxFileSizeByType.length > 0) {
93
+ const limit = utils$1.resolveMaxFileSize(file.type, maxFileSizeByType, maxFileSize);
94
+ if (limit !== Number.POSITIVE_INFINITY && file.size > limit) {
95
+ errors.push({
96
+ code: reactDropzone.ErrorCode.FileTooLarge,
97
+ message: `You have exceeded the size limit, please upload a file below ${utils$1.formatBytes(limit, 2, fileSizeBase)}`
98
+ });
99
+ }
100
+ }
101
+ if (validator) {
102
+ const externalErrors = validator(file);
103
+ if (externalErrors) {
104
+ if (Array.isArray(externalErrors)) {
105
+ errors.push(...externalErrors);
106
+ } else {
107
+ errors.push(externalErrors);
108
+ }
109
+ }
110
+ }
111
+ return errors.length > 0 ? errors : null;
112
+ },
113
+ [maxFileSizeByType, maxFileSize, fileSizeBase, validator]
77
114
  );
78
115
  const onDrop = $670gB$react.useCallback(
79
116
  (acceptedFiles, fileRejections) => {
@@ -108,7 +145,7 @@ const FileDropzone = (originalProps) => {
108
145
  [setRejections]
109
146
  );
110
147
  const { getInputProps, ...dropzoneState } = reactDropzone.useDropzone({
111
- validator,
148
+ validator: composedValidator,
112
149
  accept: allowedMimeTypes.reduce(
113
150
  (acc, type) => ({ ...acc, [type]: [] }),
114
151
  {}
@@ -120,12 +157,29 @@ const FileDropzone = (originalProps) => {
120
157
  // Prevent ref hijack when there is a label
121
158
  noClick: true,
122
159
  noKeyboard: true,
123
- maxSize: maxFileSize,
160
+ maxSize: effectiveMaxSize,
124
161
  minSize: minFileSize,
125
162
  maxFiles,
126
163
  multiple: maxFiles !== 1
127
164
  });
128
165
  const fileSizeText = $670gB$react.useMemo(() => {
166
+ if (!showFileSizeText) return null;
167
+ if (fileSizeTextOverride) return fileSizeTextOverride;
168
+ if (maxFileSizeByType.length > 0) {
169
+ const parts = [];
170
+ for (const rule of maxFileSizeByType) {
171
+ const sizeStr = utils$1.formatBytes(rule.maxFileSize, 2, fileSizeBase);
172
+ const label2 = rule.label ?? rule.mimeTypes.join(", ");
173
+ parts.push(`${sizeStr} for ${label2}`);
174
+ }
175
+ const notDefaultMaxFileSize2 = maxFileSize !== Number.POSITIVE_INFINITY;
176
+ if (notDefaultMaxFileSize2) {
177
+ parts.push(
178
+ `${utils$1.formatBytes(maxFileSize, 2, fileSizeBase)} for other accepted files`
179
+ );
180
+ }
181
+ return parts.length > 0 ? `Maximum file size: ${parts.join(", ")}` : null;
182
+ }
129
183
  const notDefaultMaxFileSize = maxFileSize !== Number.POSITIVE_INFINITY;
130
184
  const notDefaultMinFileSize = minFileSize !== 0;
131
185
  const shouldShow = showFileSizeText && (notDefaultMaxFileSize || notDefaultMinFileSize);
@@ -144,7 +198,7 @@ const FileDropzone = (originalProps) => {
144
198
  return `Minimum file size: ${utils$1.formatBytes(minFileSize, 2, fileSizeBase)}`;
145
199
  }
146
200
  return null;
147
- }, [maxFileSize, minFileSize, showFileSizeText, fileSizeBase]);
201
+ }, [maxFileSize, maxFileSizeByType, minFileSize, showFileSizeText, fileSizeBase, fileSizeTextOverride]);
148
202
  const triggerFileSelector = $670gB$react.useCallback(() => {
149
203
  if (isDisabled || isReadOnly) return;
150
204
  dropzoneState.inputRef.current?.click();
@@ -3,6 +3,22 @@
3
3
 
4
4
  var reactDropzone = require('react-dropzone');
5
5
 
6
+ const matchesMimeType = (fileType, pattern) => {
7
+ if (pattern === "*" || pattern === "*/*") return true;
8
+ if (pattern.endsWith("/*")) {
9
+ const prefix = pattern.slice(0, pattern.indexOf("/"));
10
+ return fileType.startsWith(prefix + "/");
11
+ }
12
+ return fileType === pattern;
13
+ };
14
+ const resolveMaxFileSize = (fileType, rules, defaultMaxSize) => {
15
+ for (const rule of rules) {
16
+ if (rule.mimeTypes.some((pattern) => matchesMimeType(fileType, pattern))) {
17
+ return rule.maxFileSize;
18
+ }
19
+ }
20
+ return defaultMaxSize;
21
+ };
6
22
  const formatBytes = (bytes, decimals = 2, base = "binary", size) => {
7
23
  const k = base === "binary" ? 1024 : 1e3;
8
24
  const dm = decimals < 0 ? 0 : decimals;
@@ -29,3 +45,5 @@ const formatErrorMessage = (error, config) => {
29
45
 
30
46
  exports.formatBytes = formatBytes;
31
47
  exports.formatErrorMessage = formatErrorMessage;
48
+ exports.matchesMimeType = matchesMimeType;
49
+ exports.resolveMaxFileSize = resolveMaxFileSize;
@@ -1,18 +1,18 @@
1
1
  "use strict";
2
2
  "use client";
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { ErrorCode, useDropzone } from 'react-dropzone';
4
5
  import { useCallback, useMemo, useEffect } from 'react';
5
6
  import { useFormValidationState } from '@react-stately/form';
6
7
  import { useField, useId } from 'react-aria';
7
8
  import { Provider, LabelContext, GroupContext, TextContext, FieldErrorContext, Group } from 'react-aria-components';
8
- import { useDropzone } from 'react-dropzone';
9
9
  import { fileDropzoneStyles, dataAttr } from '@opengovsg/oui-theme';
10
10
  import { Label, Description, FieldError } from '../field/field.js';
11
11
  import { useControllableState } from '../hooks/use-controllable-state.js';
12
12
  import { mapPropsVariants } from '../system/utils.js';
13
13
  import { FileDropzoneStyleContext, FileDropzoneStateContext, useFileDropzoneStateContext, useFileDropzoneStyleContext } from './contexts.js';
14
14
  import { FileInfo } from './file-info.js';
15
- import { formatErrorMessage, formatBytes } from './utils.js';
15
+ import { formatErrorMessage, resolveMaxFileSize, formatBytes } from './utils.js';
16
16
  import Upload from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/upload.js';
17
17
 
18
18
  const FileDropzone = (originalProps) => {
@@ -25,6 +25,8 @@ const FileDropzone = (originalProps) => {
25
25
  allowedMimeTypes = [],
26
26
  fileSizeBase = "binary",
27
27
  maxFileSize = Number.POSITIVE_INFINITY,
28
+ maxFileSizeByType = [],
29
+ fileSizeText: fileSizeTextOverride,
28
30
  minFileSize = 0,
29
31
  showFileSizeText = true,
30
32
  maxFiles = 1,
@@ -65,13 +67,48 @@ const FileDropzone = (originalProps) => {
65
67
  const slots = fileDropzoneStyles(variantProps);
66
68
  const fileSizeTextId = useId();
67
69
  const formatError = useCallback(
68
- (error) => formatErrorMessage(error, {
69
- maxFileSize,
70
- minFileSize,
71
- maxFiles,
72
- fileSizeBase
73
- }),
74
- [fileSizeBase, maxFileSize, maxFiles, minFileSize]
70
+ (error) => {
71
+ if (maxFileSizeByType.length > 0 && error.code === ErrorCode.FileTooLarge) {
72
+ return error.message;
73
+ }
74
+ return formatErrorMessage(error, {
75
+ maxFileSize,
76
+ minFileSize,
77
+ maxFiles,
78
+ fileSizeBase
79
+ });
80
+ },
81
+ [fileSizeBase, maxFileSize, maxFileSizeByType, maxFiles, minFileSize]
82
+ );
83
+ const effectiveMaxSize = useMemo(() => {
84
+ if (maxFileSizeByType.length > 0) return Number.POSITIVE_INFINITY;
85
+ return maxFileSize;
86
+ }, [maxFileSize, maxFileSizeByType]);
87
+ const composedValidator = useCallback(
88
+ (file) => {
89
+ const errors = [];
90
+ if (maxFileSizeByType.length > 0) {
91
+ const limit = resolveMaxFileSize(file.type, maxFileSizeByType, maxFileSize);
92
+ if (limit !== Number.POSITIVE_INFINITY && file.size > limit) {
93
+ errors.push({
94
+ code: ErrorCode.FileTooLarge,
95
+ message: `You have exceeded the size limit, please upload a file below ${formatBytes(limit, 2, fileSizeBase)}`
96
+ });
97
+ }
98
+ }
99
+ if (validator) {
100
+ const externalErrors = validator(file);
101
+ if (externalErrors) {
102
+ if (Array.isArray(externalErrors)) {
103
+ errors.push(...externalErrors);
104
+ } else {
105
+ errors.push(externalErrors);
106
+ }
107
+ }
108
+ }
109
+ return errors.length > 0 ? errors : null;
110
+ },
111
+ [maxFileSizeByType, maxFileSize, fileSizeBase, validator]
75
112
  );
76
113
  const onDrop = useCallback(
77
114
  (acceptedFiles, fileRejections) => {
@@ -106,7 +143,7 @@ const FileDropzone = (originalProps) => {
106
143
  [setRejections]
107
144
  );
108
145
  const { getInputProps, ...dropzoneState } = useDropzone({
109
- validator,
146
+ validator: composedValidator,
110
147
  accept: allowedMimeTypes.reduce(
111
148
  (acc, type) => ({ ...acc, [type]: [] }),
112
149
  {}
@@ -118,12 +155,29 @@ const FileDropzone = (originalProps) => {
118
155
  // Prevent ref hijack when there is a label
119
156
  noClick: true,
120
157
  noKeyboard: true,
121
- maxSize: maxFileSize,
158
+ maxSize: effectiveMaxSize,
122
159
  minSize: minFileSize,
123
160
  maxFiles,
124
161
  multiple: maxFiles !== 1
125
162
  });
126
163
  const fileSizeText = useMemo(() => {
164
+ if (!showFileSizeText) return null;
165
+ if (fileSizeTextOverride) return fileSizeTextOverride;
166
+ if (maxFileSizeByType.length > 0) {
167
+ const parts = [];
168
+ for (const rule of maxFileSizeByType) {
169
+ const sizeStr = formatBytes(rule.maxFileSize, 2, fileSizeBase);
170
+ const label2 = rule.label ?? rule.mimeTypes.join(", ");
171
+ parts.push(`${sizeStr} for ${label2}`);
172
+ }
173
+ const notDefaultMaxFileSize2 = maxFileSize !== Number.POSITIVE_INFINITY;
174
+ if (notDefaultMaxFileSize2) {
175
+ parts.push(
176
+ `${formatBytes(maxFileSize, 2, fileSizeBase)} for other accepted files`
177
+ );
178
+ }
179
+ return parts.length > 0 ? `Maximum file size: ${parts.join(", ")}` : null;
180
+ }
127
181
  const notDefaultMaxFileSize = maxFileSize !== Number.POSITIVE_INFINITY;
128
182
  const notDefaultMinFileSize = minFileSize !== 0;
129
183
  const shouldShow = showFileSizeText && (notDefaultMaxFileSize || notDefaultMinFileSize);
@@ -142,7 +196,7 @@ const FileDropzone = (originalProps) => {
142
196
  return `Minimum file size: ${formatBytes(minFileSize, 2, fileSizeBase)}`;
143
197
  }
144
198
  return null;
145
- }, [maxFileSize, minFileSize, showFileSizeText, fileSizeBase]);
199
+ }, [maxFileSize, maxFileSizeByType, minFileSize, showFileSizeText, fileSizeBase, fileSizeTextOverride]);
146
200
  const triggerFileSelector = useCallback(() => {
147
201
  if (isDisabled || isReadOnly) return;
148
202
  dropzoneState.inputRef.current?.click();
@@ -1,6 +1,22 @@
1
1
  "use strict";
2
2
  import { ErrorCode } from 'react-dropzone';
3
3
 
4
+ const matchesMimeType = (fileType, pattern) => {
5
+ if (pattern === "*" || pattern === "*/*") return true;
6
+ if (pattern.endsWith("/*")) {
7
+ const prefix = pattern.slice(0, pattern.indexOf("/"));
8
+ return fileType.startsWith(prefix + "/");
9
+ }
10
+ return fileType === pattern;
11
+ };
12
+ const resolveMaxFileSize = (fileType, rules, defaultMaxSize) => {
13
+ for (const rule of rules) {
14
+ if (rule.mimeTypes.some((pattern) => matchesMimeType(fileType, pattern))) {
15
+ return rule.maxFileSize;
16
+ }
17
+ }
18
+ return defaultMaxSize;
19
+ };
4
20
  const formatBytes = (bytes, decimals = 2, base = "binary", size) => {
5
21
  const k = base === "binary" ? 1024 : 1e3;
6
22
  const dm = decimals < 0 ? 0 : decimals;
@@ -25,4 +41,4 @@ const formatErrorMessage = (error, config) => {
25
41
  }
26
42
  };
27
43
 
28
- export { formatBytes, formatErrorMessage };
44
+ export { formatBytes, formatErrorMessage, matchesMimeType, resolveMaxFileSize };
@@ -4,6 +4,7 @@ import type { DropzoneOptions } from "react-dropzone";
4
4
  import type { FileDropzoneSlots, FileInfoDropzoneSlots, SlotsToClasses, VariantProps } from "@opengovsg/oui-theme";
5
5
  import { fileDropzoneStyles } from "@opengovsg/oui-theme";
6
6
  import type { FileItem } from "./types";
7
+ import type { MaxFileSizeRule } from "./utils";
7
8
  export interface FileItemsRenderProps {
8
9
  file: FileItem;
9
10
  removeFile: () => void;
@@ -39,6 +40,17 @@ export interface FileDropzoneProps extends Omit<AriaFieldProps, "validate">, Inp
39
40
  * @default Number.POSITIVE_INFINITY
40
41
  */
41
42
  maxFileSize?: number;
43
+ /**
44
+ * Per-MIME-type maximum file size rules.
45
+ * Rules are matched in order; first match wins.
46
+ * Falls back to `maxFileSize` for unmatched types.
47
+ */
48
+ maxFileSizeByType?: MaxFileSizeRule[];
49
+ /**
50
+ * Custom file size text to display below the dropzone.
51
+ * If provided, overrides the auto-generated text.
52
+ */
53
+ fileSizeText?: string;
42
54
  /**
43
55
  * Minimum upload size of each file allowed in bytes.
44
56
  * @default 0
@@ -1 +1 @@
1
- {"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/file-dropzone.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,gBAAgB,CAAA;AAe/E,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACb,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAY,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAavC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,iBACf,SAAQ,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,EACtC,SAAS,EACT,UAAU,CAAC,QAAQ,EAAE,CAAC,EACtB,YAAY,CAAC,OAAO,kBAAkB,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAE9B,SAAS,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAA;IAC9C,cAAc,CAAC,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAA;IACtD,sCAAsC;IACtC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,wCAAwC;IACxC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAA;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,KAAK,CAAC,SAAS,CAAA;IAE5D;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAE7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAA;CACxC;AAED,eAAO,MAAM,YAAY,kBAAmB,iBAAiB,4CA4Q5D,CAAA"}
1
+ {"version":3,"file":"file-dropzone.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/file-dropzone.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,gBAAgB,CAAA;AAgB/E,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACb,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAY,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAWvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAG9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,UAAU,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,iBACf,SAAQ,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,EACtC,SAAS,EACT,UAAU,CAAC,QAAQ,EAAE,CAAC,EACtB,YAAY,CAAC,OAAO,kBAAkB,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAE9B,SAAS,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAA;IAC9C,cAAc,CAAC,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAA;IACtD,sCAAsC;IACtC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,wCAAwC;IACxC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAA;IACrC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAE3B;;OAEG;IACH,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAA;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,KAAK,CAAC,SAAS,CAAA;IAE5D;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAE7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAAA;CACxC;AAED,eAAO,MAAM,YAAY,kBAAmB,iBAAiB,4CAkV5D,CAAA"}
@@ -4,4 +4,5 @@ export type { FileInfoProps } from "./file-info";
4
4
  export type { FileDropzoneProps, FileItemsRenderProps } from "./file-dropzone";
5
5
  export type { FileItem } from "./types";
6
6
  export { formatBytes, formatErrorMessage } from "./utils";
7
+ export type { MaxFileSizeRule } from "./utils";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE9E,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE9E,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAEzD,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
@@ -1,4 +1,22 @@
1
1
  import type { FileRejection } from "react-dropzone";
2
+ export interface MaxFileSizeRule {
3
+ /** MIME types this rule applies to (e.g. ["application/zip"]) */
4
+ mimeTypes: string[];
5
+ /** Max file size in bytes for these types */
6
+ maxFileSize: number;
7
+ /** Display label for fileSizeText (e.g. ".zip files"). Falls back to raw mime type string. */
8
+ label?: string;
9
+ }
10
+ /**
11
+ * Check if a file's MIME type matches a pattern.
12
+ * Supports exact matches ("application/zip") and wildcards ("image/*").
13
+ */
14
+ export declare const matchesMimeType: (fileType: string, pattern: string) => boolean;
15
+ /**
16
+ * Resolve the effective max file size for a given file type.
17
+ * Iterates rules in order; first match wins. Falls back to defaultMaxSize.
18
+ */
19
+ export declare const resolveMaxFileSize: (fileType: string, rules: MaxFileSizeRule[], defaultMaxSize: number) => number;
2
20
  export declare const formatBytes: (bytes: number, decimals?: number, base?: "binary" | "decimal", size?: "bytes" | "KB" | "MB" | "GB" | "TB" | "PB" | "EB" | "ZB" | "YB") => string;
3
21
  export declare const formatErrorMessage: (error: FileRejection["errors"][number], config: {
4
22
  maxFileSize: number;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,eAAO,MAAM,WAAW,UACf,MAAM,4BAEP,QAAQ,GAAG,SAAS,SACnB,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,WAavE,CAAA;AAED,eAAO,MAAM,kBAAkB,UACtB,aAAa,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAC9B;IACN,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,QAAQ,GAAG,SAAS,CAAA;CACnC,WAgBF,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/file-dropzone/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,8FAA8F;IAC9F,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,aAChB,MAAM,WACP,MAAM,KACd,OAOF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,aACnB,MAAM,SACT,eAAe,EAAE,kBACR,MAAM,KACrB,MAOF,CAAA;AAED,eAAO,MAAM,WAAW,UACf,MAAM,4BAEP,QAAQ,GAAG,SAAS,SACnB,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,WAavE,CAAA;AAED,eAAO,MAAM,kBAAkB,UACtB,aAAa,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAC9B;IACN,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,QAAQ,GAAG,SAAS,CAAA;CACnC,WAgBF,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengovsg/oui",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "sideEffects": false,
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  "storybook": "10.1.10",
51
51
  "tsx": "^4.21.0",
52
52
  "typescript": "5.7.3",
53
- "@opengovsg/oui-theme": "0.0.50",
54
- "@oui/eslint-config": "0.0.0",
55
53
  "@oui/chromatic": "0.0.0",
56
54
  "@oui/prettier-config": "0.0.0",
55
+ "@oui/eslint-config": "0.0.0",
56
+ "@opengovsg/oui-theme": "0.0.51",
57
57
  "@oui/typescript-config": "0.0.0"
58
58
  },
59
59
  "dependencies": {
@@ -90,7 +90,7 @@
90
90
  "motion": ">=11.12.0 || >=12.0.0-alpha.1",
91
91
  "react": ">= 18",
92
92
  "react-aria-components": "^1.14.0",
93
- "@opengovsg/oui-theme": "0.0.50"
93
+ "@opengovsg/oui-theme": "0.0.51"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "tsx ../../tooling/build-scripts/main.ts --dts --clean",