lupine.components 1.0.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/package.json +40 -0
  4. package/src/components/button-push-animation.tsx +138 -0
  5. package/src/components/button.tsx +55 -0
  6. package/src/components/drag-refresh.tsx +110 -0
  7. package/src/components/editable-label.tsx +83 -0
  8. package/src/components/float-window.tsx +225 -0
  9. package/src/components/grid.tsx +18 -0
  10. package/src/components/html-var.tsx +40 -0
  11. package/src/components/index.ts +35 -0
  12. package/src/components/input-with-title.tsx +24 -0
  13. package/src/components/link-item.tsx +13 -0
  14. package/src/components/link-list.tsx +62 -0
  15. package/src/components/menu-bar.tsx +219 -0
  16. package/src/components/menu-item-props.tsx +10 -0
  17. package/src/components/menu-sidebar.tsx +288 -0
  18. package/src/components/message-box.tsx +44 -0
  19. package/src/components/meta-data.tsx +36 -0
  20. package/src/components/meta-description.tsx +12 -0
  21. package/src/components/modal.tsx +29 -0
  22. package/src/components/notice-message.tsx +118 -0
  23. package/src/components/page-title.tsx +6 -0
  24. package/src/components/paging-link.tsx +99 -0
  25. package/src/components/panel.tsx +21 -0
  26. package/src/components/popup-menu.tsx +219 -0
  27. package/src/components/progress.tsx +91 -0
  28. package/src/components/redirect.tsx +19 -0
  29. package/src/components/resizable-splitter.tsx +128 -0
  30. package/src/components/select-with-title.tsx +37 -0
  31. package/src/components/spinner.tsx +100 -0
  32. package/src/components/svg.tsx +24 -0
  33. package/src/components/tabs.tsx +251 -0
  34. package/src/components/text-glow.tsx +36 -0
  35. package/src/components/text-wave.tsx +54 -0
  36. package/src/components/theme-selector.tsx +31 -0
  37. package/src/components/toggle-base.tsx +260 -0
  38. package/src/components/toggle-switch.tsx +156 -0
  39. package/src/index.ts +4 -0
  40. package/src/lib/date-utils.ts +317 -0
  41. package/src/lib/deep-merge.ts +37 -0
  42. package/src/lib/document-ready.ts +36 -0
  43. package/src/lib/dom/calculate-text-width.ts +13 -0
  44. package/src/lib/dom/download-stream.ts +17 -0
  45. package/src/lib/dom/download.ts +12 -0
  46. package/src/lib/dom/index.ts +71 -0
  47. package/src/lib/dynamical-load.ts +138 -0
  48. package/src/lib/format-bytes.ts +11 -0
  49. package/src/lib/index.ts +13 -0
  50. package/src/lib/lite-dom.ts +227 -0
  51. package/src/lib/message-hub.ts +105 -0
  52. package/src/lib/observable.ts +188 -0
  53. package/src/lib/promise-timeout.ts +1 -0
  54. package/src/lib/simple-storage.ts +40 -0
  55. package/src/lib/stop-propagation.ts +7 -0
  56. package/src/lib/upload-file.ts +68 -0
  57. package/src/styles/base-themes.ts +17 -0
  58. package/src/styles/dark-themes.ts +86 -0
  59. package/src/styles/index.ts +5 -0
  60. package/src/styles/light-themes.ts +93 -0
  61. package/src/styles/media-query.ts +93 -0
  62. package/src/styles/shared-themes.ts +52 -0
  63. package/tsconfig.json +113 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 uuware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # lupine.components
