@opengovsg/oui 0.0.29 → 0.0.31

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 (106) hide show
  1. package/dist/cjs/avatar/avatar-context.cjs +12 -0
  2. package/dist/cjs/avatar/avatar-group-context.cjs +88 -0
  3. package/dist/cjs/avatar/avatar-group.cjs +60 -0
  4. package/dist/cjs/avatar/avatar.cjs +132 -0
  5. package/dist/cjs/avatar/hooks/use-img-loading-status.cjs +68 -0
  6. package/dist/cjs/avatar/index.cjs +23 -0
  7. package/dist/cjs/avatar/utils.cjs +9 -0
  8. package/dist/cjs/banner/banner.cjs +1 -1
  9. package/dist/cjs/checkbox/checkbox.cjs +3 -3
  10. package/dist/cjs/combo-box/combo-box.cjs +1 -1
  11. package/dist/cjs/date-field/date-field.cjs +1 -1
  12. package/dist/cjs/date-picker/date-picker.cjs +5 -4
  13. package/dist/cjs/date-range-picker/date-range-picker.cjs +3 -3
  14. package/dist/cjs/file-dropzone/file-dropzone.cjs +12 -8
  15. package/dist/cjs/file-dropzone/file-info.cjs +3 -2
  16. package/dist/cjs/file-dropzone/utils.cjs +4 -4
  17. package/dist/cjs/hooks/use-scroll-position.cjs +53 -0
  18. package/dist/cjs/index.cjs +61 -47
  19. package/dist/cjs/modal/modal-content.cjs +1 -1
  20. package/dist/cjs/navbar/navbar-menu/menu.cjs +2 -2
  21. package/dist/cjs/navbar/navbar-menu/toggle.cjs +3 -2
  22. package/dist/cjs/navbar/navbar.cjs +25 -1
  23. package/dist/cjs/navbar/use-navbar.cjs +42 -32
  24. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/user.cjs +22 -0
  25. package/dist/cjs/number-field/number-field.cjs +2 -2
  26. package/dist/cjs/range-calendar/range-calendar.cjs +1 -1
  27. package/dist/cjs/select/select.cjs +2 -2
  28. package/dist/cjs/tag-field/tag-field.cjs +1 -1
  29. package/dist/cjs/text-area-field/text-area-field.cjs +1 -1
  30. package/dist/cjs/text-field/text-field.cjs +1 -1
  31. package/dist/esm/avatar/avatar-context.js +9 -0
  32. package/dist/esm/avatar/avatar-group-context.js +84 -0
  33. package/dist/esm/avatar/avatar-group.js +58 -0
  34. package/dist/esm/avatar/avatar.js +128 -0
  35. package/dist/esm/avatar/hooks/use-img-loading-status.js +66 -0
  36. package/dist/esm/avatar/index.js +13 -0
  37. package/dist/esm/avatar/utils.js +7 -0
  38. package/dist/esm/banner/banner.js +1 -1
  39. package/dist/esm/checkbox/checkbox.js +3 -3
  40. package/dist/esm/combo-box/combo-box.js +1 -1
  41. package/dist/esm/date-field/date-field.js +1 -1
  42. package/dist/esm/date-picker/date-picker.js +5 -4
  43. package/dist/esm/date-range-picker/date-range-picker.js +3 -3
  44. package/dist/esm/file-dropzone/file-dropzone.js +12 -8
  45. package/dist/esm/file-dropzone/file-info.js +3 -2
  46. package/dist/esm/file-dropzone/utils.js +4 -4
  47. package/dist/esm/hooks/use-scroll-position.js +51 -0
  48. package/dist/esm/index.js +20 -15
  49. package/dist/esm/modal/modal-content.js +1 -1
  50. package/dist/esm/navbar/navbar-menu/menu.js +2 -2
  51. package/dist/esm/navbar/navbar-menu/toggle.js +3 -2
  52. package/dist/esm/navbar/navbar.js +26 -2
  53. package/dist/esm/navbar/use-navbar.js +43 -33
  54. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/user.js +17 -0
  55. package/dist/esm/number-field/number-field.js +2 -2
  56. package/dist/esm/range-calendar/range-calendar.js +1 -1
  57. package/dist/esm/select/select.js +2 -2
  58. package/dist/esm/tag-field/tag-field.js +1 -1
  59. package/dist/esm/text-area-field/text-area-field.js +1 -1
  60. package/dist/esm/text-field/text-field.js +1 -1
  61. package/dist/types/avatar/avatar-context.d.ts +12 -0
  62. package/dist/types/avatar/avatar-context.d.ts.map +1 -0
  63. package/dist/types/avatar/avatar-group-context.d.ts +70 -0
  64. package/dist/types/avatar/avatar-group-context.d.ts.map +1 -0
  65. package/dist/types/avatar/avatar-group.d.ts +5 -0
  66. package/dist/types/avatar/avatar-group.d.ts.map +1 -0
  67. package/dist/types/avatar/avatar.d.ts +18 -0
  68. package/dist/types/avatar/avatar.d.ts.map +1 -0
  69. package/dist/types/avatar/hooks/use-img-loading-status.d.ts +4 -0
  70. package/dist/types/avatar/hooks/use-img-loading-status.d.ts.map +1 -0
  71. package/dist/types/avatar/index.d.ts +15 -0
  72. package/dist/types/avatar/index.d.ts.map +1 -0
  73. package/dist/types/avatar/utils.d.ts +2 -0
  74. package/dist/types/avatar/utils.d.ts.map +1 -0
  75. package/dist/types/calendar/calendar.d.ts.map +1 -1
  76. package/dist/types/checkbox/checkbox-group-style-context.d.ts +1 -1
  77. package/dist/types/checkbox/checkbox-group-style-context.d.ts.map +1 -1
  78. package/dist/types/date-field/date-field.d.ts.map +1 -1
  79. package/dist/types/file-dropzone/file-dropzone.d.ts +8 -2
  80. package/dist/types/file-dropzone/file-dropzone.d.ts.map +1 -1
  81. package/dist/types/file-dropzone/file-info.d.ts.map +1 -1
  82. package/dist/types/file-dropzone/types.d.ts +1 -0
  83. package/dist/types/file-dropzone/types.d.ts.map +1 -1
  84. package/dist/types/file-dropzone/utils.d.ts +2 -1
  85. package/dist/types/file-dropzone/utils.d.ts.map +1 -1
  86. package/dist/types/hooks/use-scroll-position.d.ts +29 -0
  87. package/dist/types/hooks/use-scroll-position.d.ts.map +1 -0
  88. package/dist/types/index.d.mts +1 -0
  89. package/dist/types/index.d.ts +1 -0
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/menu/menu.d.ts.map +1 -1
  92. package/dist/types/navbar/navbar-context.d.ts +10 -2
  93. package/dist/types/navbar/navbar-context.d.ts.map +1 -1
  94. package/dist/types/navbar/navbar-menu/menu.d.ts.map +1 -1
  95. package/dist/types/navbar/navbar-menu/toggle.d.ts.map +1 -1
  96. package/dist/types/navbar/navbar.d.ts.map +1 -1
  97. package/dist/types/navbar/use-navbar.d.ts +33 -1
  98. package/dist/types/navbar/use-navbar.d.ts.map +1 -1
  99. package/dist/types/range-calendar/range-calendar.d.ts.map +1 -1
  100. package/dist/types/system/react-utils/context.d.ts +4 -4
  101. package/dist/types/system/react-utils/context.d.ts.map +1 -1
  102. package/dist/types/system/utils.d.ts.map +1 -1
  103. package/dist/types/tabs/tabs.d.ts.map +1 -1
  104. package/dist/types/tag-field/tag-field-item.d.ts.map +1 -1
  105. package/dist/types/tag-field/tag-field-list.d.ts.map +1 -1
  106. package/package.json +3 -3
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useMemo } from 'react';
4
+ import { forwardRef } from '../system/utils.js';
5
+ import { AvatarRoot, AvatarFallback } from './avatar.js';
6
+ import { useAvatarGroup, AvatarGroupProvider } from './avatar-group-context.js';
7
+
8
+ const AvatarGroup = forwardRef((props, ref) => {
9
+ const {
10
+ Component,
11
+ clones,
12
+ context,
13
+ remainingCount,
14
+ getAvatarGroupCountProps,
15
+ getAvatarGroupProps,
16
+ renderCount
17
+ } = useAvatarGroup({
18
+ ...props,
19
+ ref
20
+ });
21
+ const renderedCount = useMemo(() => {
22
+ if (remainingCount <= 0) return null;
23
+ if (renderCount) {
24
+ return renderCount(remainingCount);
25
+ }
26
+ const countAvatarVariantProps = {
27
+ prominence: "subtle",
28
+ color: "primary"
29
+ };
30
+ if (context.prominence === "subtle") {
31
+ countAvatarVariantProps.color = "white";
32
+ }
33
+ return /* @__PURE__ */ jsx(
34
+ AvatarRoot,
35
+ {
36
+ ...countAvatarVariantProps,
37
+ ...getAvatarGroupCountProps(),
38
+ name: `+${remainingCount}`,
39
+ children: /* @__PURE__ */ jsxs(AvatarFallback, { children: [
40
+ "+",
41
+ remainingCount
42
+ ] })
43
+ }
44
+ );
45
+ }, [
46
+ context.prominence,
47
+ getAvatarGroupCountProps,
48
+ remainingCount,
49
+ renderCount
50
+ ]);
51
+ return /* @__PURE__ */ jsx(Component, { ...getAvatarGroupProps(), children: /* @__PURE__ */ jsxs(AvatarGroupProvider, { value: context, children: [
52
+ clones,
53
+ renderedCount
54
+ ] }) });
55
+ });
56
+ AvatarGroup.displayName = "AvatarGroup";
57
+
58
+ export { AvatarGroup };
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ "use client";
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { useState, useMemo } from 'react';
5
+ import { useLayoutEffect } from '@react-aria/utils';
6
+ import { avatarStyles, dataAttr } from '@opengovsg/oui-theme';
7
+ import { forwardRef, mapPropsVariants } from '../system/utils.js';
8
+ import { AvatarContext, useAvatarContext } from './avatar-context.js';
9
+ import { useAvatarGroupContext } from './avatar-group-context.js';
10
+ import { useImageLoadingStatus } from './hooks/use-img-loading-status.js';
11
+ import { getInitialsFromText } from './utils.js';
12
+ import { useDomRef } from '../system/react-utils/refs.js';
13
+ import User from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/user.js';
14
+
15
+ const AvatarRoot = forwardRef(
16
+ (originalProps, ref) => {
17
+ const groupContext = useAvatarGroupContext();
18
+ const [
19
+ {
20
+ name,
21
+ getInitials = getInitialsFromText,
22
+ classNames,
23
+ className,
24
+ children,
25
+ as,
26
+ ...props
27
+ },
28
+ {
29
+ color = groupContext?.color,
30
+ prominence = groupContext?.prominence,
31
+ size = groupContext?.size,
32
+ radius = groupContext?.radius,
33
+ ...variantProps
34
+ }
35
+ ] = mapPropsVariants(originalProps, avatarStyles.variantKeys);
36
+ const isInGroup = !!groupContext;
37
+ const domRef = useDomRef(ref);
38
+ const slots = avatarStyles({
39
+ color,
40
+ prominence,
41
+ size,
42
+ radius,
43
+ isInGroup,
44
+ ...variantProps
45
+ });
46
+ const [imageLoadingStatus, setImageLoadingStatus] = useState("idle");
47
+ const Component = as || "span";
48
+ return /* @__PURE__ */ jsx(
49
+ AvatarContext,
50
+ {
51
+ value: {
52
+ imageLoadingStatus,
53
+ setImageLoadingStatus,
54
+ slots,
55
+ classNames,
56
+ getInitials,
57
+ name
58
+ },
59
+ children: /* @__PURE__ */ jsx(
60
+ Component,
61
+ {
62
+ ref: domRef,
63
+ ...props,
64
+ className: slots.base({ className: className ?? classNames?.base }),
65
+ children
66
+ }
67
+ )
68
+ }
69
+ );
70
+ }
71
+ );
72
+ const AvatarImage = forwardRef(
73
+ ({ src, as, ...props }, ref) => {
74
+ const domRef = useDomRef(ref);
75
+ const {
76
+ setImageLoadingStatus,
77
+ imageLoadingStatus,
78
+ slots,
79
+ name,
80
+ classNames
81
+ } = useAvatarContext();
82
+ const currentImageStatus = useImageLoadingStatus(src, props);
83
+ const Component = as || "img";
84
+ useLayoutEffect(() => {
85
+ setImageLoadingStatus(currentImageStatus);
86
+ }, [currentImageStatus, setImageLoadingStatus]);
87
+ return /* @__PURE__ */ jsx(
88
+ Component,
89
+ {
90
+ ref: domRef,
91
+ alt: name,
92
+ "data-loaded": dataAttr(imageLoadingStatus === "loaded"),
93
+ ...props,
94
+ src,
95
+ className: slots.image({ className: classNames?.image })
96
+ }
97
+ );
98
+ }
99
+ );
100
+ const AvatarFallback = forwardRef(
101
+ ({ children, as, ...props }, ref) => {
102
+ const domRef = useDomRef(ref);
103
+ const { slots, classNames, imageLoadingStatus, name, getInitials } = useAvatarContext();
104
+ const childrenToRender = useMemo(() => {
105
+ if (children) return children;
106
+ if (name) {
107
+ return getInitials(name);
108
+ }
109
+ return /* @__PURE__ */ jsx(User, { className: slots.icon({ className: classNames?.icon }) });
110
+ }, [children, classNames?.icon, getInitials, name, slots]);
111
+ const Component = as || "div";
112
+ if (imageLoadingStatus === "loaded") {
113
+ return null;
114
+ }
115
+ return /* @__PURE__ */ jsx(
116
+ Component,
117
+ {
118
+ title: name,
119
+ className: slots.fallback({ className: classNames?.fallback }),
120
+ ref: domRef,
121
+ ...props,
122
+ children: childrenToRender
123
+ }
124
+ );
125
+ }
126
+ );
127
+
128
+ export { AvatarFallback, AvatarImage, AvatarRoot };
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ import { useRef, useState, useLayoutEffect, useSyncExternalStore } from 'react';
3
+
4
+ function useIsHydrated() {
5
+ return useSyncExternalStore(
6
+ subscribe,
7
+ () => true,
8
+ () => false
9
+ );
10
+ }
11
+ function subscribe() {
12
+ return () => {
13
+ };
14
+ }
15
+ function resolveLoadingStatus(image, src) {
16
+ if (!image) {
17
+ return "idle";
18
+ }
19
+ if (!src) {
20
+ return "error";
21
+ }
22
+ if (image.src !== src) {
23
+ image.src = src;
24
+ }
25
+ return image.complete && image.naturalWidth > 0 ? "loaded" : "loading";
26
+ }
27
+ function useImageLoadingStatus(src, { referrerPolicy, crossOrigin }) {
28
+ const isHydrated = useIsHydrated();
29
+ const imageRef = useRef(null);
30
+ const image = (() => {
31
+ if (!isHydrated) return null;
32
+ if (!imageRef.current) {
33
+ imageRef.current = new window.Image();
34
+ }
35
+ return imageRef.current;
36
+ })();
37
+ const [loadingStatus, setLoadingStatus] = useState(
38
+ () => resolveLoadingStatus(image, src)
39
+ );
40
+ useLayoutEffect(() => {
41
+ setLoadingStatus(resolveLoadingStatus(image, src));
42
+ }, [image, src]);
43
+ useLayoutEffect(() => {
44
+ const updateStatus = (status) => () => {
45
+ setLoadingStatus(status);
46
+ };
47
+ if (!image) return;
48
+ const handleLoad = updateStatus("loaded");
49
+ const handleError = updateStatus("error");
50
+ image.addEventListener("load", handleLoad);
51
+ image.addEventListener("error", handleError);
52
+ if (referrerPolicy) {
53
+ image.referrerPolicy = referrerPolicy;
54
+ }
55
+ if (typeof crossOrigin === "string") {
56
+ image.crossOrigin = crossOrigin;
57
+ }
58
+ return () => {
59
+ image.removeEventListener("load", handleLoad);
60
+ image.removeEventListener("error", handleError);
61
+ };
62
+ }, [image, crossOrigin, referrerPolicy]);
63
+ return loadingStatus;
64
+ }
65
+
66
+ export { useImageLoadingStatus };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ import { AvatarRoot, AvatarFallback, AvatarImage } from './avatar.js';
3
+ export { AvatarContext, useAvatarContext } from './avatar-context.js';
4
+ export { AvatarGroup } from './avatar-group.js';
5
+ export { AvatarGroupProvider, useAvatarGroup } from './avatar-group-context.js';
6
+
7
+ const Avatar = Object.assign(AvatarRoot, {
8
+ Root: AvatarRoot,
9
+ Image: AvatarImage,
10
+ Fallback: AvatarFallback
11
+ });
12
+
13
+ export { Avatar, AvatarFallback, AvatarImage, AvatarRoot };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const getInitialsFromText = (text, limit = 2) => {
3
+ const initials = text?.trim().split(/[\s\-_.]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase()).join("") || "";
4
+ return initials.slice(0, limit);
5
+ };
6
+
7
+ export { getInitialsFromText };
@@ -5,9 +5,9 @@ import { useMemo, useRef } from 'react';
5
5
  import { useMessageFormatter, useDisclosure } from 'react-aria';
