@opengovsg/oui 0.0.0-snapshot-20251103063040 → 0.0.0-snapshot-20251203091804

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.
Files changed (184) hide show
  1. package/dist/cjs/badge/badge.cjs +4 -4
  2. package/dist/cjs/badge/use-badge.cjs +6 -6
  3. package/dist/cjs/banner/banner.cjs +4 -4
  4. package/dist/cjs/button/button.cjs +3 -3
  5. package/dist/cjs/calendar/calendar-bottom-content.cjs +3 -3
  6. package/dist/cjs/calendar/calendar-header.cjs +2 -2
  7. package/dist/cjs/calendar/calendar-month-day-selector.cjs +4 -3
  8. package/dist/cjs/calendar/calendar.cjs +4 -4
  9. package/dist/cjs/calendar/hooks/use-calendar-selectors.cjs +4 -4
  10. package/dist/cjs/calendar/utils.cjs +3 -3
  11. package/dist/cjs/combo-box/combo-box-fuzzy.cjs +6 -6
  12. package/dist/cjs/combo-box/combo-box-item.cjs +2 -2
  13. package/dist/cjs/combo-box/combo-box.cjs +3 -3
  14. package/dist/cjs/date-field/date-field.cjs +15 -6
  15. package/dist/cjs/date-picker/date-picker.cjs +39 -13
  16. package/dist/cjs/date-range-picker/date-range-picker.cjs +4 -4
  17. package/dist/cjs/field/field.cjs +20 -6
  18. package/dist/cjs/field/index.cjs +1 -0
  19. package/dist/cjs/file-dropzone/contexts.cjs +18 -0
  20. package/dist/cjs/file-dropzone/file-dropzone.cjs +311 -0
  21. package/dist/cjs/file-dropzone/file-info.cjs +146 -0
  22. package/dist/cjs/file-dropzone/index.cjs +13 -0
  23. package/dist/cjs/file-dropzone/types.cjs +3 -0
  24. package/dist/cjs/file-dropzone/utils.cjs +31 -0
  25. package/dist/cjs/govt-banner/govt-banner.cjs +3 -3
  26. package/dist/cjs/hooks/index.cjs +2 -0
  27. package/dist/cjs/hooks/use-callback-ref.cjs +4 -4
  28. package/dist/cjs/hooks/use-controllable-state.cjs +2 -2
  29. package/dist/cjs/hooks/use-draggable.cjs +88 -0
  30. package/dist/cjs/index.cjs +47 -22
  31. package/dist/cjs/input/input.cjs +2 -2
  32. package/dist/cjs/menu/menu.cjs +6 -6
  33. package/dist/cjs/modal/i18n.cjs +19 -0
  34. package/dist/cjs/modal/index.cjs +19 -0
  35. package/dist/cjs/modal/modal-body.cjs +26 -0
  36. package/dist/cjs/modal/modal-content.cjs +54 -0
  37. package/dist/cjs/modal/modal-footer.cjs +27 -0
  38. package/dist/cjs/modal/modal-header.cjs +25 -0
  39. package/dist/cjs/modal/modal-variant-context.cjs +13 -0
  40. package/dist/cjs/modal/modal.cjs +66 -0
  41. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/Icon.cjs +4 -4
  42. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/createLucideIcon.cjs +3 -3
  43. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/plus.cjs +22 -0
  44. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/trash-2.cjs +25 -0
  45. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/upload.cjs +23 -0
  46. package/dist/cjs/number-field/index.cjs +8 -0
  47. package/dist/cjs/number-field/number-field.cjs +136 -0
  48. package/dist/cjs/pagination/hooks/use-pagination.cjs +7 -7
  49. package/dist/cjs/pagination/pagination.cjs +6 -6
  50. package/dist/cjs/pagination/use-pagination-item.cjs +7 -8
  51. package/dist/cjs/pagination/use-pagination.cjs +8 -8
  52. package/dist/cjs/range-calendar/range-calendar.cjs +7 -7
  53. package/dist/cjs/ripple/use-ripple.cjs +4 -4
  54. package/dist/cjs/select/select.cjs +3 -3
  55. package/dist/cjs/spinner/use-spinner.cjs +3 -3
  56. package/dist/cjs/system/react-utils/context.cjs +3 -3
  57. package/dist/cjs/system/react-utils/refs.cjs +3 -3
  58. package/dist/cjs/system/utils.cjs +3 -3
  59. package/dist/cjs/tabs/tabs.cjs +2 -2
  60. package/dist/cjs/tag-field/tag-field-item.cjs +2 -2
  61. package/dist/cjs/tag-field/tag-field-list.cjs +4 -4
  62. package/dist/cjs/tag-field/tag-field-root.cjs +14 -14
  63. package/dist/cjs/tag-field/tag-field-state-context.cjs +2 -2
  64. package/dist/cjs/tag-field/tag-field-tag-list.cjs +3 -3
  65. package/dist/cjs/tag-field/tag-field-trigger.cjs +2 -2
  66. package/dist/cjs/tag-field/tag-field.cjs +2 -2
  67. package/dist/cjs/tag-field/use-tag-field-state.cjs +6 -6
  68. package/dist/cjs/tag-field/use-tag-field.cjs +4 -4
  69. package/dist/cjs/text-area/text-area.cjs +2 -2
  70. package/dist/cjs/toggle/toggle.cjs +3 -3
  71. package/dist/esm/banner/banner.js +1 -1
  72. package/dist/esm/calendar/calendar-month-day-selector.js +2 -1
  73. package/dist/esm/date-field/date-field.js +12 -3
  74. package/dist/esm/date-picker/date-picker.js +37 -11
  75. package/dist/esm/date-range-picker/date-range-picker.js +1 -1
  76. package/dist/esm/field/field.js +20 -7
  77. package/dist/esm/field/index.js +1 -1
  78. package/dist/esm/file-dropzone/contexts.js +13 -0
  79. package/dist/esm/file-dropzone/file-dropzone.js +309 -0
  80. package/dist/esm/file-dropzone/file-info.js +144 -0
  81. package/dist/esm/file-dropzone/index.js +4 -0
  82. package/dist/esm/file-dropzone/types.js +1 -0
  83. package/dist/esm/file-dropzone/utils.js +28 -0
  84. package/dist/esm/hooks/index.js +1 -0
  85. package/dist/esm/hooks/use-draggable.js +86 -0
  86. package/dist/esm/index.js +19 -8
  87. package/dist/esm/modal/i18n.js +17 -0
  88. package/dist/esm/modal/index.js +7 -0
  89. package/dist/esm/modal/modal-body.js +24 -0
  90. package/dist/esm/modal/modal-content.js +52 -0
  91. package/dist/esm/modal/modal-footer.js +25 -0
  92. package/dist/esm/modal/modal-header.js +23 -0
  93. package/dist/esm/modal/modal-variant-context.js +10 -0
  94. package/dist/esm/modal/modal.js +64 -0
  95. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/plus.js +17 -0
  96. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/trash-2.js +20 -0
  97. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/upload.js +18 -0
  98. package/dist/esm/number-field/index.js +2 -0
  99. package/dist/esm/number-field/number-field.js +134 -0
  100. package/dist/esm/pagination/use-pagination-item.js +5 -6
  101. package/dist/esm/range-calendar/range-calendar.js +1 -1
  102. package/dist/esm/select/select.js +1 -1
  103. package/dist/types/badge/use-badge.d.ts +12 -12
  104. package/dist/types/calendar/calendar-month-day-selector.d.ts.map +1 -1
  105. package/dist/types/date-field/date-field.d.ts +1 -0
  106. package/dist/types/date-field/date-field.d.ts.map +1 -1
  107. package/dist/types/date-picker/date-picker.d.ts +5 -2
  108. package/dist/types/date-picker/date-picker.d.ts.map +1 -1
  109. package/dist/types/field/field.d.ts +4 -1
  110. package/dist/types/field/field.d.ts.map +1 -1
  111. package/dist/types/file-dropzone/contexts.d.ts +4 -0
  112. package/dist/types/file-dropzone/contexts.d.ts.map +1 -0
  113. package/dist/types/file-dropzone/file-dropzone.d.ts +82 -0
  114. package/dist/types/file-dropzone/file-dropzone.d.ts.map +1 -0
  115. package/dist/types/file-dropzone/file-info.d.ts +9 -0
  116. package/dist/types/file-dropzone/file-info.d.ts.map +1 -0
  117. package/dist/types/file-dropzone/index.d.ts +7 -0
  118. package/dist/types/file-dropzone/index.d.ts.map +1 -0
  119. package/dist/types/file-dropzone/types.d.ts +24 -0
  120. package/dist/types/file-dropzone/types.d.ts.map +1 -0
  121. package/dist/types/file-dropzone/utils.d.ts +8 -0
  122. package/dist/types/file-dropzone/utils.d.ts.map +1 -0
  123. package/dist/types/hooks/index.d.ts +4 -1
  124. package/dist/types/hooks/index.d.ts.map +1 -1
  125. package/dist/types/hooks/use-draggable.d.ts +24 -0
  126. package/dist/types/hooks/use-draggable.d.ts.map +1 -0
  127. package/dist/types/index.d.mts +3 -0
  128. package/dist/types/index.d.ts +3 -0
  129. package/dist/types/index.d.ts.map +1 -1
  130. package/dist/types/menu/menu.d.ts.map +1 -1
  131. package/dist/types/modal/i18n.d.ts +3 -0
  132. package/dist/types/modal/i18n.d.ts.map +1 -0
  133. package/dist/types/modal/index.d.ts +12 -0
  134. package/dist/types/modal/index.d.ts.map +1 -0
  135. package/dist/types/modal/modal-body.d.ts +5 -0
  136. package/dist/types/modal/modal-body.d.ts.map +1 -0
  137. package/dist/types/modal/modal-content.d.ts +10 -0
  138. package/dist/types/modal/modal-content.d.ts.map +1 -0
  139. package/dist/types/modal/modal-footer.d.ts +5 -0
  140. package/dist/types/modal/modal-footer.d.ts.map +1 -0
  141. package/dist/types/modal/modal-header.d.ts +5 -0
  142. package/dist/types/modal/modal-header.d.ts.map +1 -0
  143. package/dist/types/modal/modal-variant-context.d.ts +8 -0
  144. package/dist/types/modal/modal-variant-context.d.ts.map +1 -0
  145. package/dist/types/modal/modal.d.ts +8 -0
  146. package/dist/types/modal/modal.d.ts.map +1 -0
  147. package/dist/types/number-field/index.d.ts +3 -0
  148. package/dist/types/number-field/index.d.ts.map +1 -0
  149. package/dist/types/number-field/number-field.d.ts +24 -0
  150. package/dist/types/number-field/number-field.d.ts.map +1 -0
  151. package/dist/types/pagination/use-pagination.d.ts +36 -36
  152. package/package.json +14 -10
  153. package/dist/cjs/node_modules/.pnpm/@react-aria_focus@3.20.5_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/focus/dist/useFocusRing.cjs +0 -45
  154. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/context.cjs +0 -21
  155. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/textSelection.cjs +0 -72
  156. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocus.cjs +0 -60
  157. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocusVisible.cjs +0 -210
  158. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocusWithin.cjs +0 -100
  159. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useHover.cjs +0 -152
  160. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/usePress.cjs +0 -676
  161. package/dist/cjs/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/utils.cjs +0 -160
  162. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_check_private_redeclaration.cjs +0 -9
  163. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_apply_descriptor_get.cjs +0 -9
  164. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_apply_descriptor_set.cjs +0 -16
  165. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_extract_field_descriptor.cjs +0 -9
  166. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_get.cjs +0 -11
  167. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_init.cjs +0 -10
  168. package/dist/cjs/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_set.cjs +0 -12
  169. package/dist/esm/node_modules/.pnpm/@react-aria_focus@3.20.5_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/focus/dist/useFocusRing.js +0 -43
  170. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/context.js +0 -19
  171. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/textSelection.js +0 -69
  172. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocus.js +0 -58
  173. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocusVisible.js +0 -205
  174. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useFocusWithin.js +0 -98
  175. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/useHover.js +0 -150
  176. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/usePress.js +0 -674
  177. package/dist/esm/node_modules/.pnpm/@react-aria_interactions@3.25.3_react-dom@19.0.0_react@19.0.0__react@19.0.0/node_modules/@react-aria/interactions/dist/utils.js +0 -155
  178. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_check_private_redeclaration.js +0 -7
  179. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_apply_descriptor_get.js +0 -7
  180. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_apply_descriptor_set.js +0 -14
  181. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_extract_field_descriptor.js +0 -7
  182. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_get.js +0 -9
  183. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_init.js +0 -8
  184. package/dist/esm/node_modules/.pnpm/@swc_helpers@0.5.17/node_modules/@swc/helpers/esm/_class_private_field_set.js +0 -10
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ "use client";
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { useCallback, useMemo, useEffect } from 'react';
5
+ import { useFormValidationState } from '@react-stately/form';
6
+ import { useField, useId } from 'react-aria';
7
+ import { Provider, LabelContext, GroupContext, TextContext, FieldErrorContext, Group } from 'react-aria-components';
8
+ import { useDropzone } from 'react-dropzone';
9
+ import { fileDropzoneStyles, dataAttr } from '@opengovsg/oui-theme';
10
+ import { Label, Description, FieldError } from '../field/field.js';
11
+ import { useControllableState } from '../hooks/use-controllable-state.js';
12
+ import { mapPropsVariants } from '../system/utils.js';
13
+ import { FileDropzoneStyleContext, FileDropzoneStateContext, useFileDropzoneStateContext, useFileDropzoneStyleContext } from './contexts.js';
14
+ import { FileInfo } from './file-info.js';
15
+ import { formatErrorMessage, formatBytes } from './utils.js';
16
+ import Upload from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/upload.js';
17
+
18
+ const FileDropzone = (originalProps) => {
19
+ const [props, variantProps] = mapPropsVariants(
20
+ originalProps,
21
+ fileDropzoneStyles.variantKeys
22
+ );
23
+ const {
24
+ name,
25
+ allowedMimeTypes = [],
26
+ maxFileSize = Number.POSITIVE_INFINITY,
27
+ minFileSize = 0,
28
+ showFileSizeText = true,
29
+ maxFiles = 1,
30
+ isDisabled,
31
+ isReadOnly,
32
+ classNames,
33
+ itemClassNames,
34
+ validator,
35
+ showRejectedFiles,
36
+ onError,
37
+ errorMessage,
38
+ label,
39
+ description,
40
+ children,
41
+ hideDropzoneOnValue = maxFiles === 1,
42
+ imagePreview = "small"
43
+ } = props;
44
+ const [value, setValue] = useControllableState({
45
+ value: props.value,
46
+ defaultValue: props.defaultValue || [],
47
+ onChange: props.onChange
48
+ });
49
+ const [rejections, setRejections] = useControllableState({
50
+ value: props.rejections,
51
+ defaultValue: [],
52
+ onChange: props.onRejection
53
+ });
54
+ const validationState = useFormValidationState({
55
+ ...props,
56
+ value
57
+ });
58
+ const { isInvalid, validationErrors, validationDetails } = validationState.displayValidation;
59
+ const { labelProps, fieldProps, descriptionProps, errorMessageProps } = useField({
60
+ ...props,
61
+ isInvalid,
62
+ errorMessage: props.errorMessage || validationErrors
63
+ });
64
+ const slots = fileDropzoneStyles(variantProps);
65
+ const fileSizeTextId = useId();
66
+ const formatError = useCallback(
67
+ (error) => formatErrorMessage(error, {
68
+ maxFileSize,
69
+ minFileSize,
70
+ maxFiles
71
+ }),
72
+ [maxFileSize, maxFiles, minFileSize]
73
+ );
74
+ const onDrop = useCallback(
75
+ (acceptedFiles, fileRejections) => {
76
+ const files = acceptedFiles;
77
+ if (showRejectedFiles) {
78
+ const invalidFiles = fileRejections.map(({ file, errors }) => {
79
+ file.errors = errors;
80
+ return file;
81
+ });
82
+ setRejections(invalidFiles);
83
+ }
84
+ setValue(files);
85
+ if (onError && fileRejections.length > 0) {
86
+ const firstError = fileRejections[0].errors[0];
87
+ onError(formatError(firstError));
88
+ }
89
+ },
90
+ [formatError, onError, setRejections, setValue, showRejectedFiles]
91
+ );
92
+ const handleRemoveFile = useCallback(
93
+ (fileName) => {
94
+ setValue((files) => files.filter((file) => file.name !== fileName));
95
+ },
96
+ [setValue]
97
+ );
98
+ const handleRemoveRejection = useCallback(
99
+ (fileName) => {
100
+ setRejections(
101
+ (rejections2) => rejections2.filter((file) => file.name !== fileName)
102
+ );
103
+ },
104
+ [setRejections]
105
+ );
106
+ const { getInputProps, ...dropzoneState } = useDropzone({
107
+ validator,
108
+ accept: allowedMimeTypes.reduce(
109
+ (acc, type) => ({ ...acc, [type]: [] }),
110
+ {}
111
+ ),
112
+ onError: (e) => onError?.(e.message),
113
+ onDrop,
114
+ disabled: isDisabled,
115
+ noDrag: isReadOnly,
116
+ // Prevent ref hijack when there is a label
117
+ noClick: true,
118
+ noKeyboard: true,
119
+ maxSize: maxFileSize,
120
+ minSize: minFileSize,
121
+ maxFiles,
122
+ multiple: maxFiles !== 1
123
+ });
124
+ const fileSizeText = useMemo(() => {
125
+ const notDefaultMaxFileSize = maxFileSize !== Number.POSITIVE_INFINITY;
126
+ const notDefaultMinFileSize = minFileSize !== 0;
127
+ const shouldShow = showFileSizeText && (notDefaultMaxFileSize || notDefaultMinFileSize);
128
+ if (!shouldShow) return null;
129
+ if (notDefaultMaxFileSize && notDefaultMinFileSize) {
130
+ return `File size must be between ${formatBytes(minFileSize, 2)} and ${formatBytes(
131
+ maxFileSize,
132
+ 2
133
+ )}`;
134
+ }
135
+ if (notDefaultMaxFileSize) {
136
+ return `Maximum file size: ${formatBytes(maxFileSize, 2)}`;
137
+ }
138
+ if (notDefaultMinFileSize) {
139
+ return `Minimum file size: ${formatBytes(minFileSize, 2)}`;
140
+ }
141
+ return null;
142
+ }, [maxFileSize, minFileSize, showFileSizeText]);
143
+ const triggerFileSelector = useCallback(() => {
144
+ if (isDisabled || isReadOnly) return;
145
+ dropzoneState.inputRef.current?.click();
146
+ }, [dropzoneState, isDisabled, isReadOnly]);
147
+ useEffect(() => {
148
+ if (value.length <= maxFiles) {
149
+ let changed = false;
150
+ const newFiles = value.map((file) => {
151
+ if (file.errors?.some((e) => e.code === "too-many-files")) {
152
+ file.errors = file.errors.filter((e) => e.code !== "too-many-files");
153
+ changed = true;
154
+ }
155
+ return file;
156
+ });
157
+ if (changed) {
158
+ setValue(newFiles);
159
+ }
160
+ }
161
+ }, [maxFiles, setValue, value]);
162
+ const inputProps = useMemo(() => {
163
+ const inputProps2 = { ...fieldProps, name };
164
+ if (fileSizeText) {
165
+ inputProps2["aria-describedby"] = inputProps2["aria-describedby"] ? `${inputProps2["aria-describedby"]} ${fileSizeTextId}` : fileSizeTextId;
166
+ }
167
+ return getInputProps(inputProps2);
168
+ }, [fieldProps, getInputProps, fileSizeTextId, name, fileSizeText]);
169
+ const showDropzone = useMemo(() => {
170
+ if (hideDropzoneOnValue) {
171
+ return value.length < maxFiles;
172
+ }
173
+ return true;
174
+ }, [hideDropzoneOnValue, maxFiles, value.length]);
175
+ return /* @__PURE__ */ jsx(
176
+ Provider,
177
+ {
178
+ values: [
179
+ [
180
+ FileDropzoneStyleContext,
181
+ { slots, classNames, itemClassNames, ...variantProps }
182
+ ],
183
+ [
184
+ FileDropzoneStateContext,
185
+ {
186
+ isDisabled,
187
+ isReadOnly,
188
+ maxFiles,
189
+ maxFileSize,
190
+ showDropzone,
191
+ files: value,
192
+ handleRemoveFile,
193
+ handleRemoveRejection,
194
+ formatError,
195
+ inputProps,
196
+ triggerFileSelector,
197
+ ...dropzoneState
198
+ }
199
+ ],
200
+ [LabelContext, labelProps],
201
+ [
202
+ GroupContext,
203
+ {
204
+ role: "presentation",
205
+ isInvalid,
206
+ isDisabled: props.isDisabled || false
207
+ }
208
+ ],
209
+ [
210
+ TextContext,
211
+ {
212
+ slots: {
213
+ fileSize: {},
214
+ description: descriptionProps,
215
+ errorMessage: errorMessageProps
216
+ }
217
+ }
218
+ ],
219
+ [FieldErrorContext, { isInvalid, validationErrors, validationDetails }]
220
+ ],
221
+ children: /* @__PURE__ */ jsxs(Group, { className: slots.base({ className: classNames?.base }), children: [
222
+ label && /* @__PURE__ */ jsx(Label, { size: variantProps.size, children: label }),
223
+ showDropzone && /* @__PURE__ */ jsx(FileDropzoneDropzone, {}),
224
+ value.map((file) => {
225
+ if (typeof children === "function") {
226
+ return children({
227
+ file,
228
+ removeFile: () => handleRemoveFile(file.name)
229
+ });
230
+ }
231
+ return /* @__PURE__ */ jsx(FileInfo, { imagePreview, file }, file.name);
232
+ }),
233
+ rejections.length >= 1 && rejections.map((rj) => /* @__PURE__ */ jsx(FileInfo, { imagePreview, file: rj }, rj.name)),
234
+ fileSizeText && /* @__PURE__ */ jsx(
235
+ Description,
236
+ {
237
+ size: variantProps.size,
238
+ id: fileSizeTextId,
239
+ slot: "fileSize",
240
+ children: fileSizeText
241
+ }
242
+ ),
243
+ description && /* @__PURE__ */ jsx(Description, { size: variantProps.size, children: description }),
244
+ errorMessage && /* @__PURE__ */ jsx(FieldError, { size: variantProps.size, children: errorMessage })
245
+ ] })
246
+ }
247
+ );
248
+ };
249
+ const FileDropzoneDropzone = () => {
250
+ const {
251
+ maxFiles,
252
+ getRootProps,
253
+ inputProps,
254
+ triggerFileSelector,
255
+ isDisabled,
256
+ isDragActive
257
+ } = useFileDropzoneStateContext();
258
+ const { slots, classNames } = useFileDropzoneStyleContext();
259
+ return /* @__PURE__ */ jsxs(
260
+ "div",
261
+ {
262
+ ...getRootProps({
263
+ "aria-disabled": isDisabled,
264
+ className: slots.group({
265
+ className: classNames?.group
266
+ })
267
+ }),
268
+ tabIndex: isDisabled ? void 0 : 0,
269
+ onClick: triggerFileSelector,
270
+ onKeyDown: (e) => {
271
+ if (e.key === "Enter" || e.key === " ") {
272
+ e.preventDefault();
273
+ triggerFileSelector();
274
+ }
275
+ },
276
+ children: [
277
+ /* @__PURE__ */ jsx("input", { ...inputProps }),
278
+ /* @__PURE__ */ jsxs(
279
+ "div",
280
+ {
281
+ "data-dragging": dataAttr(isDragActive),
282
+ className: slots.dropzone({ className: classNames?.dropzone }),
283
+ children: [
284
+ /* @__PURE__ */ jsx(Upload, { className: slots.icon({ className: classNames?.icon }) }),
285
+ /* @__PURE__ */ jsxs("div", { className: slots.text({ className: classNames?.text }), children: [
286
+ /* @__PURE__ */ jsxs(
287
+ "span",
288
+ {
289
+ className: slots.dropzoneHighlight({
290
+ className: classNames?.dropzoneHighlight
291
+ }),
292
+ children: [
293
+ "Choose ",
294
+ maxFiles === 1 ? `file` : `files`
295
+ ]
296
+ }
297
+ ),
298
+ " ",
299
+ "or drag and drop here"
300
+ ] })
301
+ ]
302
+ }
303
+ )
304
+ ]
305
+ }
306
+ );
307
+ };
308
+
309
+ export { FileDropzone };
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ "use client";
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { useState, useEffect } from 'react';
5
+ import { fileInfoDropzoneStyles, cn } from '@opengovsg/oui-theme';
6
+ import { Button } from '../button/button.js';
7
+ import { useFileDropzoneStateContext, useFileDropzoneStyleContext } from './contexts.js';
8
+ import { formatBytes } from './utils.js';
9
+ import Trash2 from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/trash-2.js';
10
+
11
+ const FileInfo = ({ file, imagePreview, classNames }) => {
12
+ const {
13
+ handleRemoveFile,
14
+ handleRemoveRejection,
15
+ formatError,
16
+ isDisabled,
17
+ isReadOnly
18
+ } = useFileDropzoneStateContext();
19
+ const { size, variant, itemClassNames } = useFileDropzoneStyleContext();
20
+ const readableFileSize = formatBytes(file.size, 2);
21
+ const styles = fileInfoDropzoneStyles({
22
+ size,
23
+ variant,
24
+ imagePreview: imagePreview ?? void 0
25
+ });
26
+ const [previewSrc, setPreviewSrc] = useState("");
27
+ useEffect(() => {
28
+ let objectUrl = "";
29
+ if (file.type.startsWith("image/")) {
30
+ objectUrl = URL.createObjectURL(file);
31
+ setPreviewSrc(objectUrl);
32
+ }
33
+ return () => URL.revokeObjectURL(objectUrl);
34
+ }, [file]);
35
+ return /* @__PURE__ */ jsxs(
36
+ "div",
37
+ {
38
+ className: styles.base({
39
+ className: cn(itemClassNames?.base, classNames?.base)
40
+ }),
41
+ children: [
42
+ /* @__PURE__ */ jsxs("div", { className: "sr-only", children: [
43
+ "File attached: ",
44
+ file.name,
45
+ " with file size of ",
46
+ readableFileSize
47
+ ] }),
48
+ imagePreview && previewSrc && /* @__PURE__ */ jsx(
49
+ "div",
50
+ {
51
+ className: styles.imageContainer({
52
+ className: cn(
53
+ itemClassNames?.imageContainer,
54
+ classNames?.imageContainer
55
+ )
56
+ }),
57
+ children: /* @__PURE__ */ jsx(
58
+ "img",
59
+ {
60
+ src: previewSrc,
61
+ alt: `Image preview of uploaded file: ${file.name}`,
62
+ className: styles.image({
63
+ className: cn(itemClassNames?.image, classNames?.image)
64
+ })
65
+ }
66
+ )
67
+ }
68
+ ),
69
+ /* @__PURE__ */ jsxs(
70
+ "div",
71
+ {
72
+ className: styles.container({
73
+ className: cn(itemClassNames?.container, classNames?.container)
74
+ }),
75
+ children: [
76
+ /* @__PURE__ */ jsxs(
77
+ "div",
78
+ {
79
+ className: styles.textContainer({
80
+ className: cn(
81
+ itemClassNames?.textContainer,
82
+ classNames?.textContainer
83
+ )
84
+ }),
85
+ children: [
86
+ /* @__PURE__ */ jsx(
87
+ "p",
88
+ {
89
+ title: file.name,
90
+ className: styles.name({
91
+ className: cn(itemClassNames?.name, classNames?.name)
92
+ }),
93
+ children: file.name
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsx(
97
+ "p",
98
+ {
99
+ className: styles.size({
100
+ className: cn(itemClassNames?.size, classNames?.size)
101
+ }),
102
+ children: readableFileSize
103
+ }
104
+ ),
105
+ file.errors?.length && /* @__PURE__ */ jsx(
106
+ "p",
107
+ {
108
+ className: styles.error({
109
+ className: cn(itemClassNames?.error, classNames?.error)
110
+ }),
111
+ children: file.errors.map(formatError).join(", ")
112
+ }
113
+ )
114
+ ]
115
+ }
116
+ ),
117
+ /* @__PURE__ */ jsx(
118
+ Button,
119
+ {
120
+ isDisabled: isDisabled || isReadOnly,
121
+ isIconOnly: !file.errors?.length,
122
+ size: size === "md" ? "md" : "xs",
123
+ variant: "clear",
124
+ color: file.errors?.length ? "main" : "critical",
125
+ "aria-label": "Remove file",
126
+ className: styles.actionButton({
127
+ className: cn(
128
+ itemClassNames?.actionButton,
129
+ classNames?.actionButton
130
+ )
131
+ }),
132
+ onPress: () => file.errors?.length ? handleRemoveRejection(file.name) : handleRemoveFile(file.name),
133
+ children: file.errors?.length ? "Dismiss" : /* @__PURE__ */ jsx(Trash2, {})
134
+ }
135
+ )
136
+ ]
137
+ }
138
+ )
139
+ ]
140
+ }
141
+ );
142
+ };
143
+
144
+ export { FileInfo };
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ export { FileDropzone } from './file-dropzone.js';
3
+ export { FileInfo } from './file-info.js';
4
+ export { formatBytes, formatErrorMessage } from './utils.js';
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ import { ErrorCode } from 'react-dropzone';
3
+
4
+ const formatBytes = (bytes, decimals = 2, size) => {
5
+ const k = 1e3;
6
+ const dm = decimals < 0 ? 0 : decimals;
7
+ const sizes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
8
+ if (bytes === 0 || bytes === void 0)
9
+ return size !== void 0 ? `0 ${size}` : "0 bytes";
10
+ const i = size !== void 0 ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k));
11
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
12
+ };
13
+ const formatErrorMessage = (error, config) => {
14
+ const { maxFileSize, minFileSize, maxFiles } = config;
15
+ switch (error.code) {
16
+ case ErrorCode.FileTooLarge:
17
+ return `You have exceeded the size limit, please upload a file below ${formatBytes(maxFileSize, 2)}`;
18
+ case ErrorCode.FileTooSmall:
19
+ return `Please upload a file above ${formatBytes(minFileSize, 2)}`;
20
+ case ErrorCode.TooManyFiles:
21
+ return `Maximum number of files allowed is ${maxFiles}.`;
22
+ default: {
23
+ return error.message;
24
+ }
25
+ }
26
+ };
27
+
28
+ export { formatBytes, formatErrorMessage };
@@ -1,2 +1,3 @@
1
1
  "use strict";
