@dt-dds/react-dropdown 1.0.0-beta.100

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,374 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+
33
+ // src/Dropdown.tsx
34
+ import {
35
+ forwardRef as forwardRef2,
36
+ useCallback,
37
+ useRef
38
+ } from "react";
39
+ import { FocusTrap } from "focus-trap-react";
40
+ import { Portal, useClickOutside } from "@dt-dds/react-core";
41
+
42
+ // src/components/option/DropdownOption.tsx
43
+ import {
44
+ forwardRef
45
+ } from "react";
46
+
47
+ // src/components/option/DropdownOption.styled.ts
48
+ import styled from "@emotion/styled";
49
+ var DropdownOptionStyled = styled.li`
50
+ ${({ theme }) => `
51
+ ${theme.fontStyles.bodyMdRegular};
52
+
53
+ display: block;
54
+ color: ${theme.palette.content.default};
55
+ padding: ${theme.spacing.spacing_40} ${theme.spacing.spacing_50};
56
+ list-style: none;
57
+ text-overflow: ellipsis;
58
+ overflow-x: hidden;
59
+ text-wrap: nowrap;
60
+
61
+ &:not([aria-disabled="true"]) {
62
+ &[aria-selected="true"], &[aria-selected="true"] span {
63
+ ${theme.fontStyles.bodyMdBold};
64
+ }
65
+
66
+ &:hover, &[data-highlighted="true"] {
67
+ background: ${theme.palette.accent.light};
68
+ cursor: pointer;
69
+ }
70
+
71
+ &:focus-visible {
72
+ outline: 2px solid ${theme.palette.primary.default};
73
+ outline-offset: -2px;
74
+ }
75
+ }
76
+
77
+ &[aria-disabled="true"] {
78
+ color: ${theme.palette.content.light};
79
+ cursor: not-allowed;
80
+
81
+ &[aria-selected="true"] {
82
+ background-color: ${theme.palette.surface.light};
83
+ }
84
+ }
85
+ `}
86
+ `;
87
+
88
+ // src/components/option/DropdownOption.tsx
89
+ import { jsx } from "react/jsx-runtime";
90
+ var DropdownOption = forwardRef(
91
+ (_a, ref) => {
92
+ var _b = _a, {
93
+ dataTestId,
94
+ children,
95
+ style,
96
+ isDisabled,
97
+ isSelected = false,
98
+ isHighlighted = false,
99
+ onClick
100
+ } = _b, rest = __objRest(_b, [
101
+ "dataTestId",
102
+ "children",
103
+ "style",
104
+ "isDisabled",
105
+ "isSelected",
106
+ "isHighlighted",
107
+ "onClick"
108
+ ]);
109
+ const testId = dataTestId != null ? dataTestId : "dropdown-option";
110
+ const handleClick = (e) => {
111
+ if (isDisabled) {
112
+ e.preventDefault();
113
+ e.stopPropagation();
114
+ return;
115
+ }
116
+ onClick == null ? void 0 : onClick(e);
117
+ };
118
+ const handleKeyDown = (e) => {
119
+ if (e.code === "Enter" || e.code === "Space") {
120
+ e.preventDefault();
121
+ onClick == null ? void 0 : onClick(e);
122
+ }
123
+ };
124
+ return /* @__PURE__ */ jsx(
125
+ DropdownOptionStyled,
126
+ __spreadProps(__spreadValues(__spreadValues({
127
+ onClick: handleClick,
128
+ onKeyDown: handleKeyDown,
129
+ tabIndex: isDisabled ? -1 : 0
130
+ }, !isSelected && { role: "menuitem" }), rest), {
131
+ "aria-disabled": isDisabled,
132
+ "aria-selected": isSelected,
133
+ "data-highlighted": isHighlighted,
134
+ "data-testid": testId,
135
+ ref,
136
+ style,
137
+ children
138
+ })
139
+ );
140
+ }
141
+ );
142
+
143
+ // src/Dropdown.styled.ts
144
+ import styled2 from "@emotion/styled";
145
+ var DropdownStyled = styled2.div`
146
+ list-style-type: none;
147
+ width: 100%;
148
+ overflow: auto;
149
+ `;
150
+
151
+ // src/hooks/useFloatingPosition.ts
152
+ import { useLayoutEffect, useState } from "react";
153
+ import { DROPDOWN_MENU_Z_INDEX } from "@dt-dds/react-core";
154
+ function basePos({
155
+ placement,
156
+ anchor,
157
+ menuWidth,
158
+ menuHeight,
159
+ offsetX,
160
+ offsetY
161
+ }) {
162
+ switch (placement) {
163
+ case "bottom":
164
+ return { top: anchor.bottom + offsetY, left: anchor.left + offsetX };
165
+ case "top":
166
+ return {
167
+ top: anchor.top - offsetY - menuHeight,
168
+ left: anchor.left + offsetX
169
+ };
170
+ case "right":
171
+ return { top: anchor.top + offsetY, left: anchor.right + offsetX };
172
+ case "left":
173
+ return {
174
+ top: anchor.top + offsetY,
175
+ left: anchor.left - offsetX - menuWidth
176
+ };
177
+ }
178
+ }
179
+ function useFloatingPosition(anchorEl, open, {
180
+ offsetX = 0,
181
+ offsetY = 4,
182
+ matchWidth = true,
183
+ minViewportPadding = 8,
184
+ placement = "bottom",
185
+ menuRef
186
+ } = {}) {
187
+ const [style, setStyle] = useState({
188
+ visibility: "hidden",
189
+ position: "fixed"
190
+ });
191
+ useLayoutEffect(() => {
192
+ if (!open || !(anchorEl == null ? void 0 : anchorEl.current)) {
193
+ setStyle({
194
+ visibility: "hidden",
195
+ position: "fixed"
196
+ });
197
+ return;
198
+ }
199
+ const anchorElement = anchorEl == null ? void 0 : anchorEl.current;
200
+ const menuElement = menuRef == null ? void 0 : menuRef.current;
201
+ const updatePosition = () => {
202
+ var _a, _b;
203
+ const vw = window.innerWidth;
204
+ const vh = window.innerHeight;
205
+ const anchor = anchorElement.getBoundingClientRect();
206
+ const menuRect = menuElement == null ? void 0 : menuElement.getBoundingClientRect();
207
+ const menuWidth = matchWidth ? anchor.width : (_a = menuRect == null ? void 0 : menuRect.width) != null ? _a : anchor.width;
208
+ const menuHeight = (_b = menuRect == null ? void 0 : menuRect.height) != null ? _b : 0;
209
+ let { top, left } = basePos({
210
+ placement,
211
+ anchor,
212
+ menuWidth,
213
+ menuHeight,
214
+ offsetX,
215
+ offsetY
216
+ });
217
+ const maxLeft = Math.max(
218
+ minViewportPadding,
219
+ vw - menuWidth - minViewportPadding
220
+ );
221
+ const maxTop = Math.max(
222
+ minViewportPadding,
223
+ vh - menuHeight - minViewportPadding
224
+ );
225
+ left = Math.max(minViewportPadding, Math.min(left, maxLeft));
226
+ top = Math.max(minViewportPadding, Math.min(top, maxTop));
227
+ setStyle({
228
+ position: "fixed",
229
+ top,
230
+ left,
231
+ width: matchWidth ? anchor.width : void 0,
232
+ maxWidth: matchWidth ? anchor.width : 300,
233
+ boxSizing: "border-box",
234
+ zIndex: DROPDOWN_MENU_Z_INDEX
235
+ });
236
+ };
237
+ updatePosition();
238
+ const ro = new ResizeObserver(updatePosition);
239
+ ro.observe(anchorElement);
240
+ if (anchorElement) {
241
+ ro.observe(anchorElement);
242
+ }
243
+ const opts = { passive: true };
244
+ window.addEventListener("scroll", updatePosition, opts);
245
+ window.addEventListener("resize", updatePosition, opts);
246
+ return () => {
247
+ ro.disconnect();
248
+ window.removeEventListener("scroll", updatePosition, opts);
249
+ window.removeEventListener("resize", updatePosition, opts);
250
+ };
251
+ }, [
252
+ menuRef,
253
+ anchorEl,
254
+ open,
255
+ placement,
256
+ offsetX,
257
+ offsetY,
258
+ matchWidth,
259
+ minViewportPadding
260
+ ]);
261
+ return { style };
262
+ }
263
+
264
+ // src/utils/set-ref.ts
265
+ var setRef = (target, node) => {
266
+ if (!target) {
267
+ return;
268
+ }
269
+ if (typeof target === "function") {
270
+ target(node);
271
+ return;
272
+ }
273
+ target.current = node;
274
+ };
275
+
276
+ // src/Dropdown.tsx
277
+ import { jsx as jsx2 } from "react/jsx-runtime";
278
+ var Dropdown = Object.assign(
279
+ forwardRef2(
280
+ (_a, forwardedRef) => {
281
+ var _b = _a, {
282
+ children,
283
+ style,
284
+ dataTestId = "dropdown",
285
+ isOpen = false,
286
+ anchorRef,
287
+ matchWidth = true,
288
+ offsetX,
289
+ offsetY,
290
+ as = "div",
291
+ onClose,
292
+ placement,
293
+ isFocusable = true
294
+ } = _b, rest = __objRest(_b, [
295
+ "children",
296
+ "style",
297
+ "dataTestId",
298
+ "isOpen",
299
+ "anchorRef",
300
+ "matchWidth",
301
+ "offsetX",
302
+ "offsetY",
303
+ "as",
304
+ "onClose",
305
+ "placement",
306
+ "isFocusable"
307
+ ]);
308
+ const localMenuRef = useRef(null);
309
+ const setMenuRef = useCallback(
310
+ (node) => {
311
+ localMenuRef.current = node;
312
+ setRef(forwardedRef, node);
313
+ },
314
+ [forwardedRef]
315
+ );
316
+ const { style: floatingStyle } = useFloatingPosition(
317
+ anchorRef,
318
+ isOpen,
319
+ {
320
+ matchWidth,
321
+ offsetX,
322
+ offsetY,
323
+ placement,
324
+ menuRef: localMenuRef
325
+ }
326
+ );
327
+ useClickOutside({
328
+ refs: [localMenuRef, anchorRef],
329
+ handler: () => onClose == null ? void 0 : onClose()
330
+ });
331
+ const dropdownNode = /* @__PURE__ */ jsx2(
332
+ DropdownStyled,
333
+ __spreadProps(__spreadValues(__spreadValues({
334
+ as,
335
+ "data-testid": dataTestId,
336
+ ref: setMenuRef,
337
+ role: "menu",
338
+ style: __spreadValues(__spreadValues({}, floatingStyle), style)
339
+ }, rest), !isFocusable && {
340
+ onMouseDown: (e) => e.preventDefault()
341
+ }), {
342
+ children
343
+ })
344
+ );
345
+ return /* @__PURE__ */ jsx2(Portal, { isOpen: true, children: isFocusable ? /* @__PURE__ */ jsx2(
346
+ FocusTrap,
347
+ {
348
+ active: isOpen,
349
+ focusTrapOptions: {
350
+ initialFocus: () => {
351
+ var _a2;
352
+ return (_a2 = localMenuRef == null ? void 0 : localMenuRef.current) != null ? _a2 : false;
353
+ },
354
+ fallbackFocus: () => document.body,
355
+ escapeDeactivates: true,
356
+ allowOutsideClick: true,
357
+ onDeactivate: () => {
358
+ onClose == null ? void 0 : onClose();
359
+ },
360
+ tabbableOptions: { displayCheck: "none" }
361
+ },
362
+ children: dropdownNode
363
+ }
364
+ ) : dropdownNode });
365
+ }
366
+ ),
367
+ {
368
+ Option: DropdownOption
369
+ }
370
+ );
371
+ export {
372
+ Dropdown,
373
+ DropdownOption
374
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@dt-dds/react-dropdown",
3
+ "version": "1.0.0-beta.100",
4
+ "license": "MIT",
5
+ "exports": {
6
+ ".": "./dist/index.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist/**"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "lint": "eslint --cache .",
18
+ "test": "jest",
19
+ "test:report": "open ./jest-coverage/lcov-report/index.html",
20
+ "test:update:snapshot": "jest -u"
21
+ },
22
+ "dependencies": {
23
+ "@dt-dds/react-box": "1.0.0-beta.76",
24
+ "@dt-dds/react-core": "1.0.0-beta.58",
25
+ "@dt-dds/react-icon": "1.0.0-beta.61",
26
+ "@dt-dds/react-icon-button": "1.0.0-beta.27",
27
+ "@dt-dds/react-typography": "1.0.0-beta.49",
28
+ "@dt-dds/themes": "1.0.0-beta.13",
29
+ "focus-trap-react": "^11.0.4"
30
+ },
31
+ "devDependencies": {
32
+ "@babel/core": "^7.22.9",
33
+ "@babel/preset-env": "^7.22.9",
34
+ "@babel/preset-react": "^7.22.5",
35
+ "@babel/preset-typescript": "^7.23.3",
36
+ "@emotion/babel-plugin": "^11.11.0",
37
+ "@emotion/css": "^11.7.1",
38
+ "@emotion/jest": "^11.10.0",
39
+ "@emotion/react": "^11.8.2",
40
+ "@emotion/styled": "^11.8.1",
41
+ "@types/react": "^18.0.9",
42
+ "@types/react-dom": "^18.0.4",
43
+ "babel-loader": "^8.3.0",
44
+ "eslint-config-custom": "*",
45
+ "eslint-plugin-storybook": "^9.1.0",
46
+ "jest-config": "*",
47
+ "react": "^18.1.0",
48
+ "react-dom": "^18.2.0",
49
+ "tsconfig": "*",
50
+ "tsup": "^8.5.0",
51
+ "typescript": "^4.5.3"
52
+ },
53
+ "peerDependencies": {
54
+ "@emotion/css": "^11.7.1",
55
+ "@emotion/react": "^11.8.2",
56
+ "@emotion/styled": "^11.8.1",
57
+ "react": ">=17.0.2",
58
+ "react-dom": ">=17.0.2"
59
+ }
60
+ }