6
6
  import { useDisclosureState } from 'react-stately';
7
7
  import { bannerStyles } from '@opengovsg/oui-theme';
8
+ import { Button } from '../button/button.js';
8
9
  import CircleAlert from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/circle-alert.js';
9
10
  import Info from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/info.js';
10
- import { Button } from '../button/button.js';
11
11
  import X from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/x.js';
12
12
 
13
13
  const i18nStrings = {
@@ -3,11 +3,11 @@
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { Checkbox as Checkbox$1, composeRenderProps, Provider, CheckboxGroup as CheckboxGroup$1 } from 'react-aria-components';
5
5
  import { checkboxStyles, checkboxGroupStyles } from '@opengovsg/oui-theme';
6
+ import { Label, Description, FieldError } from '../field/field.js';
6
7
  import { mapPropsVariants } from '../system/utils.js';
7
8
  import { useCheckboxGroupStyleContext, CheckboxGroupStyleContext } from './checkbox-group-style-context.js';
8
9
  import Minus from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/minus.js';
9
10
  import Check from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/check.js';
10
- import { Label, Description, FieldError } from '../field/field.js';
11
11
 
12
12
  const Checkbox = ({
13
13
  classNames,
@@ -18,8 +18,8 @@ const Checkbox = ({
18
18
  originalProps,
19
19
  checkboxStyles.variantKeys
20
20
  );
21
- const { size } = useCheckboxGroupStyleContext();
22
- const styles = checkboxStyles({ size, ...variants });
21
+ const context = useCheckboxGroupStyleContext();
22
+ const styles = checkboxStyles({ size: context?.size, ...variants });
23
23
  return /* @__PURE__ */ jsx(
24
24
  Checkbox$1,
25
25
  {
@@ -5,10 +5,10 @@ import { useMemo, useCallback } from 'react';
5
5
  import { useMessageFormatter } from 'react-aria';
6
6
  import { ListLayout, Provider, ComboBox as ComboBox$1, Input, Button, Virtualizer, ListBox } from 'react-aria-components';
7
7
  import { listBoxItemStyles, cn, comboBoxStyles, composeTailwindRenderProps, composeRenderProps, comboBoxClearButtonStyles } from '@opengovsg/oui-theme';
8
+ import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
8
9
  import { Popover } from '../popover/popover.js';
9
10
  import { mapPropsVariants } from '../system/utils.js';
10
11
  import { ComboBoxVariantContext } from './combo-box-variant-context.js';
11
- import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
12
12
  import ChevronUp from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/chevron-up.js';
13
13
  import ChevronDown from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/chevron-down.js';
14
14
  import X from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/x.js';
@@ -4,8 +4,8 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { useMemo } from 'react';
5
5
  import { DateField as DateField$1, DateInput as DateInput$1, DateSegment } from 'react-aria-components';
6
6
  import { dateFieldStyles, composeTailwindRenderProps, dateInputStyles, composeRenderProps } from '@opengovsg/oui-theme';
7
- import { mapPropsVariants } from '../system/utils.js';
8
7
  import { Label, Description, FieldError } from '../field/field.js';
8
+ import { mapPropsVariants } from '../system/utils.js';
9
9
 
10
10
  function DateField(originalProps) {
11
11
  const [
@@ -4,13 +4,14 @@ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { useMemo } from 'react';
5
5
  import { DatePicker as DatePicker$1, Dialog } from 'react-aria-components';
6
6
  import { datePickerStyles, composeTailwindRenderProps } from '@opengovsg/oui-theme';
7
+ import { Button } from '../button/button.js';
8
+ import { Calendar as Calendar$1 } from '../calendar/calendar.js';
9
+ import '@internationalized/date';
10
+ import { DateInput } from '../date-field/date-field.js';
11
+ import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
7
12
  import { Popover } from '../popover/popover.js';
8
13
  import { mapPropsVariants } from '../system/utils.js';
9
- import { DateInput } from '../date-field/date-field.js';
10
14
  import Calendar from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/calendar.js';
11
- import { Calendar as Calendar$1 } from '../calendar/calendar.js';
12
- import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
13
- import { Button } from '../button/button.js';
14
15
 
15
16
  function DatePicker(originalProps) {
16
17
  const [
@@ -5,13 +5,13 @@ import { useMemo } from 'react';
5
5
  import { CalendarDate } from '@internationalized/date';
6
6
  import { DateRangePicker as DateRangePicker$1, Dialog } from 'react-aria-components';
7
7
  import { dateRangePickerStyles, composeTailwindRenderProps } from '@opengovsg/oui-theme';
8
+ import { Button } from '../button/button.js';
9
+ import { DateInput } from '../date-field/date-field.js';
10
+ import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
8
11
  import { Popover } from '../popover/popover.js';
9
12
  import { RangeCalendar } from '../range-calendar/range-calendar.js';
10
13
  import { mapPropsVariants } from '../system/utils.js';
11
14
  import Calendar from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/calendar.js';
12
- import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
13
- import { DateInput } from '../date-field/date-field.js';
14
- import { Button } from '../button/button.js';
15
15
 
16
16
  function DateRangePicker(originalProps) {
17
17
  const [
@@ -7,13 +7,13 @@ import { useField, useId } from 'react-aria';
7
7
  import { Provider, LabelContext, GroupContext, TextContext, FieldErrorContext, Group } from 'react-aria-components';
8
8
  import { useDropzone } from 'react-dropzone';
9
9
  import { fileDropzoneStyles, dataAttr } from '@opengovsg/oui-theme';
10
+ import { Label, Description, FieldError } from '../field/field.js';
10
11
  import { useControllableState } from '../hooks/use-controllable-state.js';
11
12
  import { mapPropsVariants } from '../system/utils.js';
12
13
  import { FileDropzoneStyleContext, FileDropzoneStateContext, useFileDropzoneStateContext, useFileDropzoneStyleContext } from './contexts.js';
13
14
  import { FileInfo } from './file-info.js';
14
15
  import { formatErrorMessage, formatBytes } from './utils.js';
15
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';
16
- import { Label, Description, FieldError } from '../field/field.js';
17
17
 
18
18
  const FileDropzone = (originalProps) => {
19
19
  const [props, variantProps] = mapPropsVariants(
@@ -23,6 +23,7 @@ const FileDropzone = (originalProps) => {
23
23
  const {
24
24
  name,
25
25
  allowedMimeTypes = [],
26
+ fileSizeBase = "binary",
26
27
  maxFileSize = Number.POSITIVE_INFINITY,
27
28
  minFileSize = 0,
28
29
  showFileSizeText = true,
@@ -67,9 +68,10 @@ const FileDropzone = (originalProps) => {
67
68
  (error) => formatErrorMessage(error, {
68
69
  maxFileSize,
69
70
  minFileSize,
70
- maxFiles
71
+ maxFiles,
72
+ fileSizeBase
71
73
  }),
72
- [maxFileSize, maxFiles, minFileSize]
74
+ [fileSizeBase, maxFileSize, maxFiles, minFileSize]
73
75
  );
74
76
  const onDrop = useCallback(
75
77
  (acceptedFiles, fileRejections) => {
@@ -127,19 +129,20 @@ const FileDropzone = (originalProps) => {
127
129
  const shouldShow = showFileSizeText && (notDefaultMaxFileSize || notDefaultMinFileSize);
128
130
  if (!shouldShow) return null;
129
131
  if (notDefaultMaxFileSize && notDefaultMinFileSize) {
130
- return `File size must be between ${formatBytes(minFileSize, 2)} and ${formatBytes(
132
+ return `File size must be between ${formatBytes(minFileSize, 2, fileSizeBase)} and ${formatBytes(
131
133
  maxFileSize,
132
- 2
134
+ 2,
135
+ fileSizeBase
133
136
  )}`;
134
137
  }
135
138
  if (notDefaultMaxFileSize) {
136
- return `Maximum file size: ${formatBytes(maxFileSize, 2)}`;
139
+ return `Maximum file size: ${formatBytes(maxFileSize, 2, fileSizeBase)}`;
137
140
  }
138
141
  if (notDefaultMinFileSize) {
139
- return `Minimum file size: ${formatBytes(minFileSize, 2)}`;
142
+ return `Minimum file size: ${formatBytes(minFileSize, 2, fileSizeBase)}`;
140
143
  }
141
144
  return null;
142
- }, [maxFileSize, minFileSize, showFileSizeText]);
145
+ }, [maxFileSize, minFileSize, showFileSizeText, fileSizeBase]);
143
146
  const triggerFileSelector = useCallback(() => {
144
147
  if (isDisabled || isReadOnly) return;
145
148
  dropzoneState.inputRef.current?.click();
@@ -185,6 +188,7 @@ const FileDropzone = (originalProps) => {
185
188
  {
186
189
  isDisabled,
187
190
  isReadOnly,
191
+ fileSizeBase,
188
192
  maxFiles,
189
193
  maxFileSize,
190
194
  showDropzone,
@@ -3,10 +3,10 @@
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
  import { useState, useEffect } from 'react';
5
5
  import { fileInfoDropzoneStyles, cn } from '@opengovsg/oui-theme';
6
+ import { Button } from '../button/button.js';
6
7
  import { useFileDropzoneStateContext, useFileDropzoneStyleContext } from './contexts.js';
7
8
  import { formatBytes } from './utils.js';
8
9
  import Trash2 from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/trash-2.js';
9
- import { Button } from '../button/button.js';
10
10
 
11
11
  const FileInfo = ({ file, imagePreview, classNames }) => {
12
12
  const {
@@ -17,7 +17,8 @@ const FileInfo = ({ file, imagePreview, classNames }) => {
17
17
  isReadOnly
18
18
  } = useFileDropzoneStateContext();
19
19
  const { size, variant, itemClassNames } = useFileDropzoneStyleContext();
20
- const readableFileSize = formatBytes(file.size, 2);
20
+ const { fileSizeBase } = useFileDropzoneStateContext();
21
+ const readableFileSize = formatBytes(file.size, 2, fileSizeBase);
21
22
  const styles = fileInfoDropzoneStyles({
22
23
  size,
23
24
  variant,
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  import { ErrorCode } from 'react-dropzone';
3
3
 
4
- const formatBytes = (bytes, decimals = 2, size) => {
5
- const k = 1e3;
4
+ const formatBytes = (bytes, decimals = 2, base = "binary", size) => {
5
+ const k = base === "binary" ? 1024 : 1e3;
6
6
  const dm = decimals < 0 ? 0 : decimals;
7
7
  const sizes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
8
8
  if (bytes === 0 || bytes === void 0)
@@ -14,9 +14,9 @@ const formatErrorMessage = (error, config) => {
14
14
  const { maxFileSize, minFileSize, maxFiles } = config;
15
15
  switch (error.code) {
16
16
  case ErrorCode.FileTooLarge:
17
- return `You have exceeded the size limit, please upload a file below ${formatBytes(maxFileSize, 2)}`;
17
+ return `You have exceeded the size limit, please upload a file below ${formatBytes(maxFileSize, 2, config.fileSizeBase)}`;
18
18
  case ErrorCode.FileTooSmall:
19
- return `Please upload a file above ${formatBytes(minFileSize, 2)}`;
19
+ return `Please upload a file above ${formatBytes(minFileSize, 2, config.fileSizeBase)}`;
20
20
  case ErrorCode.TooManyFiles:
21
21
  return `Maximum number of files allowed is ${maxFiles}.`;
22
22
  default: {
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ import { useRef, useCallback, useEffect } from 'react';
3
+
4
+ const isBrowser = typeof window !== "undefined";
5
+ function getScrollPosition(element) {
6
+ if (!isBrowser) return { x: 0, y: 0 };
7
+ if (!element) {
8
+ return { x: window.scrollX, y: window.scrollY };
9
+ }
10
+ return { x: element.scrollLeft, y: element.scrollTop };
11
+ }
12
+ const useScrollPosition = (props) => {
13
+ const { elementRef, delay = 30, callback, isEnabled } = props;
14
+ const position = useRef(
15
+ isEnabled ? getScrollPosition(elementRef?.current) : { x: 0, y: 0 }
16
+ );
17
+ const throttleTimeout = useRef(null);
18
+ const handler = useCallback(() => {
19
+ const currPos = getScrollPosition(elementRef?.current);
20
+ if (typeof callback === "function") {
21
+ callback({ prevPos: position.current, currPos });
22
+ }
23
+ position.current = currPos;
24
+ throttleTimeout.current = null;
25
+ }, [callback, elementRef]);
26
+ useEffect(() => {
27
+ if (!isEnabled) return;
28
+ const handleScroll = () => {
29
+ if (delay) {
30
+ if (throttleTimeout.current) {
31
+ clearTimeout(throttleTimeout.current);
32
+ }
33
+ throttleTimeout.current = setTimeout(handler, delay);
34
+ } else {
35
+ handler();
36
+ }
37
+ };
38
+ const target = elementRef?.current || window;
39
+ target.addEventListener("scroll", handleScroll);
40
+ return () => {
41
+ target.removeEventListener("scroll", handleScroll);
42
+ if (throttleTimeout.current) {
43
+ clearTimeout(throttleTimeout.current);
44
+ throttleTimeout.current = null;
45
+ }
46
+ };
47
+ }, [elementRef?.current, delay, handler, isEnabled]);
48
+ return position.current;
49
+ };
50
+
51
+ export { useScrollPosition };
package/dist/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  export { useControllableState } from './hooks/use-controllable-state.js';
3
3
  export { useDraggable } from './hooks/use-draggable.js';
4
+ export { Button } from './button/button.js';
4
5
  export { GovtBanner } from './govt-banner/govt-banner.js';
5
6
  export { Ripple } from './ripple/ripple.js';
6
7
  export { useRipple } from './ripple/use-ripple.js';
@@ -10,17 +11,31 @@ export { Toggle } from './toggle/toggle.js';
10
11
  export { SkipNavLink } from './skip-nav-link/skip-nav-link.js';
11
12
  export { Input } from './input/input.js';
12
13
  export { TextField } from './text-field/text-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';
17
+ export { ComboBox, ComboBoxEmptyState } from './combo-box/combo-box.js';
18
+ export { ComboBoxFuzzy } from './combo-box/combo-box-fuzzy.js';
19
+ export { ComboBoxItem } from './combo-box/combo-box-item.js';
20
+ export { ComboBoxVariantContext, useComboBoxVariantContext } from './combo-box/combo-box-variant-context.js';
15
21
  export { TagField } from './tag-field/tag-field.js';
16
22
  export { TagFieldItem } from './tag-field/tag-field-item.js';
17
23
  export { Select } from './select/select.js';
18
24
  export { SelectItem } from './select/select-item.js';
19
25
  export { SelectVariantContext, useSelectVariantContext } from './select/select-variant-context.js';
26
+ export { Calendar, CalendarStateWrapper } from './calendar/calendar.js';
27
+ export { CalendarStyleContext, useCalendarStyleContext } from './calendar/calendar-style-context.js';
28
+ export { getEraFormat, useGenerateLocalizedMonths, useGenerateLocalizedYears, useLocalizedMonthYear } from './calendar/utils.js';
29
+ export { CalendarDate } from '@internationalized/date';
20
30
  export { RangeCalendar, RangeCalendarCell, RangeCalendarStateWrapper } from './range-calendar/range-calendar.js';
21
31
  export { Menu, MenuItem, MenuSection, MenuSeparator, MenuTrigger, MenuVariantContext, SubmenuTrigger, useMenuVariantContext } from './menu/menu.js';
22
32
  export { Popover } from './popover/popover.js';
23
33
  export { Tab, TabList, TabPanel, Tabs, TabsVariantContext, useTabsVariantContext } from './tabs/tabs.js';
34
+ export { DateField, DateInput } from './date-field/date-field.js';
35
+ export { DatePicker } from './date-picker/date-picker.js';
36
+ export { DateRangePicker } from './date-range-picker/date-range-picker.js';
37
+ export { Checkbox, CheckboxGroup } from './checkbox/checkbox.js';
38
+ export { CheckboxGroupStyleContext, useCheckboxGroupStyleContext } from './checkbox/checkbox-group-style-context.js';
24
39
  export { Pagination } from './pagination/pagination.js';
25
40
  export { PaginationCursor } from './pagination/pagination-cursor.js';
26
41
  export { PaginationItem } from './pagination/pagination-item.js';
@@ -46,21 +61,11 @@ export { NavbarMenuToggle } from './navbar/navbar-menu/toggle.js';
46
61
  export { NavbarItem } from './navbar/navbar-item.js';
47
62
  export { useNavbar } from './navbar/use-navbar.js';
48
63
  export { NavbarProvider, useNavbarContext } from './navbar/navbar-context.js';
49
- export { Button } from './button/button.js';
50
- export { Description, FieldError, FieldErrorIcon, FieldGroup, Label } from './field/field.js';
51
- export { ComboBox, ComboBoxEmptyState } from './combo-box/combo-box.js';
52
- export { ComboBoxFuzzy } from './combo-box/combo-box-fuzzy.js';
53
- export { ComboBoxItem } from './combo-box/combo-box-item.js';
54
- export { ComboBoxVariantContext, useComboBoxVariantContext } from './combo-box/combo-box-variant-context.js';
64
+ export { Avatar } from './avatar/index.js';
55
65
  export { Banner } from './banner/banner.js';
56
66
  export { Badge } from './badge/badge.js';
57
- export { CalendarDate } from '@internationalized/date';
58
- export { Calendar, CalendarStateWrapper } from './calendar/calendar.js';
59
- export { CalendarStyleContext, useCalendarStyleContext } from './calendar/calendar-style-context.js';
60
- export { getEraFormat, useGenerateLocalizedMonths, useGenerateLocalizedYears, useLocalizedMonthYear } from './calendar/utils.js';
61
- export { DateField, DateInput } from './date-field/date-field.js';
62
- export { DatePicker } from './date-picker/date-picker.js';
63
- export { DateRangePicker } from './date-range-picker/date-range-picker.js';
64
- export { Checkbox, CheckboxGroup } from './checkbox/checkbox.js';
65
- export { CheckboxGroupStyleContext, useCheckboxGroupStyleContext } from './checkbox/checkbox-group-style-context.js';
66
67
  export { toast } from 'sonner';
68
+ export { AvatarContext, useAvatarContext } from './avatar/avatar-context.js';
69
+ export { AvatarGroup } from './avatar/avatar-group.js';
70
+ export { AvatarGroupProvider, useAvatarGroup } from './avatar/avatar-group-context.js';
71
+ export { AvatarFallback, AvatarImage, AvatarRoot } from './avatar/avatar.js';
@@ -5,10 +5,10 @@ import { useContext, isValidElement } from 'react';
5
5
  import { useMessageFormatter } from 'react-aria';
6
6
  import { Dialog } from 'react-aria-components';
7
7
  import { cn } from '@opengovsg/oui-theme';
8
+ import { Button } from '../button/button.js';
8
9
  import { i18nStrings } from './i18n.js';
9
10
  import { ModalVariantContext } from './modal-variant-context.js';
10
11
  import X from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.2.3/node_modules/lucide-react/dist/esm/icons/x.js';
11
- import { Button } from '../button/button.js';
12
12
 
13
13
  function ModalContent({
14
14
  closeButtonContent: closeButtonContentProp,
@@ -14,7 +14,7 @@ const NavbarMenu = forwardRef(
14
14
  const {
15
15
  slots,
16
16
  isMenuOpen,
17
- height,
17
+ menuTopOffsetPx,
18
18
  classNames,
19
19
  setIsMenuOpen,
20
20
  domRef: parentRef,
@@ -44,7 +44,7 @@ const NavbarMenu = forwardRef(
44
44
  style: {
45
45
  ...style,
46
46
  // @ts-expect-error due to not having any type declaration for CSS variables in React style prop
47
- "--navbar-height": typeof height === "number" ? `${height}px` : height
47
+ "--menu-offset": menuTopOffsetPx
48
48
  },
49
49
  onKeyDown: chain(handleKeyDown, onKeyDown),
50
50
  ...props,
@@ -24,13 +24,14 @@ const NavbarMenuToggle = ({
24
24
  isMenuOpen,
25
25
  setIsMenuOpen,
26
26
  menuRef,
27
- position
27
+ position,
28
+ menuTopOffset
28
29
  } = useNavbarContext();
29
30
  const shouldScrollToTop = (isMenuOpen2) => {
30
31
  if (!isMenuOpen2 || position === "sticky" || typeof window === "undefined") {
31
32
  return;
32
33
  }
33
- window.scrollTo({ top: 0, behavior: "instant" });
34
+ window.scrollTo({ top: menuTopOffset, behavior: "instant" });
34
35
  };
35
36
  const formatMessage = useMessageFormatter(i18nStrings);
36
37
  const toggleStyles = useMemo(() => {