2
2
  export { useControllableState } from './use-controllable-state.js';
3
+ export { useDraggable } from './use-draggable.js';
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ "use client";
3
+ import { useRef, useCallback, useEffect } from 'react';
4
+ import { useMove } from '@react-aria/interactions';
5
+
6
+ function useDraggable(props) {
7
+ const { targetRef, isDisabled = false, canOverflow = false } = props;
8
+ const boundary = useRef({ minLeft: 0, minTop: 0, maxLeft: 0, maxTop: 0 });
9
+ const isDragging = useRef(false);
10
+ let transform = { offsetX: 0, offsetY: 0 };
11
+ const onMoveStart = useCallback(() => {
12
+ isDragging.current = true;
13
+ const { offsetX, offsetY } = transform;
14
+ const targetRect = targetRef?.current?.getBoundingClientRect();
15
+ const targetLeft = targetRect?.left ?? 0;
16
+ const targetTop = targetRect?.top ?? 0;
17
+ const targetWidth = targetRect?.width ?? 0;
18
+ const targetHeight = targetRect?.height ?? 0;
19
+ const clientWidth = document.documentElement.clientWidth;
20
+ const clientHeight = document.documentElement.clientHeight;
21
+ const minLeft = -targetLeft + offsetX;
22
+ const minTop = -targetTop + offsetY;
23
+ const maxLeft = clientWidth - targetLeft - targetWidth + offsetX;
24
+ const maxTop = clientHeight - targetTop - targetHeight + offsetY;
25
+ boundary.current = {
26
+ minLeft,
27
+ minTop,
28
+ maxLeft,
29
+ maxTop
30
+ };
31
+ }, [transform, targetRef?.current]);
32
+ const onMove = useCallback(
33
+ (e) => {
34
+ if (isDisabled) {
35
+ return;
36
+ }
37
+ const { offsetX, offsetY } = transform;
38
+ const { minLeft, minTop, maxLeft, maxTop } = boundary.current;
39
+ let moveX = offsetX + e.deltaX;
40
+ let moveY = offsetY + e.deltaY;
41
+ if (!canOverflow) {
42
+ moveX = Math.min(Math.max(moveX, minLeft), maxLeft);
43
+ moveY = Math.min(Math.max(moveY, minTop), maxTop);
44
+ }
45
+ transform = {
46
+ offsetX: moveX,
47
+ offsetY: moveY
48
+ };
49
+ if (targetRef?.current) {
50
+ targetRef.current.style.transform = `translate(${moveX}px, ${moveY}px)`;
51
+ }
52
+ },
53
+ [isDisabled, transform, boundary.current, canOverflow, targetRef?.current]
54
+ );
55
+ const onMoveEnd = useCallback(() => {
56
+ isDragging.current = false;
57
+ }, []);
58
+ const { moveProps } = useMove({
59
+ onMoveStart,
60
+ onMove,
61
+ onMoveEnd
62
+ });
63
+ const preventDefault = useCallback((e) => {
64
+ if (isDragging.current) {
65
+ e.preventDefault();
66
+ }
67
+ }, []);
68
+ useEffect(() => {
69
+ if (!isDisabled) {
70
+ document.body.addEventListener("touchmove", preventDefault, {
71
+ passive: false
72
+ });
73
+ }
74
+ return () => {
75
+ document.body.removeEventListener("touchmove", preventDefault);
76
+ };
77
+ }, [isDisabled, preventDefault]);
78
+ return {
79
+ moveProps: {
80
+ ...moveProps,
81
+ style: { cursor: !isDisabled ? "move" : void 0 }
82
+ }
83
+ };
84
+ }
85
+
86
+ export { useDraggable };
package/dist/esm/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  export { useControllableState } from './hooks/use-controllable-state.js';
3
+ export { useDraggable } from './hooks/use-draggable.js';
4
+ export { Button } from './button/button.js';
3
5
  export { GovtBanner } from './govt-banner/govt-banner.js';
