@linzjs/windows 1.0.0 → 1.1.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/.storybook/main.ts +26 -6
- package/README.md +129 -6
- package/package.json +34 -12
- package/src/modal/Modal.tsx +9 -5
- package/src/modal/ModalContextProvider.tsx +35 -36
- package/src/modal/PreModal.tsx +45 -0
- package/src/panel/OpenPanelButton.tsx +18 -0
- package/src/panel/OpenPanelIcon.scss +73 -0
- package/src/panel/OpenPanelIcon.tsx +50 -0
- package/src/panel/Panel.scss +34 -0
- package/src/panel/Panel.tsx +150 -0
- package/src/panel/PanelContext.ts +17 -0
- package/src/panel/PanelInstanceContext.ts +41 -0
- package/src/panel/PanelInstanceContextProvider.tsx +47 -0
- package/src/panel/PanelsContext.tsx +36 -0
- package/src/panel/PanelsContextProvider.tsx +140 -0
- package/src/panel/PopoutWindow.tsx +183 -0
- package/src/panel/generateId.ts +23 -0
- package/src/panel/handleStyleSheetsChanges.ts +71 -0
- package/src/stories/Introduction.mdx +18 -0
- package/src/stories/Introduction.stories.tsx +8 -0
- package/src/stories/modal/Modal.mdx +9 -3
- package/src/stories/modal/Modal.stories.tsx +1 -1
- package/src/stories/modal/PreModal.mdx +26 -0
- package/src/stories/modal/PreModal.stories.tsx +27 -0
- package/src/stories/modal/PreModal.tsx +79 -0
- package/src/stories/modal/TestModal.scss +21 -0
- package/src/stories/panel/PanelButtons/ShowPanel.mdx +21 -0
- package/src/stories/panel/PanelButtons/ShowPanel.stories.tsx +27 -0
- package/src/stories/panel/PanelButtons/ShowPanel.tsx +86 -0
- package/src/stories/panel/ShowPanel/ShowPanel.mdx +20 -0
- package/src/stories/panel/ShowPanel/ShowPanel.stories.tsx +27 -0
- package/src/stories/panel/ShowPanel/ShowPanel.tsx +70 -0
- package/src/stories/panel/ShowPanelResizingAgGrid/ShowPanelResizingAgGrid.mdx +21 -0
- package/src/stories/panel/ShowPanelResizingAgGrid/ShowPanelResizingAgGrid.stories.tsx +27 -0
- package/src/stories/panel/ShowPanelResizingAgGrid/ShowPanelResizingStepAgGrid.tsx +164 -0
- package/src/stories/support.js +16 -0
- package/src/util/useInterval.ts +11 -19
package/.storybook/main.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill";
|
|
2
|
+
import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill";
|
|
1
3
|
import type { StorybookConfig } from "@storybook/react-vite";
|
|
4
|
+
import { mergeConfig } from "vite";
|
|
5
|
+
|
|
2
6
|
const config: StorybookConfig = {
|
|
3
|
-
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
4
|
-
addons: [
|
|
5
|
-
"@storybook/addon-links",
|
|
6
|
-
"@storybook/addon-essentials",
|
|
7
|
-
"@storybook/addon-interactions",
|
|
8
|
-
],
|
|
7
|
+
stories: ["../src/**/Introduction.mdx", "../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
|
8
|
+
addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
|
|
9
9
|
framework: {
|
|
10
10
|
name: "@storybook/react-vite",
|
|
11
11
|
options: {},
|
|
@@ -13,5 +13,25 @@ const config: StorybookConfig = {
|
|
|
13
13
|
docs: {
|
|
14
14
|
autodocs: "tag",
|
|
15
15
|
},
|
|
16
|
+
viteFinal: async (config) => {
|
|
17
|
+
return mergeConfig(config, {
|
|
18
|
+
optimizeDeps: {
|
|
19
|
+
esbuildOptions: {
|
|
20
|
+
// Node.js global to browser globalThis
|
|
21
|
+
define: {
|
|
22
|
+
global: "globalThis",
|
|
23
|
+
},
|
|
24
|
+
// Enable esbuild polyfill plugins
|
|
25
|
+
plugins: [
|
|
26
|
+
NodeGlobalsPolyfillPlugin({
|
|
27
|
+
buffer: true,
|
|
28
|
+
process: true,
|
|
29
|
+
}),
|
|
30
|
+
NodeModulesPolyfillPlugin(),
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
},
|
|
16
36
|
};
|
|
17
37
|
export default config;
|
package/README.md
CHANGED
|
@@ -1,10 +1,133 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @linzjs/windows
|
|
2
2
|
|
|
3
|
-
[](https://github.com/semantic-release/semantic-release)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> Reusable promise based windowing component for LINZ / Toitū te whenua.
|
|
7
|
+
|
|
8
|
+
Rect state based modals/windows are painful because they require:
|
|
9
|
+
- shared states for open/closed.
|
|
10
|
+
- callbacks/states for return values.
|
|
11
|
+
- inline modal/window includes, which prevent you from closing the invoking component before the modal/window has
|
|
12
|
+
completed.
|
|
13
|
+
|
|
14
|
+
This module gives you promise based modals/windows which don't require all the state
|
|
15
|
+
based boiler-plate / inline-components.
|
|
4
16
|
|
|
5
|
-
> Reusable windowing component for LINZ / Toitū te whenua.
|
|
6
|
-
>
|
|
7
17
|
## Features
|
|
8
|
-
|
|
18
|
+
- Async HTML dialog based Modals.
|
|
19
|
+
- Draggable and resizeable, pop-in/out Windows.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
```
|
|
23
|
+
npm install @linzjs/windows
|
|
24
|
+
```
|
|
25
|
+
or with Yarn
|
|
26
|
+
```
|
|
27
|
+
yarn add @linzjs/windows
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Demo
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run storybook
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See [Chromatic storybook](https://64a2356b80885af35510b627-gsvwsgdsde.chromatic.com/) for the best documentation.
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
### Modal
|
|
41
|
+
#### Example: Modal Context Provider
|
|
42
|
+
Don't forget to add a ModalContextProvider at the root of your project.
|
|
43
|
+
```
|
|
44
|
+
export const App = () => (
|
|
45
|
+
<ModalContextProvider>
|
|
46
|
+
<div>...the rest of your app...</div>
|
|
47
|
+
</ModalContextProvider>
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Example: Modal Component
|
|
52
|
+
```
|
|
53
|
+
// Extend your props with ModalCallback<RESULT_TYPE> to add a return type, and enable close/resolve
|
|
54
|
+
export interface TestModalProps extends ModalCallback<number> {
|
|
55
|
+
text: string; // A user property
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Close and resolve will be passed to your props magically!
|
|
59
|
+
export const TestModal = ({ text, close, resolve }: TestModalProps) => (
|
|
60
|
+
<Modal>
|
|
61
|
+
<div>
|
|
62
|
+
<div>This is the modal text: '{text}'</div>
|
|
63
|
+
<div>
|
|
64
|
+
<button onClick={close}>Close</button>
|
|
65
|
+
<button onClick={() => resolve(1)}>Return 1</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</Modal>
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Example: Modal Invocation
|
|
73
|
+
```
|
|
74
|
+
export const TestModalUsage = () => {
|
|
75
|
+
// showModal to show modal, modalOwnerRef is only required if you have popout windows
|
|
76
|
+
const { showModal, modalOwnerRef } = useShowModal();
|
|
77
|
+
|
|
78
|
+
const showModalHandler = async () => {
|
|
79
|
+
// Show modal and await result
|
|
80
|
+
const result = await showModal(TestModal, { text: "Text text" });
|
|
81
|
+
|
|
82
|
+
// If result is undefined the modal was closed
|
|
83
|
+
if (!result) return alert("Modal closed");
|
|
84
|
+
|
|
85
|
+
// Otherwise we have a result
|
|
86
|
+
alert(`Modal result is: ${result}`);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Remember to add the modalOwnerRef!
|
|
90
|
+
return (
|
|
91
|
+
<div ref={modalOwnerRef}>
|
|
92
|
+
<button onClick={showModalHandler}>Show modal</button>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Windows
|
|
99
|
+
**Note:** *Windows are called Panels so as not to conflict with browser window types*
|
|
100
|
+
|
|
101
|
+
##### Example: Panel Context Provider
|
|
102
|
+
Don't forget to add a PanelContextProvider at the root of your project.
|
|
103
|
+
```
|
|
104
|
+
export const App = () => (
|
|
105
|
+
<PanelsContextProvider baseZIndex={500}>
|
|
106
|
+
...the rest of your app...
|
|
107
|
+
</PanelsContextProvider>
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
##### Example: Panel Component
|
|
112
|
+
```
|
|
113
|
+
export const TestPanel = () => {
|
|
114
|
+
return (
|
|
115
|
+
<Panel name={"Panel demo"} size={{ width: 640, height: 400 }}>
|
|
116
|
+
<PanelHeader title={"Panel demo"} />
|
|
117
|
+
<PanelContent>
|
|
118
|
+
...PanelContent...
|
|
119
|
+
</PanelContent>
|
|
120
|
+
</Panel>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
##### Example: Panel Invocation
|
|
126
|
+
```
|
|
127
|
+
export const TestPanelUsage = () => {
|
|
128
|
+
const { openPanel } = useContext(PanelsContext);
|
|
129
|
+
|
|
130
|
+
return <button onClick={() => openPanel(TestPanel)}>Show panel</button>;
|
|
131
|
+
};
|
|
132
|
+
```
|
|
9
133
|
|
|
10
|
-
See [Chromatic](https://64a2356b80885af35510b627-gsvwsgdsde.chromatic.com/) for usage.
|
package/package.json
CHANGED
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
"name": "@linzjs/windows",
|
|
3
3
|
"repository": "github:linz/windows.git",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"ts",
|
|
8
|
+
"typescript",
|
|
9
|
+
"modal",
|
|
10
|
+
"react-component",
|
|
11
|
+
"window",
|
|
12
|
+
"panel",
|
|
13
|
+
"popout"
|
|
14
|
+
],
|
|
15
|
+
"version": "1.1.0",
|
|
6
16
|
"peerDependencies": {
|
|
17
|
+
"@linzjs/lui": "^17",
|
|
7
18
|
"lodash-es": ">=4",
|
|
8
19
|
"react": ">=17",
|
|
9
20
|
"react-dom": ">=17"
|
|
@@ -16,25 +27,33 @@
|
|
|
16
27
|
"node": ">=16"
|
|
17
28
|
},
|
|
18
29
|
"dependencies": {
|
|
30
|
+
"@emotion/cache": "^11.11.0",
|
|
31
|
+
"@emotion/react": "^11.11.1",
|
|
32
|
+
"@emotion/styled": "^11.11.0",
|
|
33
|
+
"@linzjs/lui": "^17",
|
|
19
34
|
"lodash-es": ">=4",
|
|
20
35
|
"react": ">=17",
|
|
21
36
|
"react-dom": ">=17",
|
|
37
|
+
"react-rnd": "^10.4.1",
|
|
22
38
|
"uuid": "^9.0.0"
|
|
23
39
|
},
|
|
24
40
|
"devDependencies": {
|
|
41
|
+
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
|
42
|
+
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
43
|
+
"@linzjs/step-ag-grid": "^14.9.3",
|
|
25
44
|
"@rollup/plugin-commonjs": "^25.0.2",
|
|
26
45
|
"@rollup/plugin-json": "^6.0.0",
|
|
27
46
|
"@rollup/plugin-node-resolve": "^15.1.0",
|
|
28
|
-
"@storybook/addon-docs": "^7.0.
|
|
29
|
-
"@storybook/addon-essentials": "^7.0.
|
|
30
|
-
"@storybook/addon-interactions": "^7.0.
|
|
31
|
-
"@storybook/addon-links": "^7.0.
|
|
32
|
-
"@storybook/blocks": "^7.0.
|
|
33
|
-
"@storybook/builder-webpack5": "^7.0.
|
|
47
|
+
"@storybook/addon-docs": "^7.0.26",
|
|
48
|
+
"@storybook/addon-essentials": "^7.0.26",
|
|
49
|
+
"@storybook/addon-interactions": "^7.0.26",
|
|
50
|
+
"@storybook/addon-links": "^7.0.26",
|
|
51
|
+
"@storybook/blocks": "^7.0.26",
|
|
52
|
+
"@storybook/builder-webpack5": "^7.0.26",
|
|
34
53
|
"@storybook/jest": "^0.1.0",
|
|
35
|
-
"@storybook/preset-create-react-app": "^7.0.
|
|
36
|
-
"@storybook/react": "^7.0.
|
|
37
|
-
"@storybook/react-vite": "^7.0.
|
|
54
|
+
"@storybook/preset-create-react-app": "^7.0.26",
|
|
55
|
+
"@storybook/react": "^7.0.26",
|
|
56
|
+
"@storybook/react-vite": "^7.0.26",
|
|
38
57
|
"@storybook/test-runner": "^0.11.0",
|
|
39
58
|
"@storybook/testing-library": "^0.2.0",
|
|
40
59
|
"@testing-library/jest-dom": "^5.16.5",
|
|
@@ -47,6 +66,8 @@
|
|
|
47
66
|
"@types/react": "^18.2.14",
|
|
48
67
|
"@types/react-dom": "^18.2.6",
|
|
49
68
|
"@types/uuid": "^9.0.2",
|
|
69
|
+
"ag-grid-community": "^27.3.0",
|
|
70
|
+
"ag-grid-react": "^27.3.0",
|
|
50
71
|
"eslint": "^8.44.0",
|
|
51
72
|
"eslint-config-prettier": "^8.8.0",
|
|
52
73
|
"eslint-config-react-app": "^7.0.1",
|
|
@@ -73,7 +94,7 @@
|
|
|
73
94
|
"sass": "^1.63.6",
|
|
74
95
|
"sass-loader": "^13.3.2",
|
|
75
96
|
"semantic-release": "^19.0.5",
|
|
76
|
-
"storybook": "^7.0.
|
|
97
|
+
"storybook": "^7.0.26",
|
|
77
98
|
"style-loader": "^3.3.3",
|
|
78
99
|
"stylelint": "^14.16.1",
|
|
79
100
|
"stylelint-config-prettier": "^9.0.5",
|
|
@@ -85,13 +106,14 @@
|
|
|
85
106
|
"vite": "^4.3.9"
|
|
86
107
|
},
|
|
87
108
|
"scripts": {
|
|
88
|
-
"build": "run-s clean stylelint lint bundle",
|
|
109
|
+
"build": "run-s clean stylelint lint lint-circular-deps bundle",
|
|
89
110
|
"yalc": "run-s clean css bundle && yalc publish",
|
|
90
111
|
"clean": "rimraf dist && mkdirp ./dist",
|
|
91
112
|
"bundle": "rollup -c",
|
|
92
113
|
"test": "jest",
|
|
93
114
|
"stylelint": "stylelint src/**/*.scss src/**/*.css --fix",
|
|
94
115
|
"lint": "eslint ./src --ext .js,.ts,.tsx --fix --cache --ignore-path .gitignore",
|
|
116
|
+
"lint-circular-deps": "npx madge --circular --extensions ts,tsx ./",
|
|
95
117
|
"storybook": "storybook dev -p 6006",
|
|
96
118
|
"build-storybook": "storybook build",
|
|
97
119
|
"deploy-storybook": "npx --yes -p @storybook/storybook-deployer storybook-to-ghpages",
|
package/src/modal/Modal.tsx
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { ModalInstanceContext } from "./ModalInstanceContext";
|
|
2
|
-
import {
|
|
2
|
+
import { delay } from "lodash-es";
|
|
3
3
|
import { ReactElement, useContext, useEffect, useRef } from "react";
|
|
4
4
|
|
|
5
5
|
export interface ModalProps {
|
|
6
|
-
selectFirstInput?: boolean;
|
|
7
6
|
children: ReactElement | ReactElement[];
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export const Modal = ({
|
|
9
|
+
export const Modal = ({ children }: ModalProps): ReactElement => {
|
|
11
10
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
12
11
|
|
|
13
12
|
const { close } = useContext(ModalInstanceContext);
|
|
@@ -21,8 +20,13 @@ export const Modal = ({ selectFirstInput = true, children }: ModalProps): ReactE
|
|
|
21
20
|
}, []);
|
|
22
21
|
|
|
23
22
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Dialogs auto select the first focusable element, this is in case you don't want that
|
|
24
|
+
delay(() => {
|
|
25
|
+
const input = dialogRef.current?.querySelectorAll("[data-autofocus]") as any;
|
|
26
|
+
input[0]?.focus?.();
|
|
27
|
+
input?.select?.();
|
|
28
|
+
}, 100);
|
|
29
|
+
}, []);
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
32
|
<dialog ref={dialogRef} onClick={(e) => e.target === e.currentTarget && close()} style={{ padding: 0 }}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useInterval } from "../util/useInterval";
|
|
2
2
|
import { ComponentType, ModalContext } from "./ModalContext";
|
|
3
3
|
import { ModalInstanceContext } from "./ModalInstanceContext";
|
|
4
|
-
import { Fragment, MutableRefObject, ReactElement, useState } from "react";
|
|
4
|
+
import { Fragment, MutableRefObject, ReactElement, useCallback, useState } from "react";
|
|
5
5
|
import * as ReactDOM from "react-dom";
|
|
6
6
|
import { v4 as uuid } from "uuid";
|
|
7
7
|
|
|
@@ -67,49 +67,48 @@ export const ModalContextProvider = ({ children }: { children: ReactElement }):
|
|
|
67
67
|
* @param Component React component.
|
|
68
68
|
* @param args Arguments for react component.
|
|
69
69
|
*/
|
|
70
|
-
const showModal =
|
|
71
|
-
ownerRef: MutableRefObject<HTMLElement | null>,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
70
|
+
const showModal = useCallback(
|
|
71
|
+
async (ownerRef: MutableRefObject<HTMLElement | null>, Component: ComponentType, args: any): Promise<any> => {
|
|
72
|
+
let componentInstance: ReactElement | undefined;
|
|
73
|
+
const promise = new Promise((resolve) => {
|
|
74
|
+
try {
|
|
75
|
+
// If there are any exceptions the modal won't show
|
|
76
|
+
setModals([
|
|
77
|
+
...modals,
|
|
78
|
+
{
|
|
79
|
+
uuid: uuid(),
|
|
80
|
+
ownerElement: ownerRef.current ?? document.body,
|
|
81
|
+
componentInstance: <Component {...args} resolve={resolve} close={() => resolve(undefined)} />,
|
|
82
|
+
resolve,
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(e);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
// Wait for modal to complete
|
|
92
|
+
const result = await promise;
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
// Close modal
|
|
95
|
+
setModals(modals.filter((e) => e.componentInstance !== componentInstance));
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
return result;
|
|
98
|
+
},
|
|
99
|
+
[modals],
|
|
100
|
+
);
|
|
102
101
|
|
|
103
|
-
const modalHasView = (
|
|
104
|
-
!!modalInstance.ownerElement?.ownerDocument?.defaultView
|
|
102
|
+
const modalHasView = useCallback(
|
|
103
|
+
(modalInstance: ModalInstance): boolean => !!modalInstance.ownerElement?.ownerDocument?.defaultView,
|
|
104
|
+
[],
|
|
105
|
+
);
|
|
105
106
|
|
|
106
107
|
// Tidy up modals that have closed because of an external window closing
|
|
107
108
|
useInterval(() => {
|
|
108
109
|
const newModals = modals.filter(modalHasView);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
}, 1000);
|
|
110
|
+
newModals.length !== modals.length && setModals(newModals);
|
|
111
|
+
}, 500);
|
|
113
112
|
|
|
114
113
|
return (
|
|
115
114
|
<ModalContext.Provider
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Modal } from "./Modal";
|
|
2
|
+
import { ModalCallback } from "./ModalContext";
|
|
3
|
+
import { ReactElement } from "react";
|
|
4
|
+
|
|
5
|
+
import { LuiAlertModalButtons, LuiButton, LuiIcon } from "@linzjs/lui";
|
|
6
|
+
import { IconName } from "@linzjs/lui/dist/components/LuiIcon/LuiIcon";
|
|
7
|
+
|
|
8
|
+
export type WarningLevel = "success" | "info" | "warning" | "error";
|
|
9
|
+
|
|
10
|
+
export interface PreModalProps extends ModalCallback<boolean> {
|
|
11
|
+
level?: WarningLevel;
|
|
12
|
+
children: ReactElement;
|
|
13
|
+
}
|
|
14
|
+
export const getIconForLevel = (level: "success" | "info" | "warning" | "error"): IconName => {
|
|
15
|
+
switch (level) {
|
|
16
|
+
case "success":
|
|
17
|
+
return "ic_check_circle";
|
|
18
|
+
case "info":
|
|
19
|
+
return "ic_info";
|
|
20
|
+
case "warning":
|
|
21
|
+
return "ic_warning";
|
|
22
|
+
case "error":
|
|
23
|
+
return "ic_error";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const PreModal = ({ level = "warning", children, resolve }: PreModalProps) => {
|
|
28
|
+
const icon = getIconForLevel(level);
|
|
29
|
+
return (
|
|
30
|
+
<Modal>
|
|
31
|
+
<div className={`lui-modal lui-box-shadow lui-modal-${level}`} style={{ minWidth: 400 }}>
|
|
32
|
+
<LuiIcon name={icon} alt={"warning"} size={"lg"} className={"lui-msg-status-icon"} />
|
|
33
|
+
{children}
|
|
34
|
+
<LuiAlertModalButtons>
|
|
35
|
+
{level === "warning" && (
|
|
36
|
+
<LuiButton level="secondary" onClick={() => resolve(false)} buttonProps={{ "data-autofocus": true }}>
|
|
37
|
+
Cancel
|
|
38
|
+
</LuiButton>
|
|
39
|
+
)}
|
|
40
|
+
<LuiButton onClick={() => resolve(true)}>Continue</LuiButton>
|
|
41
|
+
</LuiAlertModalButtons>
|
|
42
|
+
</div>
|
|
43
|
+
</Modal>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Simple button to open panel and show panel state
|
|
2
|
+
import { PanelsContext } from "./PanelsContext";
|
|
3
|
+
import { ReactElement, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
interface OpenPanelButtonProps {
|
|
6
|
+
buttonText: string;
|
|
7
|
+
component: () => ReactElement;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const OpenPanelButton = ({ buttonText, component }: OpenPanelButtonProps) => {
|
|
11
|
+
const { openPanel, openPanels } = useContext(PanelsContext);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<button onClick={() => openPanel(buttonText, component)}>
|
|
15
|
+
Show {buttonText} {openPanels.has(buttonText) ? "(Open)" : ""}
|
|
16
|
+
</button>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
@use "node_modules/@linzjs/lui/dist/scss/Core" as lui;
|
|
2
|
+
@use "node_modules/@linzjs/lui/dist/scss/Foundation/Variables/ColorVars.scss" as colours;
|
|
3
|
+
|
|
4
|
+
.lui-button.lui-button-toolbar {
|
|
5
|
+
border-color: transparent;
|
|
6
|
+
padding: 4px;
|
|
7
|
+
line-height: 12px;
|
|
8
|
+
margin: 2px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.OpenPanelIcon-selected {
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
color: lui.$white !important;
|
|
14
|
+
background-color: lui.$blue-75 !important;
|
|
15
|
+
box-shadow: inset 0 2px 4px rgb(41 92 130);
|
|
16
|
+
|
|
17
|
+
fill: lui.$white !important;
|
|
18
|
+
|
|
19
|
+
svg * {
|
|
20
|
+
fill: lui.$white !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
svg * {
|
|
24
|
+
color: lui.$white !important;
|
|
25
|
+
fill: lui.$white !important;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.OpenPanelIcon-disabled {
|
|
30
|
+
background-color: lui.$white !important;
|
|
31
|
+
|
|
32
|
+
fill: lui.$grey-20 !important;
|
|
33
|
+
|
|
34
|
+
svg * {
|
|
35
|
+
fill: lui.$grey-20 !important;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
%OpenPanelIcon-Group {
|
|
40
|
+
background-color: white;
|
|
41
|
+
border-radius: 4px;
|
|
42
|
+
padding: 4px;
|
|
43
|
+
align-items: center;
|
|
44
|
+
box-shadow: 0 0 10px rgb(0 0 0 / 20%);
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.OpenPanelIcon-verticalGroup {
|
|
49
|
+
@extend %OpenPanelIcon-Group;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
|
|
52
|
+
.OpenPanelIcon-separator {
|
|
53
|
+
margin: 6px 0;
|
|
54
|
+
height: 2px;
|
|
55
|
+
width: 100%;
|
|
56
|
+
background-color: colours.$grey-10;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.OpenPanelIcon-horizontalGroup {
|
|
61
|
+
@extend %OpenPanelIcon-Group;
|
|
62
|
+
flex-direction: row;
|
|
63
|
+
|
|
64
|
+
.OpenPanelIcon-separator {
|
|
65
|
+
height: 100%;
|
|
66
|
+
margin-left: 6px;
|
|
67
|
+
margin-right: 6px;
|
|
68
|
+
margin-top: -3px;
|
|
69
|
+
padding-bottom: 8px;
|
|
70
|
+
width: 2px;
|
|
71
|
+
background-color: colours.$grey-10;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import "./OpenPanelIcon.scss";
|
|
2
|
+
|
|
3
|
+
import { PanelsContext } from "./PanelsContext";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { ReactElement, ReactNode, useContext, useRef } from "react";
|
|
6
|
+
import { v4 as uuid } from "uuid";
|
|
7
|
+
|
|
8
|
+
import { LuiIcon } from "@linzjs/lui";
|
|
9
|
+
import { IconName } from "@linzjs/lui/dist/components/LuiIcon/LuiIcon";
|
|
10
|
+
|
|
11
|
+
export const ButtonIconHorizontalGroup = ({ children }: { children: ReactNode }) => (
|
|
12
|
+
<div className={"OpenPanelIcon-horizontalGroup"}>{children}</div>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const ButtonIconVerticalGroup = ({ children }: { children: ReactNode }) => (
|
|
16
|
+
<div className={"OpenPanelIcon-verticalGroup"}>{children}</div>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const ButtonIconSeparator = () => <div className="OpenPanelIcon-separator"> </div>;
|
|
20
|
+
|
|
21
|
+
interface OpenPanelIconProps {
|
|
22
|
+
uniqueId?: string;
|
|
23
|
+
iconTitle: string;
|
|
24
|
+
icon: IconName;
|
|
25
|
+
component: () => ReactElement;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const OpenPanelIcon = ({ iconTitle, uniqueId, icon, component, className, disabled }: OpenPanelIconProps) => {
|
|
31
|
+
const { openPanel, openPanels } = useContext(PanelsContext);
|
|
32
|
+
const id = useRef(uniqueId ?? uuid());
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
className={clsx(
|
|
38
|
+
className,
|
|
39
|
+
"lui-button lui-button-secondary lui-button-toolbar panel-button",
|
|
40
|
+
openPanels.has(iconTitle) && "OpenPanelIcon-selected",
|
|
41
|
+
disabled && "OpenPanelIcon-disabled",
|
|
42
|
+
)}
|
|
43
|
+
title={iconTitle}
|
|
44
|
+
onClick={() => openPanel(id.current, component)}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
>
|
|
47
|
+
<LuiIcon name={icon} alt={iconTitle} size={"md"} />
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.WindowPanel {
|
|
2
|
+
box-shadow: 0 1px 6px 0 #00000026, 0 6px 10px 0 #00000040;
|
|
3
|
+
background-color: #fff;
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
border-radius: 9px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.WindowPanel-header {
|
|
10
|
+
height: 48px;
|
|
11
|
+
line-height: 48px;
|
|
12
|
+
color: #2a292c;
|
|
13
|
+
padding: 0 8px;
|
|
14
|
+
display: flex;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
border-bottom: 2px #eaeaea solid;
|
|
18
|
+
font-size: 1em;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.WindowPanel-header-title {
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
text-overflow: ellipsis;
|
|
27
|
+
flex: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.WindowPanel-content {
|
|
31
|
+
flex: 1;
|
|
32
|
+
overflow: auto;
|
|
33
|
+
display: flex;
|
|
34
|
+
}
|