2
+
3
+ lupine.components is a collection of React-like, extremely fast, small size and lightweight frontend components.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "lupine.components",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "author": "uuware.com",
6
+ "homepage": "https://uuware.com/",
7
+ "description": "lupine.components is a extremely fast, small size and lightweight frontend framework, using React TSX syntax.",
8
+ "main": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "types": "src/index.ts",
11
+ "engines": {
12
+ "node": ">= 20"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.ts",
17
+ "browser": "./src/index.ts",
18
+ "umd": "./src/index.ts",
19
+ "import": "./src/index.ts",
20
+ "require": "./src/index.ts"
21
+ }
22
+ },
23
+ "keywords": [
24
+ "frontend",
25
+ "responsive",
26
+ "lightweight",
27
+ "grid"
28
+ ],
29
+ "scripts": {
30
+ "note": "echo 'build is not needed as the typescript code is supposed to be referred directly from other projects'",
31
+ "npm-publish": "npm publish --access public",
32
+ "build": "tsc"
33
+ },
34
+ "dependencies": {
35
+ "lupine.web": "^1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.10.5"
39
+ }
40
+ }
@@ -0,0 +1,138 @@
1
+ import { CssProps, RefProps } from 'lupine.web';
2
+
3
+ export enum ButtonPushAnimationSize {
4
+ SmallSmall = 'button-ss',
5
+ Small = 'button-s',
6
+ Medium = 'button-m',
7
+ Large = 'button-l',
8
+ LargeLarge = 'button-ll',
9
+ }
10
+ export type ButtonPushAnimationHookProps = {
11
+ setEnabled?: (enabled: boolean) => void;
12
+ getEnabled?: () => boolean;
13
+ };
14
+ export type ButtonPushAnimationProps = {
15
+ text: string;
16
+ size: ButtonPushAnimationSize;
17
+ disabled?: boolean;
18
+ onClick?: () => void;
19
+ hook?: ButtonPushAnimationHookProps;
20
+ class?: string;
21
+ css?: CssProps;
22
+ };
23
+ export const ButtonPushAnimation = (props: ButtonPushAnimationProps) => {
24
+ let disabled = props.disabled || false;
25
+ const onClick = () => {
26
+ if (disabled) {
27
+ return;
28
+ }
29
+ if (props.onClick) {
30
+ props.onClick();
31
+ }
32
+ };
33
+ if (props.hook) {
34
+ props.hook.setEnabled = (enabled: boolean) => {
35
+ disabled = !enabled;
36
+ ref.current.disabled = disabled;
37
+ };
38
+ props.hook.getEnabled = () => {
39
+ return !disabled;
40
+ };
41
+ }
42
+
43
+ const ref: RefProps = {};
44
+ const css: CssProps = {
45
+ all: 'unset',
46
+ cursor: 'pointer',
47
+ '-webkit-tap-highlight-color': 'rgba(0, 0, 0, 0)',
48
+ position: 'relative',
49
+ borderRadius: 'var(--border-radius-m)',
50
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
51
+ boxShadow: '-0.15em -0.15em 0.15em -0.075em rgba(5, 5, 5, 0.25), 0.0375em 0.0375em 0.0675em 0 rgba(5, 5, 5, 0.1)',
52
+ '.button-outer': {
53
+ position: 'relative',
54
+ zIndex: 1,
55
+ borderRadius: 'inherit',
56
+ transition: 'box-shadow 300ms ease',
57
+ willChange: 'box-shadow',
58
+ boxShadow: '0 0.05em 0.05em -0.01em rgba(5, 5, 5, 1), 0 0.01em 0.01em -0.01em rgba(5, 5, 5, 0.5), 0.15em 0.3em 0.1em -0.01em rgba(5, 5, 5, 0.25)',
59
+ },
60
+ '.button-inner': {
61
+ position: 'relative',
62
+ zIndex: 2,
63
+ borderRadius: 'inherit',
64
+ padding: 'var(--button-padding)',
65
+ background: 'linear-gradient(135deg, #ffffff, #eeeeee)',
66
+ transition: 'box-shadow 300ms ease, background-image 250ms ease, transform 250ms ease;',
67
+ willChange: 'box-shadow, background-image, transform',
68
+ overflow: 'clip',
69
+ // clipPath: 'inset(0 0 0 0 round 999vw)',
70
+ boxShadow: '0 0 0 0 inset rgba(5, 5, 5, 0.1), -0.05em -0.05em 0.05em 0 inset rgba(5, 5, 5, 0.25), 0 0 0 0 inset rgba(5, 5, 5, 0.1), 0 0 0.05em 0.2em inset rgba(255, 255, 255, 0.25), 0.025em 0.05em 0.1em 0 inset rgba(255, 255, 255, 1), 0.12em 0.12em 0.12em inset rgba(255, 255, 255, 0.25), -0.075em -0.25em 0.25em 0.1em inset rgba(5, 5, 5, 0.25)',
71
+ },
72
+ '.button-inner span': {
73
+ position: 'relative',
74
+ zIndex: 4,
75
+ // fontFamily: 'Inter, sans-serif',
76
+ letterSpacing: '-0.05em',
77
+ // fontWeight: 500,
78
+ color: 'rgba(0, 0, 0, 0);',
79
+ backgroundImage: 'linear-gradient(135deg, rgba(25, 25, 25, 1), rgba(75, 75, 75, 1))',
80
+ backgroundClip: 'text',
81
+ transition: 'transform 250ms ease',
82
+ display: 'block',
83
+ willChange: 'transform',
84
+ textShadow: 'rgba(0, 0, 0, 0.1) 0 0 0.1em',
85
+ userSelect: 'none',
86
+ },
87
+ '&.button-ss': {
88
+ borderRadius: '2px',
89
+ },
90
+ '&.button-s': {
91
+ borderRadius: '3px',
92
+ },
93
+ '&.button-l': {
94
+ borderRadius: '6px',
95
+ },
96
+ '&.button-ll': {
97
+ borderRadius: '10px',
98
+ },
99
+ '&.button-ss .button-inner': {
100
+ padding: '0.1rem 0.3rem',
101
+ fontSize: '0.65rem',
102
+ },
103
+ '&.button-s .button-inner': {
104
+ padding: '0.2rem 0.5rem',
105
+ fontSize: '0.85rem',
106
+ },
107
+ '&.button-l .button-inner': {
108
+ padding: '0.4rem 1.2rem',
109
+ fontSize: '1.5rem',
110
+ },
111
+ '&.button-ll .button-inner': {
112
+ padding: '0.5rem 1.5rem',
113
+ fontSize: '2rem',
114
+ },
115
+ '&:active .button-outer': {
116
+ boxShadow: '0 0 0 0 rgba(5, 5, 5, 1), 0 0 0 0 rgba(5, 5, 5, 0.5), 0 0 0 0 rgba(5, 5, 5, 0.25)',
117
+ },
118
+ '&:active .button-inner': {
119
+ boxShadow: '0.1em 0.15em 0.05em 0 inset rgba(5, 5, 5, 0.75), -0.025em -0.03em 0.05em 0.025em inset rgba(5, 5, 5, 0.5), 0.25em 0.25em 0.2em 0 inset rgba(5, 5, 5, 0.5), 0 0 0.05em 0.5em inset rgba(255, 255, 255, 0.15), 0 0 0 0 inset rgba(255, 255, 255, 1), 0.12em 0.12em 0.12em inset rgba(255, 255, 255, 0.25), -0.075em -0.12em 0.2em 0.1em inset rgba(5, 5, 5, 0.25)',
120
+ },
121
+ '&:hover .button-inner': {
122
+ transform: 'scale(0.99)',
123
+ },
124
+ '&:hover .button-inner span': {
125
+ transform: 'scale(0.975)',
126
+ },
127
+ ...props.css,
128
+ };
129
+ return (
130
+ <button ref={ref} css={css} class={['button-push-animation', props.size, props.class].join(' ')} disabled={disabled} onClick={onClick}>
131
+ <div class="button-outer">
132
+ <div class="button-inner">
133
+ <span>{props.text}</span>
134
+ </div>
135
+ </div>
136
+ </button>
137
+ );
138
+ };
@@ -0,0 +1,55 @@
1
+ import { CssProps, RefProps } from 'lupine.web';
2
+
3
+ export enum ButtonSize {
4
+ SmallLarge = 'button-ss',
5
+ Small = 'button-s',
6
+ Medium = 'button-m',
7
+ Large = 'button-l',
8
+ LargeLarge = 'button-ll',
9
+ }
10
+ export type ButtonHookProps = {
11
+ setEnabled?: (enabled: boolean) => void;
12
+ getEnabled?: () => boolean;
13
+ };
14
+ export type ButtonProps = {
15
+ text: string;
16
+ size: ButtonSize;
17
+ disabled?: boolean;
18
+ onClick?: () => void;
19
+ hook?: ButtonHookProps;
20
+ class?: string;
21
+ css?: CssProps;
22
+ };
23
+ export const Button = (props: ButtonProps) => {
24
+ let disabled = props.disabled || false;
25
+ const onClick = () => {
26
+ if (disabled) {
27
+ return;
28
+ }
29
+ if (props.onClick) {
30
+ props.onClick();
31
+ }
32
+ };
33
+ if (props.hook) {
34
+ props.hook.setEnabled = (enabled: boolean) => {
35
+ disabled = !enabled;
36
+ ref.current.disabled = disabled;
37
+ };
38
+ props.hook.getEnabled = () => {
39
+ return !disabled;
40
+ };
41
+ }
42
+
43
+ const ref: RefProps = {};
44
+ return (
45
+ <button
46
+ ref={ref}
47
+ class={['button-base', props.size, props.class].join(' ')}
48
+ css={props.css}
49
+ disabled={disabled}
50
+ onClick={onClick}
51
+ >
52
+ {props.text}
53
+ </button>
54
+ );
55
+ };
@@ -0,0 +1,110 @@
1
+ import { CssProps, RefProps } from '../jsx';
2
+ import { Spinner02, SpinnerSize } from './spinner';
3
+
4
+ export type DragRefreshCloseProps = () => void;
5
+
6
+ export type DragRefreshProps = {
7
+ container: string;
8
+ onDragRefresh: (close: DragRefreshCloseProps) => Promise<void>;
9
+ };
10
+
11
+ export const DragFresh = (props: DragRefreshProps) => {
12
+ const css: CssProps = {
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ width: '100%',
16
+ height: '0px',
17
+ position: 'relative',
18
+ '.drag-spinner': {
19
+ position: 'fixed',
20
+ top: '0',
21
+ left: '0',
22
+ width: '100%',
23
+ zIndex: 3,
24
+ display: 'none',
25
+ justifyContent: 'center',
26
+ transition: 'opacity 0.5s ease',
27
+ alignItems: 'end',
28
+ backgroundImage: 'linear-gradient(to bottom, rgba(200,200,200,0.8), rgba(255,255,255,0))',
29
+ },
30
+ '&.show .drag-spinner': {
31
+ display: 'flex',
32
+ },
33
+ };
34
+
35
+ const closeSpin = () => {
36
+ const spinnerDom = ref.$('.drag-spinner') as HTMLDivElement;
37
+ if (!spinnerDom) return;
38
+ spinnerDom.style.opacity = '0';
39
+ setTimeout(() => {
40
+ spinnerDom.style.opacity = '1';
41
+ spinnerDom.parentElement!.classList.remove('show');
42
+ }, 300);
43
+ };
44
+ const ref: RefProps = {
45
+ onLoad: async () => {
46
+ const container = document.querySelector(props.container) as HTMLDivElement;
47
+ const pullDom = ref.current as HTMLDivElement;
48
+ const spinnerDom = ref.$('.drag-spinner') as HTMLDivElement;
49
+ if (!container || !pullDom || !spinnerDom) return;
50
+ let touchstartY = 0;
51
+ let touchstartX = 0;
52
+ let direction = '';
53
+ let needRefresh = false;
54
+ const maxHeight = 150;
55
+ container.addEventListener('touchstart', (e: any) => {
56
+ touchstartY = e.touches[0].clientY;
57
+ touchstartX = e.touches[0].clientX;
58
+ direction = '';
59
+ needRefresh = false;
60
+ });
61
+ container.addEventListener('touchmove', (e: any) => {
62
+ console.log(`window.scrollY: ${window.scrollY}`);
63
+ const touchY = e.touches[0].clientY;
64
+ const touchX = e.touches[0].clientX;
65
+ const movedY = touchY - touchstartY;
66
+ const movedX = touchX - touchstartX;
67
+ if (direction === '') {
68
+ if (movedY > 0) {
69
+ direction = 'Y';
70
+ } else if (movedX > 0) {
71
+ direction = 'X';
72
+ }
73
+ }
74
+ if (direction === 'X') {
75
+ return;
76
+ }
77
+ if (window.scrollY === 0) {
78
+ needRefresh = movedY > 30;
79
+ if (movedY > 5) {
80
+ pullDom.classList.add('show');
81
+ spinnerDom.style.height = `${Math.min(maxHeight, movedY)}px`;
82
+ } else {
83
+ pullDom.classList.remove('show');
84
+ spinnerDom.style.height = '0';
85
+ }
86
+ } else if (window.scrollY > Math.min(maxHeight, movedY)) {
87
+ pullDom.classList.remove('show');
88
+ spinnerDom.style.height = '0';
89
+ }
90
+ });
91
+ container.addEventListener('touchend', (e) => {
92
+ if (direction === 'Y') {
93
+ if (needRefresh) {
94
+ props.onDragRefresh(closeSpin);
95
+ } else {
96
+ closeSpin();
97
+ }
98
+ }
99
+ direction = '';
100
+ });
101
+ },
102
+ };
103
+ return (
104
+ <div css={css} ref={ref} class='drag-refresh-box'>
105
+ <div class='drag-spinner'>
106
+ <Spinner02 size={SpinnerSize.Large} />
107
+ </div>
108
+ </div>
109
+ );
110
+ };
@@ -0,0 +1,83 @@
1
+ import { CssProps, RefProps } from '../jsx';
2
+
3
+ export type EditableLabelHookProps = {
4
+ updateValue?: (value: string) => void;
5
+ };
6
+
7
+ export type EditableLabelProps = {
8
+ text: string;
9
+ type?: 'text' | 'number';
10
+ mandtory?: boolean;
11
+ save?: (value: string) => void;
12
+ hook?: EditableLabelHookProps;
13
+ };
14
+ export const EditableLabel = (props: EditableLabelProps) => {
15
+ let editFlag = false;
16
+ let oldValue = props.text;
17
+ const onDblClick = () => {
18
+ if (editFlag) return;
19
+ editFlag = true;
20
+ const el = ref.$('input.editable-label');
21
+ oldValue = el.value;
22
+ el.removeAttribute('readonly');
23
+ el.classList.remove('not-editable');
24
+ el.setSelectionRange(0, 0);
25
+ };
26
+ const reset = () => {
27
+ const el = ref.$('input.editable-label');
28
+ el.setAttribute('readonly', 'readonly');
29
+ el.classList.add('not-editable');
30
+ oldValue = '';
31
+ editFlag = false;
32
+ return el;
33
+ };
34
+ const onKeyDown = (ev: KeyboardEvent) => {
35
+ if (!editFlag) return;
36
+ if (ev.key === 'Enter') {
37
+ onBlur();
38
+ } else if (ev.key === 'Escape') {
39
+ const el = ref.$('input.editable-label');
40
+ el.value = oldValue;
41
+ reset();
42
+ }
43
+ };
44
+ const onBlur = () => {
45
+ const savedValue = oldValue;
46
+ const el = reset();
47
+ if (savedValue !== el.value) {
48
+ if (props.mandtory === true && !el.value) {
49
+ el.value = savedValue;
50
+ } else {
51
+ props.save?.(el.value);
52
+ }
53
+ }
54
+ };
55
+ if (props.hook) {
56
+ props.hook.updateValue = (value: string) => {
57
+ const el = ref.$('input.editable-label');
58
+ el.value = value;
59
+ };
60
+ }
61
+ const css: CssProps = {
62
+ '.not-editable': {
63
+ borderColor: 'transparent',
64
+ boxShadow: 'unset',
65
+ },
66
+ 'input.editable-label': {
67
+ width: '100%',
68
+ },
69
+ };
70
+ const ref: RefProps = {};
71
+ return (
72
+ <div css={css} ref={ref}>
73
+ <input
74
+ class='input-base editable-label not-editable'
75
+ onDblClick={onDblClick}
76
+ onKeyDown={onKeyDown}
77
+ value={props.text}
78
+ onBlur={onBlur}
79
+ readOnly
80
+ />
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,225 @@
1
+ import { CssProps, RefProps, VNode, mountComponents } from 'lupine.web';
2
+ import { stopPropagation } from '../lib';
3
+
4
+ export type FloatWindowCloseProps = () => void;
5
+
6
+ export type FloatWindowShowProps = {
7
+ title: string;
8
+ children: VNode<any>;
9
+ buttons?: string[];
10
+ contentMaxHeight?: string;
11
+ contentMinWidth?: string;
12
+ noMoving?: boolean;
13
+ noModal?: boolean;
14
+ closeEvent?: () => void;
15
+ handleClicked: (index: number, close: FloatWindowCloseProps) => void;
16
+ closeWhenClickOutside?: boolean; // default false
17
+ };
18
+
19
+ // because it's over a mask, so it can use primary colors
20
+ export class FloatWindow {
21
+ static hostNode: HTMLElement;
22
+
23
+ private static initialized = false;
24
+ private static pressed = false;
25
+ private static startX = 0;
26
+ private static startY = 0;
27
+ private static startTop = 0;
28
+ private static startLeft = 0;
29
+
30
+ static init() {
31
+ window.addEventListener('mousemove', FloatWindow.onMousemove.bind(FloatWindow), false);
32
+ document.documentElement.addEventListener('mouseup', FloatWindow.onMouseup.bind(FloatWindow), false);
33
+ }
34
+
35
+ static async show({
36
+ title,
37
+ children,
38
+ contentMaxHeight,
39
+ contentMinWidth,
40
+ buttons,
41
+ noMoving = false,
42
+ noModal = false,
43
+ closeEvent,
44
+ handleClicked,
45
+ closeWhenClickOutside = false,
46
+ }: FloatWindowShowProps): Promise<FloatWindowCloseProps> {
47
+ const onClickContainer = (event: any) => {
48
+ if (closeWhenClickOutside !== false && event.target.className === 'fwin-box') {
49
+ handleClose();
50
+ }
51
+ };
52
+ const handleClose = () => {
53
+ closeEvent?.();
54
+ ref.current.classList.add('transition');
55
+ ref.current.classList.remove('animation');
56
+ setTimeout(() => {
57
+ base.remove();
58
+ }, 300);
59
+ };
60
+
61
+ const base = document.createElement('div');
62
+ const onMousedown = (event: any) => {
63
+ if (noMoving) return;
64
+
65
+ if (!this.initialized) {
66
+ this.initialized = true;
67
+ this.init();
68
+ }
69
+
70
+ FloatWindow.hostNode = ref.current;
71
+ FloatWindow.onMousedown.bind(FloatWindow)(event);
72
+ };
73
+
74
+ const newButtons = !buttons || buttons.length === 0 ? ['OK', 'Cancel'] : buttons;
75
+ const onClickButtons = (index: number) => {
76
+ handleClicked(index, handleClose);
77
+ };
78
+
79
+ const ref: RefProps = {
80
+ onLoad: async () => {
81
+ ref.current.classList.add('transition', 'animation');
82
+ setTimeout(() => {
83
+ // don't need transition for moving
84
+ ref.current.classList.remove('transition');
85
+ }, 300);
86
+ },
87
+ };
88
+ const cssContainer: CssProps = {
89
+ position: noModal ? '' : 'fixed',
90
+ top: 0,
91
+ left: 0,
92
+ width: '100%',
93
+ height: '100%',
94
+ backgroundColor: noModal ? '' : 'var(--cover-mask-bg-color)',
95
+ '.fwin-body': {
96
+ position: 'fixed',
97
+ top: '50%',
98
+ left: '50%',
99
+ transform: 'translate(-50%, -50%) scale(0.1)',
100
+ color: 'var(--primary-color)',
101
+ backgroundColor: 'var(--cover-bg-color)', //'#fefefe',
102
+ border: 'var(--primary-border)', //'1px solid #888',
103
+ borderRadius: '6px',
104
+ minWidth: contentMinWidth ? contentMinWidth : '',
105
+ maxWidth: '90%',
106
+ boxShadow: 'var(--cover-box-shadow)', //'#0000004c 0px 19px 38px, #00000038 0px 15px 12px',
107
+ opacity: 0,
108
+ zIndex: 'var(--layer-float-window)',
109
+ '&.transition': {
110
+ transition: 'all 0.3s',
111
+ },
112
+ '&.animation': {
113
+ transform: 'translate(-50%, -50%) scale(1)',
114
+ opacity: 1,
115
+ },
116
+ '&.animation-close': {
117
+ transition: 'all 0.3s',
118
+ transform: 'translate(-50%, -50%) scale(0)',
119
+ opacity: 0,
120
+ },
121
+ '.fwin-title': {
122
+ padding: '10px 15px 5px',
123
+ borderBottom: 'var(--primary-border)', //'1px solid #e9ecef',
124
+ '.fwin-close': {
125
+ color: '#aaaaaa',
126
+ float: 'right',
127
+ fontSize: '26px',
128
+ fontWeight: 'bold',
129
+ cursor: 'pointer',
130
+ marginTop: '-8px',
131
+ marginRight: '-10px',
132
+ },
133
+ '.fwin-close:hover': {
134
+ transition: 'all 300ms ease',
135
+ color: '#555555',
136
+ },
137
+ },
138
+ '.fwin-content': {
139
+ padding: '15px',
140
+ maxHeight: contentMaxHeight ? `min(${contentMaxHeight}, calc(100% - 100px))` : 'calc(100% - 100px)',
141
+ overflowY: 'auto',
142
+ },
143
+ '.fwin-bottom': {
144
+ display: 'flex',
145
+ padding: '5px 15px',
146
+ borderTop: 'var(--primary-border)', //'1px solid #e9ecef',
147
+ justifyContent: 'end',
148
+ '>div': {
149
+ marginLeft: '5px',
150
+ },
151
+ },
152
+ },
153
+ };
154
+ const component = (
155
+ <div css={cssContainer} class='fwin-box' onClick={onClickContainer}>
156
+ <div ref={ref} class='fwin-body' onMouseDown={onMousedown}>
157
+ <div class='fwin-title'>
158
+ {title}
159
+ <span class='fwin-close' onClick={handleClose}>
160
+ ×
161
+ </span>
162
+ </div>
163
+ <div class='fwin-content'>{children}</div>
164
+ <div class='fwin-bottom'>
165
+ {newButtons.map((i, index) => (
166
+ <button
167
+ class='button-base button-s mr-m'
168
+ onClick={() => {
169
+ onClickButtons(index);
170
+ }}
171
+ >
172
+ {i}
173
+ </button>
174
+ ))}
175
+ </div>
176
+ </div>
177
+ </div>
178
+ );
179
+ base.style.position = 'fixed';
180
+ document.body.appendChild(base);
181
+ await mountComponents(base, component);
182
+ return handleClose;
183
+ }
184
+
185
+ static onMousedown(event: any) {
186
+ if (event.buttons !== 1 || event.button !== 0) return;
187
+ if (event.srcElement.className !== 'fwin-title') return;
188
+
189
+ this.pressed = true;
190
+ this.startX = event.clientX;
191
+ this.startY = event.clientY;
192
+ const nodeStyle = document.defaultView!.getComputedStyle(this.hostNode);
193
+ this.startTop = parseInt(nodeStyle['top'], 10);
194
+ this.startLeft = parseInt(nodeStyle['left'], 10);
195
+ }
196
+
197
+ static onMousemove(event: any) {
198
+ if (!this.pressed || event.buttons !== 1 || event.button !== 0) {
199
+ return;
200
+ }
201
+
202
+ // prevent text/element selection when drag
203
+ stopPropagation(event);
204
+ if (
205
+ event.clientX < 0 ||
206
+ event.clientY < 0 ||
207
+ event.clientX > window.innerWidth ||
208
+ event.clientY > window.innerHeight
209
+ ) {
210
+ return;
211
+ }
212
+
213
+ let movedX = this.startLeft + (event.clientX - this.startX);
214
+ let movedY = this.startTop + (event.clientY - this.startY);
215
+ if (movedY <= 0) movedY = 0;
216
+ if (movedX <= 0) movedX = 0;
217
+
218
+ this.hostNode.style.top = movedY + 'px';
219
+ this.hostNode.style.left = movedX + 'px';
220
+ }
221
+
222
+ static onMouseup() {
223
+ if (this.pressed) this.pressed = false;
224
+ }
225
+ }
@@ -0,0 +1,18 @@
1
+ export const Grid = ({ gridOption }: { gridOption: any }) => {
2
+ const cssContainer: any = {
3
+ display: 'grid',
4
+ ...gridOption.options,
5
+ };
6
+ const cells: any = [];
7
+ gridOption.cells.forEach((cell: any, index: number) => {
8
+ const name = cell.name || 'cell' + index;
9
+ cssContainer[`.${name}`] = cell.option;
10
+ cells.push(<div class={name}>{cell.component}</div>);
11
+ });
12
+ const className = 'grid-box' + (gridOption.className ? ` ${gridOption.className}` : '');
13
+ return (
14
+ <div css={cssContainer} class={className}>
15
+ {cells}
16
+ </div>
17
+ );
18
+ };