4
6
  export { Ripple } from './ripple/ripple.js';
5
7
  export { useRipple } from './ripple/use-ripple.js';
@@ -9,18 +11,24 @@ export { Toggle } from './toggle/toggle.js';
9
11
  export { SkipNavLink } from './skip-nav-link/skip-nav-link.js';
10
12
  export { Input } from './input/input.js';
11
13
  export { TextField } from './text-field/text-field.js';
12
- export { Description, FieldError, FieldGroup, Label } from './field/field.js';
14
+ export { Description, FieldError, FieldErrorIcon, FieldGroup, Label } from './field/field.js';
13
15
  export { TextArea } from './text-area/text-area.js';
14
16
  export { TextAreaField } from './text-area-field/text-area-field.js';
15
17
  export { ComboBox, ComboBoxEmptyState } from './combo-box/combo-box.js';
16
18
  export { ComboBoxFuzzy } from './combo-box/combo-box-fuzzy.js';
17
19
  export { ComboBoxItem } from './combo-box/combo-box-item.js';
18
20
  export { ComboBoxVariantContext, useComboBoxVariantContext } from './combo-box/combo-box-variant-context.js';
21
+ export { Banner } from './banner/banner.js';
19
22
  export { TagField } from './tag-field/tag-field.js';
20
23
  export { TagFieldItem } from './tag-field/tag-field-item.js';
