lupine.components 1.0.16 → 1.0.18
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/package.json +1 -1
- package/src/components/action-sheet.tsx +40 -9
- package/src/components/desktop-footer.tsx +17 -0
- package/src/components/desktop-header.tsx +52 -0
- package/src/components/mobile-components/icon-menu-item-props.ts +6 -0
- package/src/components/mobile-components/index.ts +7 -0
- package/src/components/mobile-components/mobile-footer-menu.tsx +9 -13
- package/src/{frames/header-with-back-frame.tsx → components/mobile-components/mobile-frame-with-header.tsx} +22 -13
- package/src/components/mobile-components/mobile-header-component.tsx +16 -4
- package/src/components/mobile-components/mobile-header-title-icon.tsx +105 -0
- package/src/components/mobile-components/mobile-side-menu.tsx +150 -0
- package/src/components/mobile-components/mobile-top-sys-icon.tsx +18 -0
- package/src/components/mobile-components/mobile-top-sys-menu.tsx +62 -0
- package/src/components/popup-menu.tsx +30 -24
- package/src/components/progress.tsx +2 -2
- package/src/frames/index.ts +0 -2
- package/src/frames/responsive-frame.tsx +31 -33
- package/src/frames/slider-frame.tsx +20 -8
- package/src/frames/top-frame.tsx +4 -2
- package/src/lib/dom/download-stream.ts +3 -1
- package/src/lib/upload-file.ts +10 -7
- package/src/frames/desktop-frame.tsx +0 -81
package/package.json
CHANGED
|
@@ -4,10 +4,13 @@ export type ActionSheetCloseProps = () => void;
|
|
|
4
4
|
|
|
5
5
|
export type ActionSheetShowProps = {
|
|
6
6
|
title: string;
|
|
7
|
-
children: VNode<any>;
|
|
7
|
+
children: string | VNode<any>;
|
|
8
|
+
contentMaxWidth?: string;
|
|
8
9
|
contentMaxHeight?: string;
|
|
9
10
|
closeEvent?: () => void;
|
|
10
11
|
closeWhenClickOutside?: boolean; // default true
|
|
12
|
+
confirmButtonText?: string; // no showing if not set
|
|
13
|
+
handleConfirmClicked?: (close: ActionSheetCloseProps) => void;
|
|
11
14
|
cancelButtonText?: string; // no showing if not set
|
|
12
15
|
zIndex?: string;
|
|
13
16
|
};
|
|
@@ -19,12 +22,22 @@ export class ActionSheet {
|
|
|
19
22
|
static async show({
|
|
20
23
|
title,
|
|
21
24
|
children,
|
|
25
|
+
contentMaxWidth,
|
|
22
26
|
contentMaxHeight,
|
|
23
27
|
closeEvent,
|
|
24
28
|
closeWhenClickOutside = true,
|
|
29
|
+
confirmButtonText = '',
|
|
30
|
+
handleConfirmClicked,
|
|
25
31
|
cancelButtonText = 'Cancel',
|
|
26
32
|
zIndex,
|
|
27
33
|
}: ActionSheetShowProps): Promise<ActionSheetCloseProps> {
|
|
34
|
+
const onConfirm = () => {
|
|
35
|
+
if (handleConfirmClicked) {
|
|
36
|
+
handleConfirmClicked(handleClose);
|
|
37
|
+
} else {
|
|
38
|
+
handleClose();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
28
41
|
const onCancel = () => {
|
|
29
42
|
handleClose();
|
|
30
43
|
};
|
|
@@ -64,7 +77,7 @@ export class ActionSheet {
|
|
|
64
77
|
bottom: '0px',
|
|
65
78
|
left: '0px',
|
|
66
79
|
width: '100%',
|
|
67
|
-
maxHeight: contentMaxHeight ? contentMaxHeight : '',
|
|
80
|
+
maxHeight: contentMaxHeight ? contentMaxHeight : '100%',
|
|
68
81
|
color: 'var(--primary-color)',
|
|
69
82
|
padding: '8px',
|
|
70
83
|
transition: 'all 0.3s',
|
|
@@ -84,7 +97,7 @@ export class ActionSheet {
|
|
|
84
97
|
borderRadius: '8px',
|
|
85
98
|
backgroundColor: 'var(--cover-bg-color)', //'#fefefe',
|
|
86
99
|
width: '100%',
|
|
87
|
-
maxWidth:
|
|
100
|
+
maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 70%, 800px)`,
|
|
88
101
|
margin: '0 auto',
|
|
89
102
|
},
|
|
90
103
|
'.act-sheet-bottom-item, .act-sheet-item': {
|
|
@@ -93,7 +106,7 @@ export class ActionSheet {
|
|
|
93
106
|
cursor: 'pointer',
|
|
94
107
|
transition: 'all 0.3s ease',
|
|
95
108
|
width: '100%',
|
|
96
|
-
maxWidth:
|
|
109
|
+
maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 70%, 800px)`,
|
|
97
110
|
borderTop: '1px solid var(--primary-border-color)',
|
|
98
111
|
},
|
|
99
112
|
'.act-sheet-bottom-item': {
|
|
@@ -104,6 +117,11 @@ export class ActionSheet {
|
|
|
104
117
|
'.act-sheet-bottom-item:hover, .act-sheet-item:hover': {
|
|
105
118
|
fontWeight: 'bold',
|
|
106
119
|
},
|
|
120
|
+
'.act-sheet-confirm, .act-sheet-item': {
|
|
121
|
+
borderRadius: 'unset',
|
|
122
|
+
marginTop: 'unset',
|
|
123
|
+
maxWidth: 'unset',
|
|
124
|
+
},
|
|
107
125
|
},
|
|
108
126
|
};
|
|
109
127
|
const component = (
|
|
@@ -112,6 +130,11 @@ export class ActionSheet {
|
|
|
112
130
|
<div class='act-sheet-content'>
|
|
113
131
|
<div class='act-sheet-title'>{title}</div>
|
|
114
132
|
{children}
|
|
133
|
+
{confirmButtonText && (
|
|
134
|
+
<div class='act-sheet-bottom-item act-sheet-confirm' onClick={onConfirm}>
|
|
135
|
+
{confirmButtonText}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
115
138
|
</div>
|
|
116
139
|
{cancelButtonText && (
|
|
117
140
|
<div class='act-sheet-bottom-item' onClick={onCancel}>
|
|
@@ -131,7 +154,7 @@ export class ActionSheet {
|
|
|
131
154
|
|
|
132
155
|
export const ActionSheetSelectOptionsProps = {
|
|
133
156
|
YesNo: ['Yes', 'No'],
|
|
134
|
-
|
|
157
|
+
Ok: ['OK'],
|
|
135
158
|
};
|
|
136
159
|
export type ActionSheetSelectProps = Omit<ActionSheetShowProps, 'children'> & {
|
|
137
160
|
options: string[];
|
|
@@ -142,10 +165,12 @@ export class ActionSheetSelect {
|
|
|
142
165
|
static async show({
|
|
143
166
|
title,
|
|
144
167
|
contentMaxHeight,
|
|
145
|
-
options = ActionSheetSelectOptionsProps.
|
|
168
|
+
options = ActionSheetSelectOptionsProps.Ok,
|
|
146
169
|
closeEvent,
|
|
147
170
|
handleClicked,
|
|
148
171
|
closeWhenClickOutside = true,
|
|
172
|
+
confirmButtonText,
|
|
173
|
+
handleConfirmClicked,
|
|
149
174
|
cancelButtonText = 'Cancel',
|
|
150
175
|
}: ActionSheetSelectProps): Promise<ActionSheetCloseProps> {
|
|
151
176
|
const handleClose = await ActionSheet.show({
|
|
@@ -160,6 +185,8 @@ export class ActionSheetSelect {
|
|
|
160
185
|
</div>
|
|
161
186
|
),
|
|
162
187
|
contentMaxHeight,
|
|
188
|
+
confirmButtonText,
|
|
189
|
+
handleConfirmClicked,
|
|
163
190
|
cancelButtonText,
|
|
164
191
|
closeEvent,
|
|
165
192
|
closeWhenClickOutside,
|
|
@@ -169,7 +196,7 @@ export class ActionSheetSelect {
|
|
|
169
196
|
}
|
|
170
197
|
|
|
171
198
|
export type ActionSheetMessageProps = Omit<ActionSheetShowProps, 'children' | 'handleClicked' | 'closeEvent'> & {
|
|
172
|
-
message: string
|
|
199
|
+
message: string | VNode<any>;
|
|
173
200
|
};
|
|
174
201
|
export class ActionSheetMessage {
|
|
175
202
|
static async show({
|
|
@@ -177,13 +204,17 @@ export class ActionSheetMessage {
|
|
|
177
204
|
message,
|
|
178
205
|
contentMaxHeight,
|
|
179
206
|
closeWhenClickOutside = true,
|
|
207
|
+
confirmButtonText,
|
|
208
|
+
handleConfirmClicked,
|
|
180
209
|
cancelButtonText = '',
|
|
181
210
|
}: ActionSheetMessageProps): Promise<ActionSheetCloseProps> {
|
|
182
211
|
const handleClose = await ActionSheet.show({
|
|
183
212
|
title,
|
|
184
|
-
children: <div css={{ padding: '
|
|
213
|
+
children: <div css={{ padding: '8px',borderTop: '1px solid var(--primary-border-color)', }}>{message}</div>,
|
|
185
214
|
contentMaxHeight,
|
|
186
|
-
|
|
215
|
+
confirmButtonText,
|
|
216
|
+
handleConfirmClicked,
|
|
217
|
+
cancelButtonText,
|
|
187
218
|
closeWhenClickOutside,
|
|
188
219
|
});
|
|
189
220
|
return handleClose;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export const DesktopFooter = (props: { title: string }) => {
|
|
4
|
+
const css: CssProps = {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
padding: '0 32px 16px',
|
|
7
|
+
'.d-footer-cp': {
|
|
8
|
+
padding: '1px 15px',
|
|
9
|
+
margin: 'auto',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
return (
|
|
13
|
+
<div css={css} class='d-footer-box'>
|
|
14
|
+
<div class='d-footer-cp'>{props.title}</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CssProps } from 'lupine.components';
|
|
2
|
+
import { IconMenuItemProps } from './mobile-components/icon-menu-item-props';
|
|
3
|
+
|
|
4
|
+
export const DesktopHeader = (props: { title: string; items: IconMenuItemProps[] }) => {
|
|
5
|
+
const css: CssProps = {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'row',
|
|
8
|
+
width: '100%',
|
|
9
|
+
height: '100%',
|
|
10
|
+
'.d-header-title': {
|
|
11
|
+
display: 'flex',
|
|
12
|
+
flex: '1',
|
|
13
|
+
margin: '8px 16px',
|
|
14
|
+
textShadow: '-3px -3px 10px white, 3px 3px 10px black',
|
|
15
|
+
color: 'darkblue',
|
|
16
|
+
fontSize: '22px',
|
|
17
|
+
},
|
|
18
|
+
'.desktop-menu-bar': {
|
|
19
|
+
display: 'flex',
|
|
20
|
+
flexDirection: 'row',
|
|
21
|
+
width: 'auto',
|
|
22
|
+
padding: '4px 16px 0',
|
|
23
|
+
'.desktop-menu-item': {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
padding: '0 8px',
|
|
26
|
+
height: 'fit-content',
|
|
27
|
+
a: {
|
|
28
|
+
textDecoration: 'none',
|
|
29
|
+
color: 'var(--sidebar-color)',
|
|
30
|
+
i: {
|
|
31
|
+
paddingRight: '4px',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
return (
|
|
38
|
+
<div css={css} class='desktop-menu-box'>
|
|
39
|
+
<div class='flex-1 d-header-title'>{props.title}</div>
|
|
40
|
+
<div class='desktop-menu-bar'>
|
|
41
|
+
{props.items.map((item) => (
|
|
42
|
+
<div class='desktop-menu-item'>
|
|
43
|
+
<a href={item.href}>
|
|
44
|
+
<i class={`ifc-icon ${item.icon}`}></i>
|
|
45
|
+
{item.text}
|
|
46
|
+
</a>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
export * from './icon-menu-item-props';
|
|
1
2
|
export * from './mobile-footer-menu';
|
|
3
|
+
export * from './mobile-frame-with-header';
|
|
2
4
|
export * from './mobile-header-component';
|
|
5
|
+
export * from './mobile-header-title-icon';
|
|
6
|
+
export * from './mobile-side-menu';
|
|
7
|
+
export * from './mobile-top-sys-icon';
|
|
8
|
+
export * from './mobile-top-sys-menu';
|
|
9
|
+
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { CssProps } from 'lupine.
|
|
2
|
-
import {
|
|
1
|
+
import { CssProps, MediaQueryRange } from 'lupine.components';
|
|
2
|
+
import { IconMenuItemProps } from './icon-menu-item-props';
|
|
3
3
|
|
|
4
|
-
export interface MobileFooterMenuItemProps {
|
|
5
|
-
icon: string;
|
|
6
|
-
href: string;
|
|
7
|
-
text: string;
|
|
8
|
-
topout?: boolean;
|
|
9
|
-
}
|
|
10
4
|
export interface MobileFooterMenuProps {
|
|
11
|
-
items:
|
|
5
|
+
items: IconMenuItemProps[];
|
|
12
6
|
color?: string;
|
|
13
7
|
activeColor?: string;
|
|
14
8
|
topoutColor?: string;
|
|
@@ -51,8 +45,10 @@ export const MobileFooterMenu = (props: MobileFooterMenuProps) => {
|
|
|
51
45
|
'.footer-menu .footer-menu-item.footer-menu-topout': {
|
|
52
46
|
marginTop: '-43px',
|
|
53
47
|
borderRadius: '50%',
|
|
54
|
-
backgroundColor: props.topoutBackgroundColor || '#
|
|
48
|
+
backgroundColor: props.topoutBackgroundColor || '#f33939',
|
|
55
49
|
color: props.topoutColor || 'var(--primary-color)',
|
|
50
|
+
},
|
|
51
|
+
'.footer-menu .footer-menu-item-a': {
|
|
56
52
|
zIndex: 'var(--layer-header-footer)',
|
|
57
53
|
},
|
|
58
54
|
'.footer-menu .footer-menu-item.active': {
|
|
@@ -76,7 +72,7 @@ export const MobileFooterMenu = (props: MobileFooterMenuProps) => {
|
|
|
76
72
|
<div css={css} class='footer-menu-box'>
|
|
77
73
|
<div class='footer-menu'>
|
|
78
74
|
{props.items.map((item, index) => (
|
|
79
|
-
<a href={item.href} key={index}>
|
|
75
|
+
<a class='footer-menu-item-a' href={item.href} key={index}>
|
|
80
76
|
<div
|
|
81
77
|
class={`footer-menu-item ${item.topout ? 'footer-menu-topout' : ''} ${
|
|
82
78
|
curretnUrl === item.href ? 'active' : ''
|
|
@@ -88,11 +84,11 @@ export const MobileFooterMenu = (props: MobileFooterMenuProps) => {
|
|
|
88
84
|
</div>
|
|
89
85
|
</a>
|
|
90
86
|
))}
|
|
91
|
-
{/* <div class='footer-menu-item'><a href='/'><i class="ifc-icon
|
|
87
|
+
{/* <div class='footer-menu-item'><a href='/'><i class="ifc-icon ma-home-outline"></i>主页</a></div>
|
|
92
88
|
<div class='footer-menu-item'><a href='/user/tools'><i class="ifc-icon bo-multimedia-music-note"></i>工具</a></div>
|
|
93
89
|
<div class='footer-menu-item footer-menu-topout'><a href='/user/customer'><i class="ifc-icon co-cil-chat-bubble"></i>客服</a></div>
|
|
94
90
|
<div class='footer-menu-item'><a href='/user/member'><i class="ifc-icon ma-crown-outline"></i>会员</a></div>
|
|
95
|
-
<div class='footer-menu-item'><a href='/user/mine'><i class="ifc-icon
|
|
91
|
+
<div class='footer-menu-item'><a href='/user/mine'><i class="ifc-icon ma-account-cog-outline"></i>我的</a></div> */}
|
|
96
92
|
</div>
|
|
97
93
|
</div>
|
|
98
94
|
);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
HeaderWithBackFrame is a full page frame with header for mobile sliders.
|
|
3
|
+
It has Back icon at Left and Close icon at Right.
|
|
4
|
+
*/
|
|
1
5
|
import { VNode, CssProps, RefProps, HtmlVar } from 'lupine.components';
|
|
6
|
+
import { MobileHeaderTitleIcon } from './mobile-header-title-icon';
|
|
2
7
|
|
|
3
8
|
export const HeaderWithBackFrameHeight = '40px';
|
|
4
9
|
export const HeaderWithBackFrameLeft = ({ onClick }: { onClick: (event: Event) => void }) => {
|
|
@@ -16,10 +21,11 @@ export const HeaderWithBackFrameEmpty = () => {
|
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
export interface HeaderWithBackFrameHookProps {
|
|
19
|
-
updateTitle?: (title: string) => void;
|
|
24
|
+
updateTitle?: (title: VNode<any> | string) => void;
|
|
20
25
|
updateLeft?: (left: VNode<any>) => void;
|
|
21
26
|
updateRight?: (right: VNode<any>) => void;
|
|
22
27
|
}
|
|
28
|
+
// there may have a few HeaderWithBackFrame one over another at the same time
|
|
23
29
|
export const HeaderWithBackFrame = ({
|
|
24
30
|
children,
|
|
25
31
|
title,
|
|
@@ -30,7 +36,7 @@ export const HeaderWithBackFrame = ({
|
|
|
30
36
|
noHeader = false,
|
|
31
37
|
}: {
|
|
32
38
|
children: VNode<any>;
|
|
33
|
-
title: string;
|
|
39
|
+
title: VNode<any> | string;
|
|
34
40
|
onBack: (event: Event) => void;
|
|
35
41
|
left?: VNode<any>;
|
|
36
42
|
right?: VNode<any>;
|
|
@@ -51,7 +57,7 @@ export const HeaderWithBackFrame = ({
|
|
|
51
57
|
width: '100vw',
|
|
52
58
|
padding: '6px 0',
|
|
53
59
|
backgroundColor: 'var(--activatable-bg-color-normal)',
|
|
54
|
-
boxShadow: '
|
|
60
|
+
boxShadow: 'var(--mobile-header-shadow)',
|
|
55
61
|
},
|
|
56
62
|
'.header-back-content': {
|
|
57
63
|
display: 'flex',
|
|
@@ -61,10 +67,12 @@ export const HeaderWithBackFrame = ({
|
|
|
61
67
|
scrollbarWidth: 'none',
|
|
62
68
|
position: 'relative',
|
|
63
69
|
'&::-webkit-scrollbar': {
|
|
64
|
-
|
|
70
|
+
display: 'none',
|
|
71
|
+
// height: '0',
|
|
65
72
|
},
|
|
66
73
|
},
|
|
67
74
|
'.header-back-title': {
|
|
75
|
+
fontSize: '15px',
|
|
68
76
|
flex: '1',
|
|
69
77
|
color: 'var(--activatable-text-color-normal)',
|
|
70
78
|
overflow: 'hidden',
|
|
@@ -84,13 +92,11 @@ export const HeaderWithBackFrame = ({
|
|
|
84
92
|
'.header-back-left i, .header-back-right i': {
|
|
85
93
|
fontSize: '28px',
|
|
86
94
|
},
|
|
87
|
-
|
|
88
95
|
};
|
|
89
96
|
|
|
90
97
|
if (hook) {
|
|
91
|
-
hook.updateTitle = (title: string) => {
|
|
92
|
-
|
|
93
|
-
titleDom && (titleDom.textContent = title);
|
|
98
|
+
hook.updateTitle = (title: VNode<any> | string) => {
|
|
99
|
+
domCenter.value = title;
|
|
94
100
|
};
|
|
95
101
|
hook.updateLeft = (left: VNode<any>) => {
|
|
96
102
|
domLeft.value = left;
|
|
@@ -100,16 +106,19 @@ export const HeaderWithBackFrame = ({
|
|
|
100
106
|
};
|
|
101
107
|
}
|
|
102
108
|
const domLeft = HtmlVar(left);
|
|
109
|
+
const domCenter = HtmlVar(title);
|
|
103
110
|
const domRight = HtmlVar(right);
|
|
104
111
|
const ref: RefProps = {};
|
|
105
112
|
return (
|
|
106
113
|
<div ref={ref} css={css} class='header-back-frame'>
|
|
107
114
|
{!noHeader && (
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
<MobileHeaderTitleIcon
|
|
116
|
+
onBack={onBack}
|
|
117
|
+
left={domLeft.node}
|
|
118
|
+
title={domCenter.node}
|
|
119
|
+
right={domRight.node}
|
|
120
|
+
hook={hook}
|
|
121
|
+
/>
|
|
113
122
|
)}
|
|
114
123
|
<div class='header-back-content'>{children}</div>
|
|
115
124
|
</div>
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
MobileHeaderComponent is the topest header for mobile, and Left, Center and Right part can be updated by other components
|
|
3
|
+
|
|
4
|
+
If Left and Right are not set, then Center (100% width) can be used by temporiry headers like MobileHeaderTitleIcon,
|
|
5
|
+
to set Left and Right icons inside Center part.
|
|
6
|
+
|
|
7
|
+
For example, the header can be updated like this with different Left and Right icons:
|
|
8
|
+
<MobileHeaderCenter>
|
|
9
|
+
<MobileHeaderTitleIcon title='工具' left={<MobileHeaderEmptyIcon />} right={<MobileTopSysIcon />} />
|
|
10
|
+
</MobileHeaderCenter>
|
|
11
|
+
You can update Left, Right and Center separately, but it's convenient to use MobileHeaderCenter to set both Left and Right icons..
|
|
12
|
+
*/
|
|
13
|
+
import { CssProps, HtmlVar, VNode } from 'lupine.components';
|
|
3
14
|
|
|
4
15
|
export class MobileHeaderHelper {
|
|
5
16
|
private static instance: MobileHeaderHelper;
|
|
@@ -63,14 +74,15 @@ export const MobileHeaderHide = () => {
|
|
|
63
74
|
return <></>;
|
|
64
75
|
};
|
|
65
76
|
|
|
77
|
+
// there should be only one MobileHeaderComponent on a page
|
|
66
78
|
export const MobileHeaderComponent = (props: any) => {
|
|
67
79
|
const css: CssProps = {
|
|
68
80
|
display: 'flex',
|
|
69
81
|
flexDirection: 'row',
|
|
70
82
|
width: '100%',
|
|
71
83
|
height: 'auto',
|
|
72
|
-
padding: '2px 0',
|
|
73
|
-
boxShadow: '
|
|
84
|
+
// padding: '2px 0',
|
|
85
|
+
// boxShadow: 'var(--mobile-header-shadow)', // 第二项 2px 的话,顶部有阴影(线)
|
|
74
86
|
'& > *': {
|
|
75
87
|
height: '100%',
|
|
76
88
|
},
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
MobileHeaderTitleIcon can be used in MobileHeaderComponent's Center part.
|
|
3
|
+
It has it's own Left and Right icons.
|
|
4
|
+
*/
|
|
5
|
+
import { VNode, CssProps, HtmlVar } from 'lupine.components';
|
|
6
|
+
|
|
7
|
+
export const MobileHeadeIconHeight = '40px';
|
|
8
|
+
export const MobileHeadeBackIcon = ({ onClick }: { onClick: (event: Event) => void }) => {
|
|
9
|
+
return <i class='ifc-icon mg-arrow_back_ios_new_outlined mhti-back-icon' onClick={(event) => onClick(event)}></i>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const MobileHeadeCloseIcon = ({ onClick }: { onClick: (event: Event) => void }) => {
|
|
13
|
+
return <i class='ifc-icon ma-close mhti-close-icon' onClick={(event) => onClick(event)}></i>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const MobileHeaderEmptyIcon = () => {
|
|
17
|
+
return <div class='mhti-empty-icon' style={{ width: '28px' }}></div>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface MobileHeaderTitleIconHookProps {
|
|
21
|
+
updateTitle?: (title: VNode<any> | string) => void;
|
|
22
|
+
updateLeft?: (left: VNode<any>) => void;
|
|
23
|
+
updateRight?: (right: VNode<any>) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface MobileHeaderTitleIconProps {
|
|
26
|
+
title: VNode<any> | string;
|
|
27
|
+
onBack?: (event: Event) => void;
|
|
28
|
+
left?: VNode<any>;
|
|
29
|
+
right?: VNode<any>;
|
|
30
|
+
hook?: MobileHeaderTitleIconHookProps;
|
|
31
|
+
}
|
|
32
|
+
// there may have a few MobileHeaderTitleIcon for different pages
|
|
33
|
+
export const MobileHeaderTitleIcon = ({ title, onBack, left, right, hook }: MobileHeaderTitleIconProps) => {
|
|
34
|
+
// const processBack = (event: Event) => {
|
|
35
|
+
// if (onBack) {
|
|
36
|
+
// onBack(event);
|
|
37
|
+
// }
|
|
38
|
+
// };
|
|
39
|
+
// left = left || <MobileHeadeBackIcon onClick={processBack} />;
|
|
40
|
+
// right = right || <MobileHeadeCloseIcon onClick={processBack} />;
|
|
41
|
+
const css: CssProps = {
|
|
42
|
+
display: 'flex',
|
|
43
|
+
flexDirection: 'row',
|
|
44
|
+
width: '100vw',
|
|
45
|
+
padding: '6px 0',
|
|
46
|
+
backgroundColor: 'var(--activatable-bg-color-normal)',
|
|
47
|
+
boxShadow: 'var(--mobile-header-shadow)',
|
|
48
|
+
'.mhti-title': {
|
|
49
|
+
display: 'flex',
|
|
50
|
+
fontSize: '1.3rem',
|
|
51
|
+
flex: '1',
|
|
52
|
+
color: 'var(--activatable-text-color-normal)',
|
|
53
|
+
overflow: 'hidden',
|
|
54
|
+
textOverflow: 'ellipsis',
|
|
55
|
+
whiteSpace: 'nowrap',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
},
|
|
59
|
+
'.mhti-title > *': {
|
|
60
|
+
display: 'flex',
|
|
61
|
+
width: '100%',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
justifyContent: 'center',
|
|
64
|
+
},
|
|
65
|
+
'.mhti-left, .mhti-right': {
|
|
66
|
+
height: MobileHeadeIconHeight,
|
|
67
|
+
display: 'flex',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
justifyContent: 'center',
|
|
70
|
+
cursor: 'pointer',
|
|
71
|
+
fontSize: '16px',
|
|
72
|
+
},
|
|
73
|
+
'.mhti-left': {
|
|
74
|
+
paddingLeft: '8px',
|
|
75
|
+
},
|
|
76
|
+
'.mhti-right': {
|
|
77
|
+
paddingRight: '8px',
|
|
78
|
+
},
|
|
79
|
+
'.mhti-left i, .mhti-right i': {
|
|
80
|
+
fontSize: '28px',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (hook) {
|
|
85
|
+
hook.updateTitle = (title: VNode<any> | string) => {
|
|
86
|
+
domCenter.value = title;
|
|
87
|
+
};
|
|
88
|
+
hook.updateLeft = (left: VNode<any>) => {
|
|
89
|
+
domLeft.value = left;
|
|
90
|
+
};
|
|
91
|
+
hook.updateRight = (right: VNode<any>) => {
|
|
92
|
+
domRight.value = right;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const domLeft = HtmlVar(left);
|
|
96
|
+
const domCenter = HtmlVar(title);
|
|
97
|
+
const domRight = HtmlVar(right);
|
|
98
|
+
return (
|
|
99
|
+
<div css={css} class='mobile-header-title-icon-top'>
|
|
100
|
+
<div class='mhti-left'>{domLeft.node}</div>
|
|
101
|
+
<div class='mhti-title'>{domCenter.node}</div>
|
|
102
|
+
<div class='mhti-right'>{domRight.node}</div>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { CssProps, RefProps, VNode } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
export class MobileSideMenuHelper {
|
|
4
|
+
static show() {
|
|
5
|
+
const ref = document.querySelector('.mobile-side-menu-mask') as HTMLDivElement;
|
|
6
|
+
ref.classList.add('show');
|
|
7
|
+
setTimeout(() => {
|
|
8
|
+
ref.classList.add('animate-show');
|
|
9
|
+
}, 1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static hide() {
|
|
13
|
+
const ref = document.querySelector('.mobile-side-menu-mask') as HTMLDivElement;
|
|
14
|
+
ref.classList.remove('animate-show');
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
ref.classList.remove('show');
|
|
17
|
+
}, 300);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static isTouchEventAdded = false;
|
|
21
|
+
static addTouchEvent() {
|
|
22
|
+
if (this.isTouchEventAdded) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.isTouchEventAdded = true;
|
|
27
|
+
let touchstartY = 0;
|
|
28
|
+
let touchstartX = 0;
|
|
29
|
+
let direction = '';
|
|
30
|
+
let moveStart = false;
|
|
31
|
+
let isOpen = false;
|
|
32
|
+
const maskDom = document.querySelector('.mobile-side-menu-mask') as HTMLDivElement;
|
|
33
|
+
document.addEventListener('touchstart', (e) => {
|
|
34
|
+
touchstartY = e.touches[0].clientY;
|
|
35
|
+
touchstartX = e.touches[0].clientX;
|
|
36
|
+
direction = '';
|
|
37
|
+
moveStart = false;
|
|
38
|
+
isOpen = maskDom?.classList.contains('show');
|
|
39
|
+
if (isOpen) {
|
|
40
|
+
if (touchstartX > 80) {
|
|
41
|
+
moveStart = true;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
if (touchstartX < 40) {
|
|
45
|
+
moveStart = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// if (e.touches[0].clientX < 30 && !maskDom?.classList.contains('show')) {
|
|
49
|
+
// // 靠左边缘, 如果菜单已经打开,忽略手势
|
|
50
|
+
// touchStartX = e.touches[0].clientX;
|
|
51
|
+
// } else {
|
|
52
|
+
// touchStartX = -1; // 忽略非左侧边缘手势
|
|
53
|
+
// }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
document.addEventListener('touchmove', (e) => {
|
|
57
|
+
if (!moveStart) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// console.log('touchmove', e.touches[0].clientX);
|
|
62
|
+
if (direction === '') {
|
|
63
|
+
if (e.touches[0].clientX - touchstartX !== 0) {
|
|
64
|
+
direction = 'X';
|
|
65
|
+
} else {
|
|
66
|
+
moveStart = false;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isOpen) {
|
|
72
|
+
if (e.touches[0].clientX - touchstartX < 30) {
|
|
73
|
+
MobileSideMenuHelper.hide();
|
|
74
|
+
moveStart = false;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
if (e.touches[0].clientX - touchstartX > 80) {
|
|
79
|
+
MobileSideMenuHelper.show();
|
|
80
|
+
moveStart = false;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
document.addEventListener('touchend', () => {
|
|
87
|
+
moveStart = false;
|
|
88
|
+
direction = '';
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export const MobileSideMenu = (props: { children: VNode<any> }) => {
|
|
93
|
+
const css: CssProps = {
|
|
94
|
+
'.mobile-side-menu-mask': {
|
|
95
|
+
display: 'none',
|
|
96
|
+
flexDirection: 'column',
|
|
97
|
+
position: 'fixed',
|
|
98
|
+
top: '0',
|
|
99
|
+
left: '0',
|
|
100
|
+
right: '0',
|
|
101
|
+
bottom: '0',
|
|
102
|
+
zIndex: 'var(--layer-menu)',
|
|
103
|
+
backgroundColor: '#000000b0',
|
|
104
|
+
'&.show': {
|
|
105
|
+
display: 'flex',
|
|
106
|
+
},
|
|
107
|
+
'&.animate-show .mobile-side-menu': {
|
|
108
|
+
transform: 'scaleX(1)',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
'.mobile-side-menu': {
|
|
112
|
+
display: 'flex',
|
|
113
|
+
flexDirection: 'column',
|
|
114
|
+
padding: '16px',
|
|
115
|
+
transition: 'transform 0.3s ease-in-out',
|
|
116
|
+
backgroundColor: 'var(--primary-bg-color)',
|
|
117
|
+
width: '70%',
|
|
118
|
+
maxWidth: '300px',
|
|
119
|
+
height: '100%',
|
|
120
|
+
overflowX: 'hidden',
|
|
121
|
+
overflowY: 'auto',
|
|
122
|
+
transformOrigin: 'left',
|
|
123
|
+
transform: 'scaleX(0)',
|
|
124
|
+
// boxShadow: 'var(--block-box-shadow)',
|
|
125
|
+
boxShadow: 'var(--cover-box-shadow)',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const onClickContainer = (event: Event) => {
|
|
130
|
+
if (
|
|
131
|
+
event.target instanceof HTMLDivElement &&
|
|
132
|
+
(event.target as HTMLDivElement).classList.contains('mobile-side-menu-mask')
|
|
133
|
+
) {
|
|
134
|
+
MobileSideMenuHelper.hide();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const ref: RefProps = {
|
|
138
|
+
onLoad: async () => {
|
|
139
|
+
MobileSideMenuHelper.addTouchEvent();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
return (
|
|
143
|
+
<div css={css} ref={ref}>
|
|
144
|
+
{/* <SliderFrame hook={props.sliderFrameHook} /> */}
|
|
145
|
+
<div class='mobile-side-menu-mask' onClick={onClickContainer}>
|
|
146
|
+
<div class='mobile-side-menu'>{props.children}</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// import { NotificationColor, NotificationMessage, updateTheme, PopupMenuWithIcon, CssProps } from 'lupine.components';
|
|
2
|
+
import { CssProps } from 'lupine.components';
|
|
3
|
+
import { MobileSideMenuHelper } from './mobile-side-menu';
|
|
4
|
+
|
|
5
|
+
export const MobileTopSysIcon = () => {
|
|
6
|
+
const css: CssProps = {
|
|
7
|
+
cursor: 'pointer',
|
|
8
|
+
display: 'flex',
|
|
9
|
+
flexDirection: 'row',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
fontSize: '28px',
|
|
12
|
+
};
|
|
13
|
+
return (
|
|
14
|
+
<div css={css} onClick={() => MobileSideMenuHelper.show()}>
|
|
15
|
+
<i class='ifc-icon bs-list'></i>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
const onLogout = async () => {
|
|
3
|
+
logout('/');
|
|
4
|
+
};
|
|
5
|
+
const userCookie = getCookieUser();
|
|
6
|
+
const listMenu = [
|
|
7
|
+
userCookie && (userCookie.email || userCookie.phone || userCookie.wx) ? '退出登录' : '登录',
|
|
8
|
+
'',
|
|
9
|
+
'浅色模式',
|
|
10
|
+
'深色模式',
|
|
11
|
+
'系统模式',
|
|
12
|
+
'',
|
|
13
|
+
'帮助',
|
|
14
|
+
'关于',
|
|
15
|
+
];
|
|
16
|
+
if (userCookie && userCookie.admin === '1') {
|
|
17
|
+
listMenu.splice(0, 0, '管理员');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const handleSelected = (value: string) => {
|
|
21
|
+
if (value === '浅色模式') {
|
|
22
|
+
updateTheme('light');
|
|
23
|
+
} else if (value === '深色模式') {
|
|
24
|
+
updateTheme('dark');
|
|
25
|
+
} else if (value === '系统模式') {
|
|
26
|
+
const themeMedia = window.matchMedia('(prefers-color-scheme: light)');
|
|
27
|
+
if (themeMedia.matches) {
|
|
28
|
+
document.documentElement.classList.remove('dark');
|
|
29
|
+
document.documentElement.classList.add('light');
|
|
30
|
+
} else {
|
|
31
|
+
document.documentElement.classList.remove('light');
|
|
32
|
+
document.documentElement.classList.add('dark');
|
|
33
|
+
}
|
|
34
|
+
} else if (value === '退出登录') {
|
|
35
|
+
onLogout();
|
|
36
|
+
} else if (value === '登录') {
|
|
37
|
+
window.location.href = '/login';
|
|
38
|
+
} else if (value === '管理员') {
|
|
39
|
+
window.open('/admin');
|
|
40
|
+
} else {
|
|
41
|
+
NotificationMessage.sendMessage('Selected: ' + value, NotificationColor.Success);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
*/
|
|
45
|
+
import { PopupMenuWithIcon } from 'lupine.components';
|
|
46
|
+
|
|
47
|
+
export const MobileTopSysMenu = (props: { menuItems: string[]; handleSelected: (value: string) => void }) => {
|
|
48
|
+
return (
|
|
49
|
+
<PopupMenuWithIcon
|
|
50
|
+
list={props.menuItems}
|
|
51
|
+
defaultValue=''
|
|
52
|
+
tips=''
|
|
53
|
+
minWidth='auto'
|
|
54
|
+
maxWidth='200px'
|
|
55
|
+
maxHeight='300px'
|
|
56
|
+
align='right'
|
|
57
|
+
noTriangleIcon={true}
|
|
58
|
+
handleSelected={props.handleSelected}
|
|
59
|
+
noUpdateLabel={true}
|
|
60
|
+
></PopupMenuWithIcon>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { CssProps, RefProps, VNode } from 'lupine.web';
|
|
2
2
|
import { stopPropagation } from '../lib';
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type PopupMenuHookProps = {
|
|
5
|
+
openMenu?: (event?: MouseEvent) => void;
|
|
6
|
+
getValue?: () => string;
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
// defaultValue=<i class='ifc-icon co-cil-hamburger-menu'></i>
|
|
7
10
|
export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
8
|
-
const
|
|
11
|
+
const hook: PopupMenuHookProps = {};
|
|
9
12
|
const css: CssProps = {
|
|
10
13
|
cursor: 'pointer',
|
|
11
14
|
display: 'flex',
|
|
@@ -16,7 +19,7 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
16
19
|
return (
|
|
17
20
|
<div
|
|
18
21
|
onClick={() => {
|
|
19
|
-
|
|
22
|
+
hook.openMenu && hook.openMenu();
|
|
20
23
|
}}
|
|
21
24
|
css={css}
|
|
22
25
|
>
|
|
@@ -30,8 +33,8 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
30
33
|
handleSelected={props.handleSelected}
|
|
31
34
|
handleOpened={props.handleOpened}
|
|
32
35
|
handleClosed={props.handleClosed}
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
37
|
+
hook={hook}
|
|
35
38
|
noTriangleIcon={props.noTriangleIcon}
|
|
36
39
|
></PopupMenu>
|
|
37
40
|
</div>
|
|
@@ -40,7 +43,7 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
40
43
|
|
|
41
44
|
export type PopupMenuProps = {
|
|
42
45
|
list: string[];
|
|
43
|
-
defaultValue: string
|
|
46
|
+
defaultValue: string;
|
|
44
47
|
tips?: string;
|
|
45
48
|
minWidth?: string;
|
|
46
49
|
maxWidth?: string;
|
|
@@ -48,8 +51,8 @@ export type PopupMenuProps = {
|
|
|
48
51
|
handleSelected?: Function;
|
|
49
52
|
handleOpened?: Function;
|
|
50
53
|
handleClosed?: Function;
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
noUpdateLabel?: boolean;
|
|
55
|
+
hook?: PopupMenuHookProps;
|
|
53
56
|
noTriangleIcon?: boolean;
|
|
54
57
|
align?: 'left' | 'right';
|
|
55
58
|
};
|
|
@@ -57,12 +60,12 @@ export type PopupMenuProps = {
|
|
|
57
60
|
export type PopupMenuWithButtonProps = { label: string } & PopupMenuProps;
|
|
58
61
|
|
|
59
62
|
export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
60
|
-
const
|
|
63
|
+
const hook: PopupMenuHookProps = {};
|
|
61
64
|
return (
|
|
62
65
|
<button
|
|
63
66
|
class='button-base'
|
|
64
67
|
onClick={() => {
|
|
65
|
-
|
|
68
|
+
hook.openMenu && hook.openMenu();
|
|
66
69
|
}}
|
|
67
70
|
css={{ '>div': { float: 'right', textAlign: 'left' } }}
|
|
68
71
|
>
|
|
@@ -77,8 +80,8 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
|
77
80
|
handleSelected={props.handleSelected}
|
|
78
81
|
handleOpened={props.handleOpened}
|
|
79
82
|
handleClosed={props.handleClosed}
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
84
|
+
hook={hook}
|
|
82
85
|
noTriangleIcon={props.noTriangleIcon}
|
|
83
86
|
align={props.align}
|
|
84
87
|
></PopupMenu>
|
|
@@ -89,11 +92,11 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
|
89
92
|
export type PopupMenuWithLabelProps = { label: string } & PopupMenuProps;
|
|
90
93
|
|
|
91
94
|
export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
|
|
92
|
-
const
|
|
95
|
+
const hook: PopupMenuHookProps = {};
|
|
93
96
|
return (
|
|
94
97
|
<div
|
|
95
98
|
onClick={() => {
|
|
96
|
-
|
|
99
|
+
hook.openMenu && hook.openMenu();
|
|
97
100
|
}}
|
|
98
101
|
css={{ cursor: 'pointer', '>div': { float: 'right', textAlign: 'left' } }}
|
|
99
102
|
>
|
|
@@ -108,8 +111,8 @@ export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
|
|
|
108
111
|
handleSelected={props.handleSelected}
|
|
109
112
|
handleOpened={props.handleOpened}
|
|
110
113
|
handleClosed={props.handleClosed}
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
115
|
+
hook={hook}
|
|
113
116
|
noTriangleIcon={props.noTriangleIcon}
|
|
114
117
|
align={props.align}
|
|
115
118
|
></PopupMenu>
|
|
@@ -127,12 +130,12 @@ export const PopupMenu = ({
|
|
|
127
130
|
handleSelected,
|
|
128
131
|
handleOpened,
|
|
129
132
|
handleClosed,
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
noUpdateLabel,
|
|
134
|
+
hook,
|
|
132
135
|
align = 'right',
|
|
133
136
|
noTriangleIcon,
|
|
134
137
|
}: PopupMenuProps) => {
|
|
135
|
-
const css:
|
|
138
|
+
const css: CssProps = {
|
|
136
139
|
'.popup-menu-item': {
|
|
137
140
|
padding: '0 0 1px 0',
|
|
138
141
|
display: 'inline-block',
|
|
@@ -209,7 +212,8 @@ export const PopupMenu = ({
|
|
|
209
212
|
|
|
210
213
|
let ref: RefProps = { id: '' };
|
|
211
214
|
let isShowing = false;
|
|
212
|
-
|
|
215
|
+
let selectedValue = defaultValue;
|
|
216
|
+
const openMenu = (event?: MouseEvent) => {
|
|
213
217
|
stopPropagation(event);
|
|
214
218
|
|
|
215
219
|
handleOpened && handleOpened();
|
|
@@ -224,8 +228,9 @@ export const PopupMenu = ({
|
|
|
224
228
|
listDom.classList.toggle('open', isShowing);
|
|
225
229
|
ref.$('.popup-menu-list .menu-focus').focus();
|
|
226
230
|
};
|
|
227
|
-
if (
|
|
228
|
-
|
|
231
|
+
if (hook) {
|
|
232
|
+
hook.openMenu = openMenu;
|
|
233
|
+
hook.getValue = () => selectedValue;
|
|
229
234
|
}
|
|
230
235
|
const itemClick = (event: any) => {
|
|
231
236
|
stopPropagation(event);
|
|
@@ -234,7 +239,8 @@ export const PopupMenu = ({
|
|
|
234
239
|
isShowing = false;
|
|
235
240
|
ref.$('.popup-menu-list').classList.remove('open');
|
|
236
241
|
if (event.target) {
|
|
237
|
-
|
|
242
|
+
selectedValue = event.target.innerText;
|
|
243
|
+
if (noUpdateLabel !== true) {
|
|
238
244
|
ref.$('.popup-menu-item .popup-menu-text').innerText = event.target.innerText;
|
|
239
245
|
}
|
|
240
246
|
if (handleSelected) {
|
|
@@ -252,7 +258,7 @@ export const PopupMenu = ({
|
|
|
252
258
|
};
|
|
253
259
|
|
|
254
260
|
return (
|
|
255
|
-
<div ref={ref} css={css} onClick={
|
|
261
|
+
<div ref={ref} css={css} onClick={openMenu} title={tips}>
|
|
256
262
|
<div class='popup-menu-item'>
|
|
257
263
|
<span class={'popup-menu-text' + (noTriangleIcon !== true ? ' triangle-icon' : '')}>
|
|
258
264
|
{defaultValue || ' '}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RefProps } from 'lupine.web';
|
|
1
|
+
import { CssProps, RefProps } from 'lupine.web';
|
|
2
2
|
import { HtmlVar } from './html-var';
|
|
3
3
|
|
|
4
4
|
export type ProgressHookProps = {
|
|
@@ -10,7 +10,7 @@ export type ProgressProps = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const Progress = (props: ProgressProps) => {
|
|
13
|
-
const css:
|
|
13
|
+
const css: CssProps = {
|
|
14
14
|
position: 'fixed',
|
|
15
15
|
display: 'flex',
|
|
16
16
|
bottom: '0',
|
package/src/frames/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* ResponsiveFrame for desktop and mobile
|
|
2
|
+
sliderFrameHook is for slider frame from right or bottom used in side-menu
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VNode, CssProps, MediaQueryRange, SliderFrameHookProps, SliderFrame } from 'lupine.components';
|
|
6
|
+
import { MobileFooterMenu } from '../components/mobile-components/mobile-footer-menu';
|
|
7
|
+
import { DesktopFooter } from '../components/desktop-footer';
|
|
8
|
+
import { DesktopHeader } from '../components/desktop-header';
|
|
3
9
|
import { MobileHeaderComponent } from '../components/mobile-components/mobile-header-component';
|
|
10
|
+
import { MobileSideMenu } from '../components/mobile-components/mobile-side-menu';
|
|
11
|
+
import { IconMenuItemProps } from '../components/mobile-components/icon-menu-item-props';
|
|
4
12
|
|
|
5
|
-
export
|
|
6
|
-
placeholderClassname: string
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
export interface ResponsiveFrameProps {
|
|
14
|
+
placeholderClassname: string;
|
|
15
|
+
mainContent: VNode<any>;
|
|
16
|
+
desktopHeaderTitle: string;
|
|
17
|
+
desktopFooterTitle: string;
|
|
18
|
+
desktopTopMenu: IconMenuItemProps[];
|
|
19
|
+
mobileBottomMenu: IconMenuItemProps[];
|
|
20
|
+
mobileSideMenuContent: VNode<any>;
|
|
21
|
+
sliderFrameHook: SliderFrameHookProps;
|
|
22
|
+
}
|
|
23
|
+
export const ResponsiveFrame = async (props: ResponsiveFrameProps) => {
|
|
13
24
|
const cssContainer: CssProps = {
|
|
14
25
|
display: 'flex',
|
|
15
26
|
flexDirection: 'column',
|
|
@@ -20,22 +31,16 @@ export const ResponsiveFrame = async (
|
|
|
20
31
|
display: 'flex',
|
|
21
32
|
flexDirection: 'column',
|
|
22
33
|
width: '100vw',
|
|
23
|
-
// height: '72px',
|
|
24
|
-
// position: 'fixed',
|
|
25
|
-
// left: 0,
|
|
26
|
-
// top: 0,
|
|
27
|
-
// zIndex: 'var(--layer-menu)',
|
|
28
34
|
backgroundColor: 'var(--activatable-bg-color-normal)',
|
|
29
35
|
},
|
|
30
36
|
'.frame-content': {
|
|
31
37
|
display: 'flex',
|
|
32
38
|
flex: '1',
|
|
33
39
|
flexDirection: 'column',
|
|
34
|
-
// paddingTop: '100px',
|
|
35
40
|
overflowY: 'auto',
|
|
36
41
|
scrollbarWidth: 'none',
|
|
37
42
|
'&::-webkit-scrollbar': {
|
|
38
|
-
|
|
43
|
+
display: 'none',
|
|
39
44
|
},
|
|
40
45
|
},
|
|
41
46
|
'.content-block': {
|
|
@@ -45,39 +50,32 @@ export const ResponsiveFrame = async (
|
|
|
45
50
|
overflowY: 'auto',
|
|
46
51
|
scrollbarWidth: 'none',
|
|
47
52
|
},
|
|
53
|
+
'.content-block::-webkit-scrollbar': {
|
|
54
|
+
display: 'none',
|
|
55
|
+
},
|
|
48
56
|
'.content-block .padding-block': {
|
|
49
57
|
padding: '0 16px',
|
|
50
58
|
},
|
|
51
|
-
// '.frame-footer': {
|
|
52
|
-
// paddingTop: '57px', // 应该和底部菜单的高度一致
|
|
53
|
-
// },
|
|
54
59
|
[MediaQueryRange.TabletBelow]: {
|
|
55
|
-
// .header-box,
|
|
56
60
|
'.frame-footer .footer-box, .frame-top-menu .desktop-menu-box': {
|
|
57
61
|
display: 'none',
|
|
58
62
|
},
|
|
59
|
-
// '.content-block': {
|
|
60
|
-
// paddingBottom: '16px',
|
|
61
|
-
// },
|
|
62
|
-
'.metronome-page-box, .gauge-box': {
|
|
63
|
-
boxShadow: '#313131 2.02px 2.02px 5.08px 1px !important',
|
|
64
|
-
},
|
|
65
63
|
},
|
|
66
64
|
};
|
|
67
65
|
|
|
68
66
|
return (
|
|
69
67
|
<div css={cssContainer} class='responsive-frame'>
|
|
68
|
+
<SliderFrame hook={props.sliderFrameHook} />
|
|
70
69
|
<div class='frame-top-menu'>
|
|
71
|
-
|
|
70
|
+
<DesktopHeader title={props.desktopHeaderTitle} items={props.desktopTopMenu}></DesktopHeader>
|
|
72
71
|
<MobileHeaderComponent></MobileHeaderComponent>
|
|
73
72
|
</div>
|
|
74
73
|
<div class='frame-content'>
|
|
75
|
-
<
|
|
74
|
+
<MobileSideMenu>{props.mobileSideMenuContent}</MobileSideMenu>
|
|
75
|
+
<div class={'content-block ' + props.placeholderClassname}>{props.mainContent}</div>
|
|
76
76
|
<div class='frame-footer'>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
</div>
|
|
80
|
-
<MobileFooterMenu items={bottomMenu}></MobileFooterMenu>
|
|
77
|
+
<DesktopFooter title={props.desktopFooterTitle}></DesktopFooter>
|
|
78
|
+
<MobileFooterMenu items={props.mobileBottomMenu}></MobileFooterMenu>
|
|
81
79
|
</div>
|
|
82
80
|
</div>
|
|
83
81
|
</div>
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* frame component to show pages (sliders) from right or bottom
|
|
2
|
+
const sliderFrameHook: SliderFrameHookProps = {};
|
|
3
|
+
const onClick = (event: Event) => {
|
|
4
|
+
sliderFrameHook.load!(<... />);
|
|
5
|
+
or
|
|
6
|
+
sliderFrameHook.close!(event);
|
|
7
|
+
};
|
|
8
|
+
return (
|
|
9
|
+
<div onClick={onClick}>
|
|
10
|
+
<SliderFrame hook={sliderFrameHook} />
|
|
11
|
+
...
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
*/
|
|
15
|
+
import { VNode, CssProps, HtmlVar, RefProps, stopPropagation, MediaQueryRange } from 'lupine.components';
|
|
5
16
|
|
|
6
17
|
export type SliderFramePosition = 'desktop-slide-left' | 'desktop-slide-right';
|
|
7
18
|
export type SliderFrameHookProps = {
|
|
@@ -69,20 +80,21 @@ export const SliderFrame = (props: SliderFrameProps) => {
|
|
|
69
80
|
},
|
|
70
81
|
'&.desktop-slide-right': {
|
|
71
82
|
[MediaQueryRange.TabletAbove]: {
|
|
72
|
-
top: '
|
|
83
|
+
top: '59px',
|
|
73
84
|
left: '30%',
|
|
74
85
|
transform: 'translateX(0)',
|
|
75
|
-
|
|
86
|
+
// notice: here is connected with mobile-header-title-icon.tsx
|
|
87
|
+
'.mobile-header-title-icon-top': {
|
|
76
88
|
width: '100%',
|
|
77
89
|
boxShadow: 'unset',
|
|
78
90
|
},
|
|
79
91
|
'.header-back-content': {
|
|
80
92
|
width: '100%',
|
|
81
93
|
},
|
|
82
|
-
'.
|
|
94
|
+
'.mhti-title': {
|
|
83
95
|
fontSize: '15px',
|
|
84
96
|
},
|
|
85
|
-
'.
|
|
97
|
+
'.mhti-left, .mhti-right': {
|
|
86
98
|
display: 'none',
|
|
87
99
|
},
|
|
88
100
|
'&.d-none': {
|
package/src/frames/top-frame.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/* most top-frame for all other pages
|
|
2
|
+
place to set safe-area-inset-top and other common styles
|
|
3
|
+
*/
|
|
4
|
+
import { VNode, CssProps } from 'lupine.components';
|
|
2
5
|
|
|
3
6
|
export const TopFrame = async (placeholderClassname: string, vnode: VNode<any>) => {
|
|
4
7
|
const cssContainer: CssProps = {
|
|
@@ -18,7 +21,6 @@ export const TopFrame = async (placeholderClassname: string, vnode: VNode<any>)
|
|
|
18
21
|
},
|
|
19
22
|
};
|
|
20
23
|
|
|
21
|
-
// console.log('Web version: ', getWebVersion());
|
|
22
24
|
return (
|
|
23
25
|
<div css={cssContainer}>
|
|
24
26
|
{/* Can't put css on this placeholder node! */}
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
downloadStream(blob, filename);
|
|
5
5
|
*/
|
|
6
6
|
export const downloadStream = (blob: Blob, filename?: string) => {
|
|
7
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
7
8
|
const dom = document.createElement('a');
|
|
8
|
-
dom.setAttribute('href',
|
|
9
|
+
dom.setAttribute('href', blobUrl);
|
|
9
10
|
dom.setAttribute('download', filename || 'true');
|
|
10
11
|
dom.style.display = 'none';
|
|
11
12
|
document.body.appendChild(dom);
|
|
12
13
|
dom.click();
|
|
13
14
|
setTimeout(() => {
|
|
14
15
|
document.body.removeChild(dom);
|
|
16
|
+
URL.revokeObjectURL(blobUrl);
|
|
15
17
|
}, 3000);
|
|
16
18
|
return dom;
|
|
17
19
|
};
|
package/src/lib/upload-file.ts
CHANGED
|
@@ -73,9 +73,12 @@ export const uploadFile = async (
|
|
|
73
73
|
chunkSize = _saveChunkSize.size;
|
|
74
74
|
}
|
|
75
75
|
if (len <= chunkSize) {
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
return
|
|
76
|
+
const result = await uploadFileChunk(file, 0, 1, uploadUrl, key, retryCount, retryMessage);
|
|
77
|
+
if (!result || result.status !== 'ok') {
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
if (progressFn) {
|
|
81
|
+
progressFn(1, 0, len);
|
|
79
82
|
}
|
|
80
83
|
return true;
|
|
81
84
|
}
|
|
@@ -85,11 +88,11 @@ export const uploadFile = async (
|
|
|
85
88
|
const start = i * chunkSize;
|
|
86
89
|
const end = Math.min((i + 1) * chunkSize, len);
|
|
87
90
|
const chunk = file.slice(start, end);
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
90
|
-
return
|
|
91
|
+
const result = await uploadFileChunk(chunk, i, totalChunks, uploadUrl, key, retryCount, retryMessage);
|
|
92
|
+
if (!result || result.status !== 'ok') {
|
|
93
|
+
return result;
|
|
91
94
|
}
|
|
92
|
-
key =
|
|
95
|
+
key = result.key;
|
|
93
96
|
if (progressFn) {
|
|
94
97
|
progressFn(Math.round(((i + 1) / totalChunks) * 100) / 100, i, totalChunks);
|
|
95
98
|
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { VNode, CssProps } from 'lupine.web';
|
|
2
|
-
import { MediaQueryMaxWidth } from '../styles';
|
|
3
|
-
import { MenuBar } from '../components';
|
|
4
|
-
|
|
5
|
-
export interface MenuBarMenuProps {
|
|
6
|
-
id: string;
|
|
7
|
-
text: string;
|
|
8
|
-
url: string;
|
|
9
|
-
}
|
|
10
|
-
export const DesktopFrame = async (
|
|
11
|
-
placeholderClassname: string,
|
|
12
|
-
vnode: VNode<any>,
|
|
13
|
-
title: string,
|
|
14
|
-
footerTitle: string,
|
|
15
|
-
logoUrl: string,
|
|
16
|
-
menu: MenuBarMenuProps[]
|
|
17
|
-
) => {
|
|
18
|
-
const cssContainer: CssProps = {
|
|
19
|
-
display: 'flex',
|
|
20
|
-
'flex-direction': 'column',
|
|
21
|
-
width: '100%',
|
|
22
|
-
'min-height': '100%',
|
|
23
|
-
'overflow-y': 'auto',
|
|
24
|
-
'.content-block': {
|
|
25
|
-
display: 'flex',
|
|
26
|
-
flex: '1',
|
|
27
|
-
margin: 'auto',
|
|
28
|
-
maxWidth: MediaQueryMaxWidth.DesktopMax,
|
|
29
|
-
width: '100%',
|
|
30
|
-
},
|
|
31
|
-
'.top-footer': {
|
|
32
|
-
paddingTop: '16px',
|
|
33
|
-
},
|
|
34
|
-
'.header-box': {
|
|
35
|
-
display: 'flex',
|
|
36
|
-
'flex-direction': 'column',
|
|
37
|
-
width: '100%',
|
|
38
|
-
'min-height': '100%',
|
|
39
|
-
'overflow-y': 'auto',
|
|
40
|
-
'.content-block': {
|
|
41
|
-
display: 'flex',
|
|
42
|
-
flex: '1',
|
|
43
|
-
margin: 'auto',
|
|
44
|
-
maxWidth: MediaQueryMaxWidth.DesktopMax,
|
|
45
|
-
width: '100%',
|
|
46
|
-
},
|
|
47
|
-
'.top-footer': {
|
|
48
|
-
paddingTop: '16px',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
'.footer-box': {
|
|
52
|
-
display: 'flex',
|
|
53
|
-
padding: '0 32px 16px',
|
|
54
|
-
'.footer-cp': {
|
|
55
|
-
padding: '1px 15px',
|
|
56
|
-
margin: 'auto',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div css={cssContainer}>
|
|
63
|
-
<div class='header-box'>
|
|
64
|
-
<div class='logo-box'>
|
|
65
|
-
<img class='logo' src={logoUrl} />
|
|
66
|
-
</div>
|
|
67
|
-
<div class='header-title'>
|
|
68
|
-
{title}
|
|
69
|
-
{/* <div class='header-subtitle pt-s'>{'ver: ' + getWebVersion()}</div> */}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
<MenuBar items={menu} maxWidthMobileMenu={'800px'} maxWidth={MediaQueryMaxWidth.DesktopMax}></MenuBar>
|
|
73
|
-
<div class={'content-block ' + placeholderClassname}>{vnode}</div>
|
|
74
|
-
<div class='top-footer'>
|
|
75
|
-
<div class='footer-box'>
|
|
76
|
-
<div class='footer-cp'>{footerTitle}</div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
};
|