@opengovsg/oui 0.0.0-snapshot-20250311073924

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 (215) hide show
  1. package/LICENSE.md +13 -0
  2. package/dist/cjs/banner/banner.cjs +118 -0
  3. package/dist/cjs/banner/index.cjs +8 -0
  4. package/dist/cjs/button/button.cjs +86 -0
  5. package/dist/cjs/button/index.cjs +8 -0
  6. package/dist/cjs/combo-box/combo-box-fuzzy.cjs +107 -0
  7. package/dist/cjs/combo-box/combo-box.cjs +277 -0
  8. package/dist/cjs/combo-box/index.cjs +12 -0
  9. package/dist/cjs/field/field.cjs +63 -0
  10. package/dist/cjs/field/index.cjs +11 -0
  11. package/dist/cjs/govt-banner/govt-banner.cjs +259 -0
  12. package/dist/cjs/govt-banner/index.cjs +8 -0
  13. package/dist/cjs/hooks/index.cjs +8 -0
  14. package/dist/cjs/hooks/use-callback-ref.cjs +17 -0
  15. package/dist/cjs/hooks/use-controllable-state.cjs +37 -0
  16. package/dist/cjs/index.cjs +47 -0
  17. package/dist/cjs/input/index.cjs +8 -0
  18. package/dist/cjs/input/input.cjs +28 -0
  19. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/Icon.cjs +48 -0
  20. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/createLucideIcon.cjs +30 -0
  21. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/defaultAttributes.cjs +24 -0
  22. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/chevron-down.cjs +19 -0
  23. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/chevron-up.cjs +19 -0
  24. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/circle-alert.cjs +23 -0
  25. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/external-link.cjs +23 -0
  26. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/info.cjs +23 -0
  27. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/landmark.cjs +26 -0
  28. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/lock.cjs +22 -0
  29. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/x.cjs +22 -0
  30. package/dist/cjs/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/shared/src/utils.cjs +16 -0
  31. package/dist/cjs/ripple/index.cjs +10 -0
  32. package/dist/cjs/ripple/ripple.cjs +70 -0
  33. package/dist/cjs/ripple/use-ripple.cjs +29 -0
  34. package/dist/cjs/skip-nav-link/index.cjs +8 -0
  35. package/dist/cjs/skip-nav-link/skip-nav-link.cjs +18 -0
  36. package/dist/cjs/spinner/index.cjs +10 -0
  37. package/dist/cjs/spinner/spinner.cjs +17 -0
  38. package/dist/cjs/spinner/use-spinner.cjs +38 -0
  39. package/dist/cjs/system/react-utils/context.cjs +31 -0
  40. package/dist/cjs/system/react-utils/index.cjs +8 -0
  41. package/dist/cjs/system/types.cjs +3 -0
  42. package/dist/cjs/system/utils.cjs +101 -0
  43. package/dist/cjs/tag-field/index.cjs +8 -0
  44. package/dist/cjs/tag-field/tag-field-list.cjs +73 -0
  45. package/dist/cjs/tag-field/tag-field-root.cjs +184 -0
  46. package/dist/cjs/tag-field/tag-field-state-context.cjs +11 -0
  47. package/dist/cjs/tag-field/tag-field-tag-list.cjs +70 -0
  48. package/dist/cjs/tag-field/tag-field-trigger.cjs +27 -0
  49. package/dist/cjs/tag-field/tag-field.cjs +106 -0
  50. package/dist/cjs/tag-field/types.cjs +3 -0
  51. package/dist/cjs/tag-field/use-tag-field-state.cjs +103 -0
  52. package/dist/cjs/tag-field/use-tag-field.cjs +188 -0
  53. package/dist/cjs/text-area/index.cjs +8 -0
  54. package/dist/cjs/text-area/text-area.cjs +28 -0
  55. package/dist/cjs/text-area-field/index.cjs +8 -0
  56. package/dist/cjs/text-area-field/text-area-field.cjs +48 -0
  57. package/dist/cjs/text-field/index.cjs +8 -0
  58. package/dist/cjs/text-field/text-field.cjs +48 -0
  59. package/dist/cjs/toggle/index.cjs +8 -0
  60. package/dist/cjs/toggle/toggle.cjs +48 -0
  61. package/dist/esm/banner/banner.js +116 -0
  62. package/dist/esm/banner/index.js +2 -0
  63. package/dist/esm/button/button.js +84 -0
  64. package/dist/esm/button/index.js +2 -0
  65. package/dist/esm/combo-box/combo-box-fuzzy.js +105 -0
  66. package/dist/esm/combo-box/combo-box.js +273 -0
  67. package/dist/esm/combo-box/index.js +3 -0
  68. package/dist/esm/field/field.js +58 -0
  69. package/dist/esm/field/index.js +2 -0
  70. package/dist/esm/govt-banner/govt-banner.js +257 -0
  71. package/dist/esm/govt-banner/index.js +2 -0
  72. package/dist/esm/hooks/index.js +2 -0
  73. package/dist/esm/hooks/use-callback-ref.js +15 -0
  74. package/dist/esm/hooks/use-controllable-state.js +35 -0
  75. package/dist/esm/index.js +19 -0
  76. package/dist/esm/input/index.js +2 -0
  77. package/dist/esm/input/input.js +26 -0
  78. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/Icon.js +44 -0
  79. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/createLucideIcon.js +26 -0
  80. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/defaultAttributes.js +20 -0
  81. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/chevron-down.js +14 -0
  82. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/chevron-up.js +14 -0
  83. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/circle-alert.js +18 -0
  84. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/external-link.js +18 -0
  85. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/info.js +18 -0
  86. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/landmark.js +21 -0
  87. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/lock.js +17 -0
  88. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/x.js +17 -0
  89. package/dist/esm/node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/shared/src/utils.js +13 -0
  90. package/dist/esm/ripple/index.js +3 -0
  91. package/dist/esm/ripple/ripple.js +49 -0
  92. package/dist/esm/ripple/use-ripple.js +27 -0
  93. package/dist/esm/skip-nav-link/index.js +2 -0
  94. package/dist/esm/skip-nav-link/skip-nav-link.js +16 -0
  95. package/dist/esm/spinner/index.js +3 -0
  96. package/dist/esm/spinner/spinner.js +15 -0
  97. package/dist/esm/spinner/use-spinner.js +36 -0
  98. package/dist/esm/system/react-utils/context.js +29 -0
  99. package/dist/esm/system/react-utils/index.js +2 -0
  100. package/dist/esm/system/types.js +1 -0
  101. package/dist/esm/system/utils.js +94 -0
  102. package/dist/esm/tag-field/index.js +2 -0
  103. package/dist/esm/tag-field/tag-field-list.js +69 -0
  104. package/dist/esm/tag-field/tag-field-root.js +182 -0
  105. package/dist/esm/tag-field/tag-field-state-context.js +9 -0
  106. package/dist/esm/tag-field/tag-field-tag-list.js +68 -0
  107. package/dist/esm/tag-field/tag-field-trigger.js +24 -0
  108. package/dist/esm/tag-field/tag-field.js +104 -0
  109. package/dist/esm/tag-field/types.js +1 -0
  110. package/dist/esm/tag-field/use-tag-field-state.js +101 -0
  111. package/dist/esm/tag-field/use-tag-field.js +186 -0
  112. package/dist/esm/text-area/index.js +2 -0
  113. package/dist/esm/text-area/text-area.js +26 -0
  114. package/dist/esm/text-area-field/index.js +2 -0
  115. package/dist/esm/text-area-field/text-area-field.js +46 -0
  116. package/dist/esm/text-field/index.js +2 -0
  117. package/dist/esm/text-field/text-field.js +46 -0
  118. package/dist/esm/toggle/index.js +2 -0
  119. package/dist/esm/toggle/toggle.js +46 -0
  120. package/dist/types/banner/banner.d.ts +19 -0
  121. package/dist/types/banner/banner.d.ts.map +1 -0
  122. package/dist/types/banner/index.d.ts +2 -0
  123. package/dist/types/banner/index.d.ts.map +1 -0
  124. package/dist/types/button/button.d.ts +48 -0
  125. package/dist/types/button/button.d.ts.map +1 -0
  126. package/dist/types/button/index.d.ts +2 -0
  127. package/dist/types/button/index.d.ts.map +1 -0
  128. package/dist/types/combo-box/combo-box-fuzzy.d.ts +11 -0
  129. package/dist/types/combo-box/combo-box-fuzzy.d.ts.map +1 -0
  130. package/dist/types/combo-box/combo-box.d.ts +50 -0
  131. package/dist/types/combo-box/combo-box.d.ts.map +1 -0
  132. package/dist/types/combo-box/index.d.ts +3 -0
  133. package/dist/types/combo-box/index.d.ts.map +1 -0
  134. package/dist/types/field/field.d.ts +14 -0
  135. package/dist/types/field/field.d.ts.map +1 -0
  136. package/dist/types/field/index.d.ts +2 -0
  137. package/dist/types/field/index.d.ts.map +1 -0
  138. package/dist/types/govt-banner/govt-banner.d.ts +39 -0
  139. package/dist/types/govt-banner/govt-banner.d.ts.map +1 -0
  140. package/dist/types/govt-banner/index.d.ts +2 -0
  141. package/dist/types/govt-banner/index.d.ts.map +1 -0
  142. package/dist/types/hooks/index.d.ts +2 -0
  143. package/dist/types/hooks/index.d.ts.map +1 -0
  144. package/dist/types/hooks/use-callback-ref.d.ts +6 -0
  145. package/dist/types/hooks/use-callback-ref.d.ts.map +1 -0
  146. package/dist/types/hooks/use-controllable-state.d.ts +11 -0
  147. package/dist/types/hooks/use-controllable-state.d.ts.map +1 -0
  148. package/dist/types/index.d.mts +16 -0
  149. package/dist/types/index.d.ts +16 -0
  150. package/dist/types/index.d.ts.map +1 -0
  151. package/dist/types/input/index.d.ts +2 -0
  152. package/dist/types/input/index.d.ts.map +1 -0
  153. package/dist/types/input/input.d.ts +9 -0
  154. package/dist/types/input/input.d.ts.map +1 -0
  155. package/dist/types/ripple/index.d.ts +3 -0
  156. package/dist/types/ripple/index.d.ts.map +1 -0
  157. package/dist/types/ripple/ripple.d.ts +10 -0
  158. package/dist/types/ripple/ripple.d.ts.map +1 -0
  159. package/dist/types/ripple/use-ripple.d.ts +15 -0
  160. package/dist/types/ripple/use-ripple.d.ts.map +1 -0
  161. package/dist/types/skip-nav-link/index.d.ts +2 -0
  162. package/dist/types/skip-nav-link/index.d.ts.map +1 -0
  163. package/dist/types/skip-nav-link/skip-nav-link.d.ts +11 -0
  164. package/dist/types/skip-nav-link/skip-nav-link.d.ts.map +1 -0
  165. package/dist/types/spinner/index.d.ts +3 -0
  166. package/dist/types/spinner/index.d.ts.map +1 -0
  167. package/dist/types/spinner/spinner.d.ts +4 -0
  168. package/dist/types/spinner/spinner.d.ts.map +1 -0
  169. package/dist/types/spinner/use-spinner.d.ts +66 -0
  170. package/dist/types/spinner/use-spinner.d.ts.map +1 -0
  171. package/dist/types/system/react-utils/context.d.ts +27 -0
  172. package/dist/types/system/react-utils/context.d.ts.map +1 -0
  173. package/dist/types/system/react-utils/index.d.ts +2 -0
  174. package/dist/types/system/react-utils/index.d.ts.map +1 -0
  175. package/dist/types/system/types.d.ts +63 -0
  176. package/dist/types/system/types.d.ts.map +1 -0
  177. package/dist/types/system/utils.d.ts +856 -0
  178. package/dist/types/system/utils.d.ts.map +1 -0
  179. package/dist/types/tag-field/index.d.ts +2 -0
  180. package/dist/types/tag-field/index.d.ts.map +1 -0
  181. package/dist/types/tag-field/tag-field-list.d.ts +22 -0
  182. package/dist/types/tag-field/tag-field-list.d.ts.map +1 -0
  183. package/dist/types/tag-field/tag-field-root.d.ts +14 -0
  184. package/dist/types/tag-field/tag-field-root.d.ts.map +1 -0
  185. package/dist/types/tag-field/tag-field-state-context.d.ts +12 -0
  186. package/dist/types/tag-field/tag-field-state-context.d.ts.map +1 -0
  187. package/dist/types/tag-field/tag-field-tag-list.d.ts +17 -0
  188. package/dist/types/tag-field/tag-field-tag-list.d.ts.map +1 -0
  189. package/dist/types/tag-field/tag-field-trigger.d.ts +8 -0
  190. package/dist/types/tag-field/tag-field-trigger.d.ts.map +1 -0
  191. package/dist/types/tag-field/tag-field.d.ts +3 -0
  192. package/dist/types/tag-field/tag-field.d.ts.map +1 -0
  193. package/dist/types/tag-field/types.d.ts +71 -0
  194. package/dist/types/tag-field/types.d.ts.map +1 -0
  195. package/dist/types/tag-field/use-tag-field-state.d.ts +35 -0
  196. package/dist/types/tag-field/use-tag-field-state.d.ts.map +1 -0
  197. package/dist/types/tag-field/use-tag-field.d.ts +48 -0
  198. package/dist/types/tag-field/use-tag-field.d.ts.map +1 -0
  199. package/dist/types/text-area/index.d.ts +2 -0
  200. package/dist/types/text-area/index.d.ts.map +1 -0
  201. package/dist/types/text-area/text-area.d.ts +9 -0
  202. package/dist/types/text-area/text-area.d.ts.map +1 -0
  203. package/dist/types/text-area-field/index.d.ts +2 -0
  204. package/dist/types/text-area-field/index.d.ts.map +1 -0
  205. package/dist/types/text-area-field/text-area-field.d.ts +12 -0
  206. package/dist/types/text-area-field/text-area-field.d.ts.map +1 -0
  207. package/dist/types/text-field/index.d.ts +2 -0
  208. package/dist/types/text-field/index.d.ts.map +1 -0
  209. package/dist/types/text-field/text-field.d.ts +12 -0
  210. package/dist/types/text-field/text-field.d.ts.map +1 -0
  211. package/dist/types/toggle/index.d.ts +2 -0
  212. package/dist/types/toggle/index.d.ts.map +1 -0
  213. package/dist/types/toggle/toggle.d.ts +22 -0
  214. package/dist/types/toggle/toggle.d.ts.map +1 -0
  215. package/package.json +93 -0
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { useMemo, useCallback, useRef, useState } from 'react';
4
+ import { useResizeObserver } from '@react-aria/utils';
5
+ import { get } from 'lodash-es';
6
+ import { useFilter } from 'react-aria';
7
+ import { useSlottedContext, FormContext, Provider, LabelContext, PopoverContext, InputContext, TextContext, GroupContext, FieldErrorContext } from 'react-aria-components';
8
+ import { removeDataAttributes } from '../system/utils.js';
9
+ import { TagFieldListContext } from './tag-field-list.js';
10
+ import { TagFieldStateContext } from './tag-field-state-context.js';
11
+ import { TagFieldTriggerContext } from './tag-field-trigger.js';
12
+ import { useTagField } from './use-tag-field.js';
13
+ import { useTagFieldState } from './use-tag-field-state.js';
14
+
15
+ const calculateEstimatedRowHeight = (size) => {
16
+ switch (size) {
17
+ case "xs":
18
+ return 44;
19
+ case "sm":
20
+ return 44;
21
+ case "md":
22
+ return 48;
23
+ }
24
+ };
25
+ function TagFieldRoot({
26
+ children,
27
+ virtualRowHeight: _virtualRowHeight,
28
+ ...props
29
+ }) {
30
+ const { itemToKey: defaultItemToKey, itemToText: defaultItemToText } = props;
31
+ const virtualRowHeight = useMemo(
32
+ () => _virtualRowHeight ?? calculateEstimatedRowHeight(props.size ?? "md"),
33
+ [_virtualRowHeight, props.size]
34
+ );
35
+ const { contains } = useFilter({ sensitivity: "base" });
36
+ const itemToText = useCallback(
37
+ (item) => {
38
+ if (defaultItemToText) return defaultItemToText(item);
39
+ return String(get(item, "textValue") ?? String(item));
40
+ },
41
+ [defaultItemToText]
42
+ );
43
+ const itemToKey = useCallback(
44
+ (item) => {
45
+ if (defaultItemToKey) return defaultItemToKey(item);
46
+ return String(get(item, "id") ?? String(item));
47
+ },
48
+ [defaultItemToKey]
49
+ );
50
+ const onSelectionChange = useCallback(
51
+ (nextItems) => {
52
+ if (props.onSelectionChange) {
53
+ props.onSelectionChange(new Set(nextItems.map(itemToKey)));
54
+ }
55
+ },
56
+ [props, itemToKey]
57
+ );
58
+ const state = useTagFieldState({
59
+ ...props,
60
+ itemToKey,
61
+ itemToText,
62
+ onSelectionChange,
63
+ defaultFilter: props.defaultFilter || contains
64
+ });
65
+ const { validationBehavior: formValidationBehavior } = useSlottedContext(FormContext) || {};
66
+ props.validationBehavior ?? formValidationBehavior ?? "native";
67
+ const fieldRef = useRef(null);
68
+ const popoverRef = useRef(null);
69
+ const listBoxRef = useRef(null);
70
+ const labelRef = useRef(null);
71
+ const inputRef = useRef(null);
72
+ const buttonRef = useRef(null);
73
+ const {
74
+ tagFieldProps,
75
+ buttonProps,
76
+ inputProps,
77
+ labelProps,
78
+ listBoxProps,
79
+ descriptionProps,
80
+ errorMessageProps,
81
+ rowVirtualizer,
82
+ ...validation
83
+ } = useTagField(
84
+ {
85
+ ...removeDataAttributes(props),
86
+ virtualRowHeight,
87
+ itemToKey,
88
+ itemToText,
89
+ inputRef,
90
+ listBoxRef,
91
+ labelRef,
92
+ buttonRef},
93
+ state
94
+ );
95
+ const [menuWidth, setMenuWidth] = useState(null);
96
+ const onResize = useCallback(() => {
97
+ if (fieldRef.current) {
98
+ const fieldRect = fieldRef.current.getBoundingClientRect();
99
+ setMenuWidth(fieldRect.right - fieldRect.left + "px");
100
+ }
101
+ }, []);
102
+ useResizeObserver({
103
+ ref: fieldRef,
104
+ onResize
105
+ });
106
+ const renderPropsState = useMemo(
107
+ () => ({
108
+ isOpen: tagFieldProps.isOpen,
109
+ isDisabled: props.isDisabled || false,
110
+ isInvalid: validation.isInvalid || false,
111
+ isRequired: props.isRequired || false
112
+ }),
113
+ [
114
+ tagFieldProps.isOpen,
115
+ props.isDisabled,
116
+ props.isRequired,
117
+ validation.isInvalid
118
+ ]
119
+ );
120
+ return /* @__PURE__ */ jsx(
121
+ Provider,
122
+ {
123
+ values: [
124
+ [
125
+ TagFieldStateContext,
126
+ {
127
+ ...state,
128
+ ...tagFieldProps,
129
+ size: props.size,
130
+ variant: props.variant
131
+ }
132
+ ],
133
+ [LabelContext, labelProps],
134
+ [TagFieldListContext, { ...listBoxProps, rowVirtualizer }],
135
+ [TagFieldTriggerContext, buttonProps],
136
+ [
137
+ PopoverContext,
138
+ {
139
+ ref: popoverRef,
140
+ triggerRef: fieldRef,
141
+ scrollRef: listBoxRef,
142
+ placement: "bottom start",
143
+ isOpen: tagFieldProps.isOpen,
144
+ isNonModal: true,
145
+ trigger: "TagField",
146
+ style: { "--trigger-width": menuWidth }
147
+ }
148
+ ],
149
+ [InputContext, inputProps],
150
+ [
151
+ TextContext,
152
+ {
153
+ slots: {
154
+ description: descriptionProps,
155
+ errorMessage: errorMessageProps
156
+ }
157
+ }
158
+ ],
159
+ [
160
+ GroupContext,
161
+ {
162
+ isDisabled: props.isDisabled || false,
163
+ isInvalid: validation.isInvalid,
164
+ ref: fieldRef
165
+ }
166
+ ],
167
+ [FieldErrorContext, validation]
168
+ ],
169
+ children: typeof children === "function" ? children({
170
+ ...renderPropsState,
171
+ defaultChildren: null,
172
+ items: state.items,
173
+ selectedItems: state.selectedItems,
174
+ highlightedIndex: tagFieldProps.highlightedIndex,
175
+ getSelectedItemProps: tagFieldProps.getSelectedItemProps,
176
+ removeSelectedItem: tagFieldProps.removeSelectedItem
177
+ }) : children
178
+ }
179
+ );
180
+ }
181
+
182
+ export { TagFieldRoot };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ import { createContext } from 'react';
3
+
4
+ const TagFieldStateContext = (
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ createContext(null)
7
+ );
8
+
9
+ export { TagFieldStateContext };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useContext, useCallback } from 'react';
4
+ import { TagFieldStateContext } from './tag-field-state-context.js';
5
+ import X from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/x.js';
6
+
7
+ const TagFieldTagList = ({
8
+ classNames,
9
+ ...props
10
+ }) => {
11
+ const {
12
+ selectedItems,
13
+ getSelectedItemProps,
14
+ removeSelectedItem,
15
+ isDisabled,
16
+ isReadOnly
17
+ } = useContext(TagFieldStateContext);
18
+ const handleRemoveSelectedItem = useCallback(
19
+ (item) => () => {
20
+ if (isDisabled || isReadOnly) return;
21
+ removeSelectedItem(item);
22
+ },
23
+ [isDisabled, isReadOnly, removeSelectedItem]
24
+ );
25
+ if (props.children !== void 0 && typeof props.children !== "function") {
26
+ return props.children;
27
+ }
28
+ return selectedItems.map((selectedItem, index) => {
29
+ const itemProps = getSelectedItemProps({
30
+ disabled: isDisabled,
31
+ readOnly: isReadOnly,
32
+ selectedItem,
33
+ index
34
+ });
35
+ if (typeof props.children === "function") {
36
+ return props.children({
37
+ item: selectedItem,
38
+ removeSelectedItem: handleRemoveSelectedItem(selectedItem),
39
+ isDisabled,
40
+ isReadOnly,
41
+ itemProps
42
+ });
43
+ }
44
+ return /* @__PURE__ */ jsxs(
45
+ "span",
46
+ {
47
+ className: classNames?.tag,
48
+ ...itemProps,
49
+ children: [
50
+ /* @__PURE__ */ jsx("span", { className: classNames?.tagText, children: selectedItem.textValue }),
51
+ /* @__PURE__ */ jsx(
52
+ X,
53
+ {
54
+ className: classNames?.tagIcon,
55
+ onClick: (e) => {
56
+ e.stopPropagation();
57
+ handleRemoveSelectedItem(selectedItem)();
58
+ }
59
+ }
60
+ )
61
+ ]
62
+ },
63
+ `selected-item-${index}`
64
+ );
65
+ });
66
+ };
67
+
68
+ export { TagFieldTagList };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { createContext } from 'react';
4
+ import { useContextProps } from 'react-aria-components';
5
+ import { forwardRef } from '../system/utils.js';
6
+
7
+ const TagFieldTriggerContext = createContext({});
8
+ const TagFieldTrigger = forwardRef(
9
+ (props, ref) => {
10
+ [props, ref] = useContextProps(props, ref, TagFieldTriggerContext);
11
+ return /* @__PURE__ */ jsx(
12
+ "button",
13
+ {
14
+ "aria-label": "toggle menu",
15
+ type: "button",
16
+ ...props,
17
+ slot: props.slot || void 0,
18
+ ref
19
+ }
20
+ );
21
+ }
22
+ );
23
+
24
+ export { TagFieldTrigger, TagFieldTriggerContext };
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ "use client";
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { createElement } from 'react';
5
+ import { tagFieldStyles } from '@opengovsg/oui-theme';
6
+ import { composeRenderProps, Popover } from 'react-aria-components';
7
+ import { TagFieldList, TagFieldListItem } from './tag-field-list.js';
8
+ import { TagFieldRoot } from './tag-field-root.js';
9
+ import { TagFieldTagList } from './tag-field-tag-list.js';
10
+ import { TagFieldTrigger } from './tag-field-trigger.js';
11
+ import ChevronDown from '../node_modules/.pnpm/lucide-react@0.475.0_react@19.0.0/node_modules/lucide-react/dist/esm/icons/chevron-down.js';
12
+ import { Label, FieldGroup, Description, FieldError } from '../field/field.js';
13
+ import { Input } from '../input/input.js';
14
+
15
+ function TagField({
16
+ classNames,
17
+ children,
18
+ ...props
19
+ }) {
20
+ const styles = tagFieldStyles(props);
21
+ return /* @__PURE__ */ jsxs(TagFieldRoot, { ...props, children: [
22
+ /* @__PURE__ */ jsxs("div", { className: styles.root({ className: classNames?.root }), children: [
23
+ /* @__PURE__ */ jsx(
24
+ Label,
25
+ {
26
+ size: props.size,
27
+ className: styles.label({ className: classNames?.label }),
28
+ children: props.label
29
+ }
30
+ ),
31
+ /* @__PURE__ */ jsxs(
32
+ FieldGroup,
33
+ {
34
+ className: composeRenderProps(
35
+ classNames?.group,
36
+ (className, renderProps) => styles.group({ className, ...renderProps })
37
+ ),
38
+ children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-row flex-wrap gap-1", children: [
40
+ /* @__PURE__ */ jsx(
41
+ TagFieldTagList,
42
+ {
43
+ classNames: {
44
+ tag: styles.tag({ className: classNames?.tag }),
45
+ tagText: styles.tagText({ className: classNames?.tagText }),
46
+ tagIcon: styles.tagIcon({ className: classNames?.tagIcon })
47
+ }
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx(
51
+ Input,
52
+ {
53
+ variant: "unstyled",
54
+ size: props.size,
55
+ className: composeRenderProps(
56
+ classNames?.field,
57
+ (className, renderProps) => styles.field({ className, ...renderProps })
58
+ )
59
+ }
60
+ )
61
+ ] }),
62
+ /* @__PURE__ */ jsx(
63
+ TagFieldTrigger,
64
+ {
65
+ className: styles.trigger({ className: classNames?.trigger }),
66
+ children: /* @__PURE__ */ jsx(ChevronDown, {})
67
+ }
68
+ )
69
+ ]
70
+ }
71
+ ),
72
+ props.description && /* @__PURE__ */ jsx(
73
+ Description,
74
+ {
75
+ size: props.size,
76
+ className: styles.description({
77
+ className: classNames?.description
78
+ }),
79
+ children: props.description
80
+ }
81
+ ),
82
+ /* @__PURE__ */ jsx(
83
+ FieldError,
84
+ {
85
+ size: props.size,
86
+ className: styles.error({
87
+ className: classNames?.error
88
+ }),
89
+ children: props.errorMessage
90
+ }
91
+ )
92
+ ] }),
93
+ /* @__PURE__ */ jsx(Popover, { children: /* @__PURE__ */ jsx(
94
+ TagFieldList,
95
+ {
96
+ className: styles.list({ className: classNames?.list }),
97
+ itemClassNames: props.itemClassNames,
98
+ children: ({ key, ...props2 }) => children ? children({ key, ...props2 }) : /* @__PURE__ */ createElement(TagFieldListItem, { ...props2, key })
99
+ }
100
+ ) })
101
+ ] });
102
+ }
103
+
104
+ export { TagField };
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ import { useMemo, useCallback } from 'react';
3
+ import { useFormValidationState } from '@react-stately/form';
4
+ import { useControlledState } from '@react-stately/utils';
5
+ import { useControllableState } from '../hooks/use-controllable-state.js';
6
+
7
+ function useTagFieldState(props) {
8
+ const { itemToText, itemToKey, defaultFilter } = props;
9
+ const itemsByKey = useMemo(() => {
10
+ const items = props.items ?? props.defaultItems ?? [];
11
+ return items.reduce(
12
+ (acc, item) => {
13
+ acc[itemToKey(item)] = item;
14
+ return acc;
15
+ },
16
+ {}
17
+ ) ?? {};
18
+ }, [itemToKey, props.defaultItems, props.items]);
19
+ const getSelectedItemsByKey = useCallback(
20
+ (keys) => {
21
+ if (!keys) return;
22
+ return [...keys].map((key) => itemsByKey[key]);
23
+ },
24
+ [itemsByKey]
25
+ );
26
+ const [selectedItems, setSelectedItems] = useControllableState({
27
+ defaultValue: getSelectedItemsByKey(props.defaultSelectedKeys) ?? [],
28
+ value: getSelectedItemsByKey(props.selectedKeys),
29
+ onChange: props.onSelectionChange
30
+ });
31
+ const defaultInputValue = props.defaultInputValue ?? "";
32
+ const [inputValue, setInputValue] = useControlledState(
33
+ props.inputValue,
34
+ defaultInputValue,
35
+ props.onInputChange
36
+ );
37
+ const controlledSelectedKeys = useMemo(
38
+ () => new Set(selectedItems.map(itemToKey)),
39
+ [itemToKey, selectedItems]
40
+ );
41
+ const validation = useFormValidationState({
42
+ ...props,
43
+ value: useMemo(
44
+ () => ({
45
+ inputValue,
46
+ selectedKeys: controlledSelectedKeys
47
+ }),
48
+ [controlledSelectedKeys, inputValue]
49
+ )
50
+ });
51
+ const filteredItems = useMemo(
52
+ () => (
53
+ // No default filter if items are controlled.
54
+ !!props.items || !defaultFilter ? props.items ?? [] : filterItems({
55
+ items: props.defaultItems ?? [],
56
+ inputValue,
57
+ itemToText,
58
+ itemToKey,
59
+ selectedKeys: controlledSelectedKeys,
60
+ filter: defaultFilter
61
+ })
62
+ ),
63
+ [
64
+ props.items,
65
+ props.defaultItems,
66
+ defaultFilter,
67
+ inputValue,
68
+ itemToText,
69
+ itemToKey,
70
+ controlledSelectedKeys
71
+ ]
72
+ );
73
+ return {
74
+ items: filteredItems,
75
+ disabledKeys: props.disabledKeys,
76
+ inputValue,
77
+ setInputValue,
78
+ selectedItems,
79
+ setSelectedItems,
80
+ itemToText,
81
+ itemToKey,
82
+ ...validation
83
+ };
84
+ }
85
+ function filterItems({
86
+ items,
87
+ inputValue,
88
+ itemToText,
89
+ itemToKey,
90
+ filter,
91
+ selectedKeys
92
+ }) {
93
+ return items.filter((item) => {
94
+ const isSelected = selectedKeys ? selectedKeys.has(itemToKey(item)) : false;
95
+ if (isSelected) return false;
96
+ const matchesFilter = filter(itemToText(item), inputValue);
97
+ return matchesFilter;
98
+ });
99
+ }
100
+
101
+ export { useTagFieldState };
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ import { useRef, useMemo, useCallback } from 'react';
3
+ import { mergeProps } from '@react-aria/utils';
4
+ import { useVirtualizer } from '@tanstack/react-virtual';
5
+ import { useMultipleSelection, useCombobox } from 'downshift';
6
+ import { omit } from 'lodash-es';
7
+ import { useTextField } from 'react-aria';
8
+
9
+ function useTagField(props, state) {
10
+ let { buttonRef } = props;
11
+ const {
12
+ inputRef,
13
+ listBoxRef,
14
+ labelRef,
15
+ shouldCloseOnBlur,
16
+ // TODO: Handle these states
17
+ isReadOnly,
18
+ isDisabled,
19
+ itemToKey,
20
+ itemToText,
21
+ label,
22
+ virtualRowHeight = 40
23
+ } = props;
24
+ const backupBtnRef = useRef(null);
25
+ buttonRef = buttonRef ?? backupBtnRef;
26
+ const {
27
+ selectedItems,
28
+ setSelectedItems,
29
+ inputValue,
30
+ setInputValue,
31
+ items,
32
+ disabledKeys
33
+ } = state;
34
+ const { getSelectedItemProps, getDropdownProps, removeSelectedItem } = useMultipleSelection({
35
+ selectedItems,
36
+ onStateChange({ selectedItems: newSelectedItems, type }) {
37
+ switch (type) {
38
+ case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownBackspace:
39
+ case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete:
40
+ case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
41
+ case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem: {
42
+ if (isDisabled) return;
43
+ setSelectedItems(newSelectedItems ?? []);
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ });
49
+ const disabledKeysSet = useMemo(() => {
50
+ return new Set(disabledKeys);
51
+ }, [disabledKeys]);
52
+ const rowVirtualizer = useVirtualizer({
53
+ count: items.length,
54
+ getScrollElement: () => listBoxRef.current,
55
+ estimateSize: () => virtualRowHeight,
56
+ getItemKey: useCallback(
57
+ (index) => itemToKey(items[index]),
58
+ [itemToKey, items]
59
+ ),
60
+ overscan: 2
61
+ });
62
+ const {
63
+ isOpen,
64
+ getToggleButtonProps,
65
+ getLabelProps,
66
+ getMenuProps,
67
+ getInputProps,
68
+ highlightedIndex,
69
+ getItemProps
70
+ } = useCombobox({
71
+ itemToString: (item) => {
72
+ if (!item) {
73
+ return "";
74
+ }
75
+ return itemToText(item);
76
+ },
77
+ isItemDisabled: (item) => isDisabled || isReadOnly || disabledKeysSet.has(itemToKey(item)),
78
+ items,
79
+ scrollIntoView: () => {
80
+ },
81
+ onHighlightedIndexChange: ({ highlightedIndex: highlightedIndex2, type }) => {
82
+ if (type !== useCombobox.stateChangeTypes.MenuMouseLeave && highlightedIndex2 >= 0) {
83
+ rowVirtualizer.scrollToIndex(highlightedIndex2);
84
+ }
85
+ },
86
+ defaultHighlightedIndex: 0,
87
+ // after selection, highlight the first item.
88
+ selectedItem: null,
89
+ inputValue,
90
+ stateReducer(_state, actionAndChanges) {
91
+ const { changes, type } = actionAndChanges;
92
+ switch (type) {
93
+ case useCombobox.stateChangeTypes.ItemClick:
94
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
95
+ case useCombobox.stateChangeTypes.InputBlur: {
96
+ return {
97
+ ...changes,
98
+ isOpen: shouldCloseOnBlur === false ? true : changes.isOpen
99
+ };
100
+ }
101
+ default:
102
+ return changes;
103
+ }
104
+ },
105
+ onStateChange({
106
+ inputValue: newInputValue,
107
+ type,
108
+ selectedItem: newSelectedItem
109
+ }) {
110
+ switch (type) {
111
+ case useCombobox.stateChangeTypes.InputBlur:
112
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
113
+ case useCombobox.stateChangeTypes.ItemClick: {
114
+ if (newSelectedItem) {
115
+ setSelectedItems((prev) => [.../* @__PURE__ */ new Set([...prev, newSelectedItem])]);
116
+ }
117
+ setInputValue("");
118
+ break;
119
+ }
120
+ case useCombobox.stateChangeTypes.InputChange:
121
+ setInputValue(newInputValue ?? "");
122
+ break;
123
+ }
124
+ }
125
+ });
126
+ const { isInvalid, validationErrors, validationDetails } = state.displayValidation;
127
+ const inputProps = getInputProps({
128
+ ref: inputRef,
129
+ "aria-label": props["aria-label"],
130
+ "aria-labelledby": props["aria-labelledby"],
131
+ ...getDropdownProps({ preventKeyAction: isOpen }),
132
+ // Somehow adding this will allow the input to be updated properly, else
133
+ // it may sometimes lag behind a single state.
134
+ // Was also in the previous downshift docs but they removed it for some reason.
135
+ // See https://github.com/downshift-js/downshift/pull/1576/files#diff-d32b6994832dda99d96f207e964a0ef27102128c532ea9492949f21ec0cf58d3
136
+ onChange: (e) => setInputValue(e.target.value)
137
+ });
138
+ const {
139
+ labelProps,
140
+ inputProps: newInputProps,
141
+ descriptionProps,
142
+ errorMessageProps
143
+ } = useTextField(
144
+ {
145
+ isReadOnly,
146
+ isDisabled,
147
+ isInvalid,
148
+ errorMessage: props.errorMessage,
149
+ description: props.description,
150
+ label,
151
+ ...inputProps,
152
+ onChange: () => {
153
+ },
154
+ value: state.inputValue
155
+ },
156
+ inputRef
157
+ );
158
+ return {
159
+ tagFieldProps: {
160
+ isDisabled: newInputProps.disabled ?? false,
161
+ isReadOnly: newInputProps.readOnly ?? false,
162
+ isInvalid,
163
+ getItemProps,
164
+ highlightedIndex,
165
+ isOpen,
166
+ getSelectedItemProps,
167
+ removeSelectedItem
168
+ },
169
+ labelProps: mergeProps(getLabelProps({ ref: labelRef }), labelProps),
170
+ // Remove onKeyDown from newInputProps to prevent it from being called twice, resulting in arrow keys moving two items.
171
+ inputProps: mergeProps(inputProps, omit(newInputProps, "onKeyDown")),
172
+ buttonProps: getToggleButtonProps({
173
+ ref: buttonRef,
174
+ disabled: isDisabled
175
+ }),
176
+ listBoxProps: getMenuProps({ ref: listBoxRef }, { suppressRefError: true }),
177
+ descriptionProps,
178
+ errorMessageProps,
179
+ isInvalid,
180
+ validationErrors,
181
+ validationDetails,
182
+ rowVirtualizer
183
+ };
184
+ }
185
+
186
+ export { useTagField };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ export { TextArea } from './text-area.js';