21
24
  export { Select } from './select/select.js';
22
25
  export { SelectItem } from './select/select-item.js';
23
26
  export { SelectVariantContext, useSelectVariantContext } from './select/select-variant-context.js';
27
+ export { Badge } from './badge/badge.js';
28
+ export { Calendar, CalendarStateWrapper } from './calendar/calendar.js';
29
+ export { CalendarStyleContext, useCalendarStyleContext } from './calendar/calendar-style-context.js';
30
+ export { getEraFormat, useGenerateLocalizedMonths, useGenerateLocalizedYears, useLocalizedMonthYear } from './calendar/utils.js';
31
+ export { CalendarDate } from '@internationalized/date';
24
32
  export { RangeCalendar, RangeCalendarCell, RangeCalendarStateWrapper } from './range-calendar/range-calendar.js';
25
33
  export { Menu, MenuItem, MenuSection, MenuSeparator, MenuTrigger, MenuVariantContext, SubmenuTrigger, useMenuVariantContext } from './menu/menu.js';
26
34
  export { Popover } from './popover/popover.js';
@@ -35,10 +43,13 @@ export { PaginationCursor } from './pagination/pagination-cursor.js';
35
43
  export { PaginationItem } from './pagination/pagination-item.js';
36
44
  export { PaginationItemType } from './pagination/hooks/use-pagination.js';
