@commander-services/cmd-dropdown-portal 1.7.7 → 1.9.0
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/README.md +0 -0
- package/package.json +13 -24
- package/src/CmdDropDownPortal.stories.tsx +15 -0
- package/src/CmdDropdownPortal.test.tsx +30 -0
- package/src/CmdDropdownPortal.tsx +117 -0
- package/src/index.ts +6 -0
- package/src/styles.css +24 -0
- package/src/vite-env.d.ts +1 -0
- package/dist/cmd-dropdown-portal.css +0 -1
- package/dist/index.d.ts +0 -15
- package/dist/index.es.js +0 -709
- package/dist/index.es.js.map +0 -1
- package/dist/index.umd.js +0 -31
- package/dist/index.umd.js.map +0 -1
package/README.md
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commander-services/cmd-dropdown-portal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "A cmd dropdown portal component",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"module": "./
|
|
8
|
-
"types": "./
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"module": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
9
|
"exports": {
|
|
10
|
-
".":
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"require": "./dist/index.umd.js"
|
|
14
|
-
},
|
|
15
|
-
"./styles.css": "./dist/cms-dropdown-portal.css"
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./styles.css": "./src/styles.css",
|
|
12
|
+
"./CHANGELOG.md": "./CHANGELOG.md"
|
|
16
13
|
},
|
|
17
14
|
"files": [
|
|
18
|
-
"
|
|
15
|
+
"src"
|
|
19
16
|
],
|
|
20
17
|
"sideEffects": [
|
|
21
18
|
"**/*.css"
|
|
22
19
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"dev": "vite",
|
|
25
|
-
"build": "tsc --p ./tsconfig-build.json && vite build",
|
|
26
|
-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
27
|
-
"preview": "vite preview"
|
|
28
|
-
},
|
|
29
20
|
"peerDependencies": {
|
|
30
21
|
"react": "^18.2.0",
|
|
31
22
|
"react-dom": "^18.2.0",
|
|
@@ -38,8 +29,6 @@
|
|
|
38
29
|
"@types/react-dom": "^18.3.6",
|
|
39
30
|
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
40
31
|
"@typescript-eslint/parser": "^8.32.1",
|
|
41
|
-
"@vitejs/plugin-react": "^4.4.1",
|
|
42
|
-
"@vitejs/plugin-react-swc": "^3.9.0",
|
|
43
32
|
"autoprefixer": "^10.4.21",
|
|
44
33
|
"eslint": "^9.27.0",
|
|
45
34
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -50,12 +39,12 @@
|
|
|
50
39
|
"react": "^18.3.1",
|
|
51
40
|
"react-dom": "^18.3.1",
|
|
52
41
|
"typescript": "~5.7.3",
|
|
53
|
-
"vite": "^6.3.5",
|
|
54
|
-
"vite-plugin-dts": "^4.5.4",
|
|
55
|
-
"vite-plugin-lib-inject-css": "^2.2.2",
|
|
56
42
|
"vitest": "^3.2.2"
|
|
57
43
|
},
|
|
58
44
|
"dependencies": {
|
|
59
|
-
"@commander-services/cmd-hooks": "
|
|
45
|
+
"@commander-services/cmd-hooks": "1.3.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
|
60
49
|
}
|
|
61
|
-
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// import { useState } from 'react';
|
|
2
|
+
import CmdDropdownPortal, { CmdDropdownPortalProps } from './CmdDropdownPortal';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
|
|
5
|
+
type CmdDropdownPortalStory = StoryObj<CmdDropdownPortalProps>;
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'CmdDropdownPortal',
|
|
9
|
+
component: CmdDropdownPortal,
|
|
10
|
+
args: {
|
|
11
|
+
elementId: 'id',
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<CmdDropdownPortalProps>;
|
|
14
|
+
|
|
15
|
+
export const Default: CmdDropdownPortalStory = {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// import { render } from "@testing-library/react";
|
|
2
|
+
import { describe, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
// import CmdLoader, { CmdLoaderProps } from "./CmdLoader";
|
|
5
|
+
|
|
6
|
+
describe("Test Component CmdLoader", () => {
|
|
7
|
+
// let props: CmdLoaderProps;
|
|
8
|
+
|
|
9
|
+
// beforeEach(() => {
|
|
10
|
+
// props = {
|
|
11
|
+
// alt: "Loading alternative text",
|
|
12
|
+
// inContent: true,
|
|
13
|
+
// oldLoader: true,
|
|
14
|
+
// };
|
|
15
|
+
// });
|
|
16
|
+
|
|
17
|
+
// const renderComponent = () => render(<CmdLoader {...props} />);
|
|
18
|
+
|
|
19
|
+
it("should render loader correctly", () => {
|
|
20
|
+
// const { getAllByAltText } = renderComponent();
|
|
21
|
+
// const component = getAllByAltText(props.alt)[0];
|
|
22
|
+
// expect(component).toHaveAttribute("alt", props.alt);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should render loader inContent correctly", () => {
|
|
26
|
+
// const { getByTestId } = renderComponent();
|
|
27
|
+
// const component = getByTestId("CmdLoaderContent");
|
|
28
|
+
// expect(component).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import CmdHooks from '@commander-services/cmd-hooks';
|
|
4
|
+
|
|
5
|
+
export interface CmdDropdownPortalProps {
|
|
6
|
+
elementId: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
handleOpen: (value: boolean) => void;
|
|
9
|
+
target?: HTMLElement;
|
|
10
|
+
keepWidth?: boolean;
|
|
11
|
+
maxDropdownHeight?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CmdDropdownPortalPosition {
|
|
15
|
+
top?: number;
|
|
16
|
+
bottom?: number;
|
|
17
|
+
left: number;
|
|
18
|
+
width: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function CmdDropdownPortal(props: CmdDropdownPortalProps): JSX.Element | null {
|
|
22
|
+
const [position, setPosition] = useState<CmdDropdownPortalPosition | null>(null);
|
|
23
|
+
const { useOutsideClick, useKeyPress, ESCAPE_KEY } = CmdHooks();
|
|
24
|
+
|
|
25
|
+
const clickOutsideRef: React.RefObject<any> = React.useRef();
|
|
26
|
+
|
|
27
|
+
const handleOutside = () => {
|
|
28
|
+
setTimeout(function () {
|
|
29
|
+
props.handleOpen(false);
|
|
30
|
+
}, 50);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
useOutsideClick(clickOutsideRef, () => {
|
|
34
|
+
handleOutside();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
useKeyPress(ESCAPE_KEY, () => {
|
|
38
|
+
handleOutside();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const isElementFullyVisible = (el: Element, container: Element) => {
|
|
42
|
+
const elRect = el.getBoundingClientRect();
|
|
43
|
+
const containerRect = container.getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
return elRect.bottom >= containerRect.top && elRect.bottom <= containerRect.bottom;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getElementPosition = React.useCallback(() => {
|
|
49
|
+
const element = document.getElementById(props.elementId);
|
|
50
|
+
const wrapperBody =
|
|
51
|
+
document.getElementById('cmd-modal-body') || document.getElementById('cmd-page-wrapper-body');
|
|
52
|
+
if (element && (wrapperBody ? isElementFullyVisible(element, wrapperBody) : true)) {
|
|
53
|
+
const rect = element.getBoundingClientRect();
|
|
54
|
+
const maxDropdownHeight = props.maxDropdownHeight || 360; // max dropdown height
|
|
55
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
56
|
+
const left = rect.left;
|
|
57
|
+
const top = rect.bottom;
|
|
58
|
+
const width = rect.width;
|
|
59
|
+
if (spaceBelow >= maxDropdownHeight || !wrapperBody) {
|
|
60
|
+
setPosition({
|
|
61
|
+
// top: rect.bottom + window.scrollY,
|
|
62
|
+
top,
|
|
63
|
+
left,
|
|
64
|
+
width,
|
|
65
|
+
bottom: undefined,
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
setPosition({
|
|
69
|
+
bottom: window.innerHeight - rect.top - window.scrollY + 3,
|
|
70
|
+
left,
|
|
71
|
+
width,
|
|
72
|
+
top: undefined,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// setPosition({ left, top, width });
|
|
76
|
+
} else {
|
|
77
|
+
setPosition(null);
|
|
78
|
+
}
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
window.addEventListener('scroll', getElementPosition, { capture: true });
|
|
83
|
+
window.addEventListener('resize', getElementPosition, { capture: true });
|
|
84
|
+
return () => {
|
|
85
|
+
window.removeEventListener('scroll', getElementPosition, { capture: true });
|
|
86
|
+
window.removeEventListener('resize', getElementPosition, { capture: true });
|
|
87
|
+
};
|
|
88
|
+
}, [getElementPosition]);
|
|
89
|
+
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
getElementPosition();
|
|
92
|
+
}, [props.elementId]);
|
|
93
|
+
|
|
94
|
+
return position
|
|
95
|
+
? ReactDOM.createPortal(
|
|
96
|
+
<div
|
|
97
|
+
className="font-poppins"
|
|
98
|
+
id={`cmd-dropdown-portal-${props.elementId}`}
|
|
99
|
+
ref={clickOutsideRef}
|
|
100
|
+
data-testid={`cmd-dropdown-portal-${props.elementId}`}
|
|
101
|
+
style={{
|
|
102
|
+
position: 'absolute',
|
|
103
|
+
left: position.left,
|
|
104
|
+
zIndex: 999,
|
|
105
|
+
...(position.top !== undefined ? { top: position.top } : {}),
|
|
106
|
+
...(position.bottom !== undefined ? { bottom: position.bottom } : {}),
|
|
107
|
+
...(!props.keepWidth ? { width: position.width } : {}),
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{props.children}
|
|
111
|
+
</div>,
|
|
112
|
+
props.target || document.body
|
|
113
|
+
)
|
|
114
|
+
: null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default CmdDropdownPortal;
|
package/src/index.ts
ADDED
package/src/styles.css
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* @import 'tailwindcss'; */
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--font-*: initial;
|
|
5
|
+
--font-poppins: Poppins, sans-serif;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
10
|
+
so we've added these compatibility styles to make sure everything still
|
|
11
|
+
looks the same as it did with Tailwind CSS v3.
|
|
12
|
+
|
|
13
|
+
If we ever want to remove these styles, we need to add an explicit border
|
|
14
|
+
color utility to any element that depends on these defaults.
|
|
15
|
+
*/
|
|
16
|
+
@layer base {
|
|
17
|
+
*,
|
|
18
|
+
::after,
|
|
19
|
+
::before,
|
|
20
|
+
::backdrop,
|
|
21
|
+
::file-selector-button {
|
|
22
|
+
border-color: var(--color-gray-200, currentColor);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@theme{--font-*: initial; --font-poppins: Poppins,sans-serif;}@layer base{*,:after,:before,::backdrop,::file-selector-button{border-color:var(--color-gray-200, currentColor)}}
|
package/dist/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { default as default_2 } from 'react';
|
|
2
|
-
|
|
3
|
-
declare function CmdDropdownPortal(props: CmdDropdownPortalProps): JSX.Element | null;
|
|
4
|
-
export default CmdDropdownPortal;
|
|
5
|
-
|
|
6
|
-
export declare interface CmdDropdownPortalProps {
|
|
7
|
-
elementId: string;
|
|
8
|
-
children: default_2.ReactNode;
|
|
9
|
-
handleOpen: (value: boolean) => void;
|
|
10
|
-
target?: HTMLElement;
|
|
11
|
-
keepWidth?: boolean;
|
|
12
|
-
maxDropdownHeight?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export { }
|