37
45
  export { CURSOR_TRANSITION_TIMEOUT, usePagination } from './pagination/use-pagination.js';
38
- export { Button } from './button/button.js';
39
- export { Banner } from './banner/banner.js';
40
- export { Badge } from './badge/badge.js';
41
- export { CalendarDate } from '@internationalized/date';
42
- export { Calendar, CalendarStateWrapper } from './calendar/calendar.js';
43
- export { CalendarStyleContext, useCalendarStyleContext } from './calendar/calendar-style-context.js';
44
- export { getEraFormat, useGenerateLocalizedMonths, useGenerateLocalizedYears, useLocalizedMonthYear } from './calendar/utils.js';
46
+ export { FileDropzone } from './file-dropzone/file-dropzone.js';
47
+ export { FileInfo } from './file-dropzone/file-info.js';
48
+ export { formatBytes, formatErrorMessage } from './file-dropzone/utils.js';
49
+ export { NumberField } from './number-field/number-field.js';
50
+ export { Modal } from './modal/modal.js';
51
+ export { ModalContent } from './modal/modal-content.js';
52
+ export { ModalFooter } from './modal/modal-footer.js';
53
+ export { ModalBody } from './modal/modal-body.js';
54
+ export { ModalHeader } from './modal/modal-header.js';
55
+ export { ModalVariantContext, useModalVariantContext } from './modal/modal-variant-context.js';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ const i18nStrings = {
3
+ "en-SG": {
4
+ dismiss: "Dismiss"
5
+ },
6
+ "zh-SG": {
7
+ dismiss: "\u53D6\u6D88"
8
+ },
9
+ "ms-SG": {
10
+ dismiss: "Tutup"
11
+ },
12
+ "ta-SG": {
13
+ dismiss: "\u0BAE\u0BC2\u0B9F\u0BC1"
14
+ }
15
+ };
16
+
17
+ export { i18nStrings };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ export { Modal } from './modal.js';
3
+ export { ModalContent } from './modal-content.js';
4
+ export { ModalFooter } from './modal-footer.js';
5
+ export { ModalBody } from './modal-body.js';
6
+ export { ModalHeader } from './modal-header.js';
7
+ export { ModalVariantContext, useModalVariantContext } from './modal-variant-context.js';
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ "use client";
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { useContext } from 'react';
5
+ import { cn } from '@opengovsg/oui-theme';
6
+ import { forwardRef } from '../system/utils.js';
7
+ import { ModalVariantContext } from './modal-variant-context.js';
8
+
9
+ const ModalBody = forwardRef(function ModalBody2({ as, ...props }, ref) {
10
+ const { slots, classNames } = useContext(ModalVariantContext);
11
+ const Component = as || "div";
12
+ return /* @__PURE__ */ jsx(
13
+ Component,
14
+ {
15
+ ref,
16
+ className: slots.body({
17
+ className: cn(classNames?.body, props.className)
18
+ }),
19
+ ...props
20
+ }
21
+ );
22
+ });
23
+
24
+ export { ModalBody };