lupine.components 1.0.10 → 1.0.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.components",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -34,3 +34,5 @@ export * from './text-wave';
34
34
  export * from './theme-selector';
35
35
  export * from './toggle-base';
36
36
  export * from './toggle-switch';
37
+
38
+ export * from './mobile-components';
@@ -0,0 +1,2 @@
1
+ export * from './mobile-footer-menu';
2
+ export * from './mobile-header-component';
@@ -0,0 +1,99 @@
1
+ import { CssProps } from 'lupine.web';
2
+ import { MediaQueryRange } from '../../styles';
3
+
4
+ export interface MobileFooterMenuItemProps {
5
+ icon: string;
6
+ href: string;
7
+ text: string;
8
+ topout?: boolean;
9
+ }
10
+ export interface MobileFooterMenuProps {
11
+ items: MobileFooterMenuItemProps[];
12
+ color?: string;
13
+ activeColor?: string;
14
+ topoutColor?: string;
15
+ topoutBackgroundColor?: string;
16
+ }
17
+ export const MobileFooterMenu = (props: MobileFooterMenuProps) => {
18
+ const css: CssProps = {
19
+ '.footer-menu': {
20
+ display: 'none',
21
+ // position: 'fixed',
22
+ // left: 0,
23
+ // right: 0,
24
+ // bottom: 0,
25
+ width: '100%',
26
+ background: 'var(--sidebar-bg-color)',
27
+ paddingBottom: 'env(safe-area-inset-bottom)',
28
+ minHeight: '50px',
29
+ justifyContent: 'space-around',
30
+ alignItems: 'center',
31
+ borderTop: 'var(--primary-border)',
32
+ },
33
+ '.footer-menu, .footer-menu a': {
34
+ textDecoration: 'none',
35
+ color: props.color || 'var(--primary-color)',
36
+ },
37
+ '.footer-menu .footer-menu-item': {
38
+ padding: '4px 16px 4px 16px',
39
+ fontSize: '11px',
40
+ height: '55px', // 和主页保留的底部菜单高度一致
41
+ width: '55px',
42
+ display: 'flex',
43
+ flexWrap: 'wrap',
44
+ alignItems: 'center',
45
+ },
46
+ '.footer-menu .footer-menu-item i': {
47
+ display: 'block',
48
+ fontSize: '22px',
49
+ marginBottom: '4px',
50
+ },
51
+ '.footer-menu .footer-menu-item.footer-menu-topout': {
52
+ marginTop: '-43px',
53
+ borderRadius: '50%',
54
+ backgroundColor: props.topoutBackgroundColor || '#ff8f8f',
55
+ color: props.topoutColor || 'var(--primary-color)',
56
+ zIndex: 'var(--layer-header-footer)',
57
+ },
58
+ '.footer-menu .footer-menu-item.active': {
59
+ color: props.activeColor || 'var(--primary-accent-color)',
60
+ },
61
+ [MediaQueryRange.TabletBelow]: {
62
+ '.footer-menu': {
63
+ display: 'flex',
64
+ },
65
+ },
66
+ };
67
+ const onClick = (index: number, href: string) => {
68
+ const items = document.querySelector('.footer-menu-item.active');
69
+ items?.classList.remove('active');
70
+ // find footer-menu-item by index
71
+ const item = document.querySelector(`a:nth-child(${index + 1}) .footer-menu-item`);
72
+ item?.classList.add('active');
73
+ };
74
+ let curretnUrl = typeof window !== 'undefined' ? window.location.pathname : '';
75
+ return (
76
+ <div css={css} class='footer-menu-box'>
77
+ <div class='footer-menu'>
78
+ {props.items.map((item, index) => (
79
+ <a href={item.href} key={index}>
80
+ <div
81
+ class={`footer-menu-item ${item.topout ? 'footer-menu-topout' : ''} ${
82
+ curretnUrl === item.href ? 'active' : ''
83
+ }`}
84
+ onClick={() => onClick(index, item.href)}
85
+ >
86
+ <i class={`ifc-icon ${item.icon}`}></i>
87
+ {item.text}
88
+ </div>
89
+ </a>
90
+ ))}
91
+ {/* <div class='footer-menu-item'><a href='/'><i class="ifc-icon bs-person-gear"></i>主页</a></div>
92
+ <div class='footer-menu-item'><a href='/user/tools'><i class="ifc-icon bo-multimedia-music-note"></i>工具</a></div>
93
+ <div class='footer-menu-item footer-menu-topout'><a href='/user/customer'><i class="ifc-icon co-cil-chat-bubble"></i>客服</a></div>
94
+ <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 bs-person-gear"></i>我的</a></div> */}
96
+ </div>
97
+ </div>
98
+ );
99
+ };
@@ -0,0 +1,89 @@
1
+ import { CssProps, VNode } from 'lupine.web';
2
+ import { HtmlVar } from '../html-var';
3
+
4
+ export class MobileHeaderHelper {
5
+ private static instance: MobileHeaderHelper;
6
+
7
+ private leftContent = HtmlVar('');
8
+ private centerContent = HtmlVar('');
9
+ private rightContent = HtmlVar('');
10
+ private constructor() {}
11
+
12
+ public static getInstance(): MobileHeaderHelper {
13
+ if (!MobileHeaderHelper.instance) {
14
+ MobileHeaderHelper.instance = new MobileHeaderHelper();
15
+ }
16
+ return MobileHeaderHelper.instance;
17
+ }
18
+
19
+ public setLeftContent(content: string | VNode<any>) {
20
+ this.leftContent.value = content;
21
+ }
22
+ public getLeftContent() {
23
+ return this.leftContent;
24
+ }
25
+ public setCenterContent(content: string | VNode<any>) {
26
+ this.centerContent.value = content;
27
+ }
28
+ public getCenterContent() {
29
+ return this.centerContent;
30
+ }
31
+ public setRightContent(content: string | VNode<any>) {
32
+ this.rightContent.value = content;
33
+ }
34
+ public getRightContent() {
35
+ return this.rightContent;
36
+ }
37
+
38
+ public hideHeader() {
39
+ this.leftContent.value = '';
40
+ this.centerContent.value = '';
41
+ this.rightContent.value = '';
42
+ }
43
+ }
44
+ export const mobileHeaderHelper = MobileHeaderHelper.getInstance();
45
+
46
+ export const MobileHeaderLeft = (props: { children: VNode<any> }) => {
47
+ mobileHeaderHelper.setLeftContent(props.children);
48
+ return <></>;
49
+ };
50
+
51
+ export const MobileHeaderCenter = (props: { children: VNode<any> }) => {
52
+ mobileHeaderHelper.setCenterContent(props.children);
53
+ return <></>;
54
+ };
55
+
56
+ export const MobileHeaderRight = (props: { children: VNode<any> }) => {
57
+ mobileHeaderHelper.setRightContent(props.children);
58
+ return <></>;
59
+ };
60
+
61
+ export const MobileHeaderHide = () => {
62
+ mobileHeaderHelper.hideHeader();
63
+ return <></>;
64
+ };
65
+
66
+ export const MobileHeaderComponent = (props: any) => {
67
+ const css: CssProps = {
68
+ display: 'flex',
69
+ flexDirection: 'row',
70
+ width: '100%',
71
+ height: 'auto',
72
+ padding: '2px 0',
73
+ boxShadow: '0 4px 4px var(--primary-border-color)', // 第二项 2px 的话,顶部有阴影(线)
74
+ '& > *': {
75
+ height: '100%',
76
+ },
77
+ '.mobile-header-center': {
78
+ flex: 1,
79
+ },
80
+ };
81
+
82
+ return (
83
+ <div css={css} class='mobile-header-component'>
84
+ <div class='mobile-header-left'>{mobileHeaderHelper.getLeftContent().node}</div>
85
+ <div class='mobile-header-center'>{mobileHeaderHelper.getCenterContent().node}</div>
86
+ <div class='mobile-header-right'>{mobileHeaderHelper.getRightContent().node}</div>
87
+ </div>
88
+ );
89
+ };
@@ -1,11 +1,46 @@
1
- import { RefProps } from 'lupine.web';
1
+ import { CssProps, RefProps, VNode } from 'lupine.web';
2
2
  import { stopPropagation } from '../lib';
3
3
 
4
4
  export type PopupMenuOpenMenuProps = { handle?: Function };
5
5
 
6
+ // defaultValue=<i class='ifc-icon co-cil-hamburger-menu'></i>
7
+ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
8
+ const handle: PopupMenuOpenMenuProps = {};
9
+ const css: CssProps = {
10
+ cursor: 'pointer',
11
+ display: 'flex',
12
+ flexDirection: 'row',
13
+ alignItems: 'center',
14
+ fontSize: '24px',
15
+ };
16
+ return (
17
+ <div
18
+ onClick={() => {
19
+ handle.handle && handle.handle();
20
+ }}
21
+ css={css}
22
+ >
23
+ <PopupMenu
24
+ list={props.list}
25
+ defaultValue={props.defaultValue}
26
+ tips={props.tips}
27
+ minWidth={props.minWidth}
28
+ maxWidth={props.maxWidth}
29
+ maxHeight={props.maxHeight}
30
+ handleSelected={props.handleSelected}
31
+ handleOpened={props.handleOpened}
32
+ handleClosed={props.handleClosed}
33
+ noUpdateValue={props.noUpdateValue}
34
+ refOpenMenu={handle}
35
+ noTriangleIcon={props.noTriangleIcon}
36
+ ></PopupMenu>
37
+ </div>
38
+ );
39
+ };
40
+
6
41
  export type PopupMenuProps = {
7
42
  list: string[];
8
- defaultValue: string;
43
+ defaultValue: string | VNode<any>;
9
44
  tips?: string;
10
45
  minWidth?: string;
11
46
  maxWidth?: string;
@@ -15,6 +50,8 @@ export type PopupMenuProps = {
15
50
  handleClosed?: Function;
16
51
  noUpdateValue?: boolean;
17
52
  refOpenMenu?: PopupMenuOpenMenuProps;
53
+ noTriangleIcon?: boolean;
54
+ align?: 'left' | 'right';
18
55
  };
19
56
 
20
57
  export type PopupMenuWithButtonProps = { label: string } & PopupMenuProps;
@@ -42,6 +79,8 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
42
79
  handleClosed={props.handleClosed}
43
80
  noUpdateValue={props.noUpdateValue}
44
81
  refOpenMenu={handle}
82
+ noTriangleIcon={props.noTriangleIcon}
83
+ align={props.align}
45
84
  ></PopupMenu>
46
85
  </button>
47
86
  );
@@ -71,6 +110,8 @@ export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
71
110
  handleClosed={props.handleClosed}
72
111
  noUpdateValue={props.noUpdateValue}
73
112
  refOpenMenu={handle}
113
+ noTriangleIcon={props.noTriangleIcon}
114
+ align={props.align}
74
115
  ></PopupMenu>
75
116
  </div>
76
117
  );
@@ -88,20 +129,22 @@ export const PopupMenu = ({
88
129
  handleClosed,
89
130
  noUpdateValue,
90
131
  refOpenMenu,
132
+ align = 'right',
133
+ noTriangleIcon,
91
134
  }: PopupMenuProps) => {
92
135
  const css: any = {
93
136
  '.popup-menu-item': {
94
137
  padding: '0 0 1px 0',
95
138
  display: 'inline-block',
96
139
  position: 'relative',
97
- '.popup-menu-text': {
140
+ '.triangle-icon': {
98
141
  display: 'inline-block',
99
142
  cursor: 'pointer',
100
143
  whiteSpace: 'nowrap',
101
144
  marginRight: '15px',
102
145
  },
103
146
  // cover-box-shadow
104
- '.popup-menu-text::after': {
147
+ '.triangle-icon::after': {
105
148
  content: '""',
106
149
  position: 'absolute',
107
150
  top: '50%',
@@ -125,7 +168,7 @@ export const PopupMenu = ({
125
168
  position: 'absolute',
126
169
  fontSize: 'var(--menu-font-size)',
127
170
  top: 0,
128
- left: '2px',
171
+ width: '100px',
129
172
  color: 'var(--activatable-color-normal)',
130
173
  backgroundColor: 'var(--activatable-bg-color-normal)',
131
174
  zIndex: 'var(--layer-menu)',
@@ -152,6 +195,15 @@ export const PopupMenu = ({
152
195
  cursor: 'pointer',
153
196
  },
154
197
  },
198
+ '.popup-menu-list.left-align': {
199
+ left: '2px',
200
+ },
201
+ '.popup-menu-list.right-align': {
202
+ right: '2px',
203
+ },
204
+ '.popup-menu-list.open': {
205
+ display: 'inline-block',
206
+ },
155
207
  },
156
208
  };
157
209
 
@@ -163,7 +215,13 @@ export const PopupMenu = ({
163
215
  handleOpened && handleOpened();
164
216
  // console.log('=======22', event);
165
217
  isShowing = !isShowing;
166
- ref.$('.popup-menu-list').style.display = isShowing ? 'inline-block' : 'none';
218
+ const listDom = ref.$('.popup-menu-list');
219
+ if (align === 'left') {
220
+ listDom.classList.add('left-align');
221
+ } else {
222
+ listDom.classList.add('right-align');
223
+ }
224
+ listDom.classList.toggle('open', isShowing);
167
225
  ref.$('.popup-menu-list .menu-focus').focus();
168
226
  };
169
227
  if (refOpenMenu) {
@@ -174,7 +232,7 @@ export const PopupMenu = ({
174
232
 
175
233
  // console.log('=======', event);
176
234
  isShowing = false;
177
- ref.$('.popup-menu-list').style.display = 'none';
235
+ ref.$('.popup-menu-list').classList.remove('open');
178
236
  if (event.target) {
179
237
  if (noUpdateValue !== true) {
180
238
  ref.$('.popup-menu-item .popup-menu-text').innerText = event.target.innerText;
@@ -187,7 +245,7 @@ export const PopupMenu = ({
187
245
  };
188
246
  const onBlur = (event: any) => {
189
247
  setTimeout(() => {
190
- ref.$('.popup-menu-list').style.display = 'none';
248
+ ref.$('.popup-menu-list').classList.remove('open');
191
249
  isShowing && handleClosed && handleClosed();
192
250
  isShowing = false;
193
251
  }, 300);
@@ -196,7 +254,9 @@ export const PopupMenu = ({
196
254
  return (
197
255
  <div ref={ref} css={css} onClick={handleClick} title={tips}>
198
256
  <div class='popup-menu-item'>
199
- <span class='popup-menu-text'>{defaultValue || '&nbsp;'}</span>
257
+ <span class={'popup-menu-text' + (noTriangleIcon !== true ? ' triangle-icon' : '')}>
258
+ {defaultValue || '&nbsp;'}
259
+ </span>
200
260
  </div>
201
261
  <div class='popup-menu-bottom'>
202
262
  <div class='popup-menu-list'>
@@ -0,0 +1,80 @@
1
+ import { VNode, CssProps, getWebVersion } from 'lupine.web';
2
+ import { MediaQueryMaxWidth } from '../styles';
3
+ import { MenuBar } from '../components';
4
+
5
+ export interface MenuBarMenuProps {
6
+ text: string;
7
+ url: string;
8
+ }
9
+ export const DesktopFrame = async (
10
+ placeholderClassname: string,
11
+ vnode: VNode<any>,
12
+ title: string,
13
+ footerTitle: string,
14
+ logoUrl: string,
15
+ menu: MenuBarMenuProps[]
16
+ ) => {
17
+ const cssContainer: CssProps = {
18
+ display: 'flex',
19
+ 'flex-direction': 'column',
20
+ width: '100%',
21
+ 'min-height': '100%',
22
+ 'overflow-y': 'auto',
23
+ '.content-block': {
24
+ display: 'flex',
25
+ flex: '1',
26
+ margin: 'auto',
27
+ maxWidth: MediaQueryMaxWidth.DesktopMax,
28
+ width: '100%',
29
+ },
30
+ '.top-footer': {
31
+ paddingTop: '16px',
32
+ },
33
+ '.header-box': {
34
+ display: 'flex',
35
+ 'flex-direction': 'column',
36
+ width: '100%',
37
+ 'min-height': '100%',
38
+ 'overflow-y': 'auto',
39
+ '.content-block': {
40
+ display: 'flex',
41
+ flex: '1',
42
+ margin: 'auto',
43
+ maxWidth: MediaQueryMaxWidth.DesktopMax,
44
+ width: '100%',
45
+ },
46
+ '.top-footer': {
47
+ paddingTop: '16px',
48
+ },
49
+ },
50
+ '.footer-box': {
51
+ display: 'flex',
52
+ padding: '0 32px 16px',
53
+ '.footer-cp': {
54
+ padding: '1px 15px',
55
+ margin: 'auto',
56
+ },
57
+ },
58
+ };
59
+
60
+ return (
61
+ <div css={cssContainer}>
62
+ <div class='header-box'>
63
+ <div class='logo-box'>
64
+ <img class='logo' src={logoUrl} />
65
+ </div>
66
+ <div class='header-title'>
67
+ {title}
68
+ <div class='header-subtitle pt-s'>{'ver: ' + getWebVersion()}</div>
69
+ </div>
70
+ </div>
71
+ <MenuBar items={menu} maxWidthMobileMenu={'800px'} maxWidth={MediaQueryMaxWidth.DesktopMax}></MenuBar>
72
+ <div class={'content-block ' + placeholderClassname}>{vnode}</div>
73
+ <div class='top-footer'>
74
+ <div class='footer-box'>
75
+ <div class='footer-cp'>{footerTitle}</div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ };
@@ -0,0 +1,117 @@
1
+ import { VNode, CssProps, RefProps, HtmlVar } from 'lupine.components';
2
+
3
+ export const HeaderWithBackFrameHeight = '40px';
4
+ export const HeaderWithBackFrameLeft = ({ onClick }: { onClick: (event: Event) => void }) => {
5
+ return (
6
+ <i class='ifc-icon mg-arrow_back_ios_new_outlined header-back-left-icon' onClick={(event) => onClick(event)}></i>
7
+ );
8
+ };
9
+
10
+ export const HeaderWithBackFrameRight = ({ onClick }: { onClick: (event: Event) => void }) => {
11
+ return <i class='ifc-icon ma-close header-back-right-icon' onClick={(event) => onClick(event)}></i>;
12
+ };
13
+
14
+ export const HeaderWithBackFrameEmpty = () => {
15
+ return <div class='header-back-top-empty'></div>;
16
+ };
17
+
18
+ export interface HeaderWithBackFrameHookProps {
19
+ updateTitle?: (title: string) => void;
20
+ updateLeft?: (left: VNode<any>) => void;
21
+ updateRight?: (right: VNode<any>) => void;
22
+ }
23
+ export const HeaderWithBackFrame = ({
24
+ children,
25
+ title,
26
+ onBack,
27
+ left,
28
+ right,
29
+ hook,
30
+ noHeader = false,
31
+ }: {
32
+ children: VNode<any>;
33
+ title: string;
34
+ onBack: (event: Event) => void;
35
+ left?: VNode<any>;
36
+ right?: VNode<any>;
37
+ hook?: HeaderWithBackFrameHookProps;
38
+ noHeader?: boolean;
39
+ }) => {
40
+ left = left || <HeaderWithBackFrameLeft onClick={onBack} />;
41
+ right = right || <HeaderWithBackFrameRight onClick={onBack} />;
42
+ const css: CssProps = {
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ width: '100%',
46
+ height: '100%',
47
+ minHeight: '100%',
48
+ '.header-back-top': {
49
+ display: 'flex',
50
+ flexDirection: 'row',
51
+ width: '100vw',
52
+ padding: '6px 0',
53
+ backgroundColor: 'var(--activatable-bg-color-normal)',
54
+ boxShadow: '0 2px 4px var(--primary-border-color)',
55
+ },
56
+ '.header-back-content': {
57
+ display: 'flex',
58
+ flex: '1',
59
+ flexDirection: 'column',
60
+ overflowY: 'auto',
61
+ scrollbarWidth: 'none',
62
+ position: 'relative',
63
+ '&::-webkit-scrollbar': {
64
+ height: '0',
65
+ },
66
+ },
67
+ '.header-back-title': {
68
+ flex: '1',
69
+ color: 'var(--activatable-text-color-normal)',
70
+ overflow: 'hidden',
71
+ textOverflow: 'ellipsis',
72
+ whiteSpace: 'nowrap',
73
+ },
74
+ '.header-back-left, .header-back-right': {
75
+ // width: HeaderWithBackFrameHeight,
76
+ height: HeaderWithBackFrameHeight,
77
+ display: 'flex',
78
+ alignItems: 'center',
79
+ justifyContent: 'center',
80
+ cursor: 'pointer',
81
+ fontSize: '16px',
82
+ padding: '0 8px',
83
+ },
84
+ '.header-back-left i, .header-back-right i': {
85
+ fontSize: '28px',
86
+ },
87
+
88
+ };
89
+
90
+ if (hook) {
91
+ hook.updateTitle = (title: string) => {
92
+ const titleDom = ref.current?.querySelector('.header-back-title') as HTMLDivElement;
93
+ titleDom && (titleDom.textContent = title);
94
+ };
95
+ hook.updateLeft = (left: VNode<any>) => {
96
+ domLeft.value = left;
97
+ };
98
+ hook.updateRight = (right: VNode<any>) => {
99
+ domRight.value = right;
100
+ };
101
+ }
102
+ const domLeft = HtmlVar(left);
103
+ const domRight = HtmlVar(right);
104
+ const ref: RefProps = {};
105
+ return (
106
+ <div ref={ref} css={css} class='header-back-frame'>
107
+ {!noHeader && (
108
+ <div class='header-back-top'>
109
+ <div class='header-back-left'>{domLeft.node}</div>
110
+ <div class='mobile-title header-back-title'>{title}</div>
111
+ <div class='header-back-right'>{domRight.node}</div>
112
+ </div>
113
+ )}
114
+ <div class='header-back-content'>{children}</div>
115
+ </div>
116
+ );
117
+ };
@@ -0,0 +1,5 @@
1
+ export * from './desktop-frame';
2
+ export * from './header-with-back-frame';
3
+ export * from './responsive-frame';
4
+ export * from './top-frame';
5
+ export * from './slider-frame';
@@ -0,0 +1,85 @@
1
+ import { VNode, CssProps, MediaQueryRange } from 'lupine.components';
2
+ import { MobileFooterMenu, MobileFooterMenuItemProps } from '../components/mobile-components/mobile-footer-menu';
3
+ import { MobileHeaderComponent } from '../components/mobile-components/mobile-header-component';
4
+
5
+ export const ResponsiveFrame = async (
6
+ placeholderClassname: string,
7
+ title: string,
8
+ footerTitle: string,
9
+ logoUrl: string,
10
+ vnode: VNode<any>,
11
+ bottomMenu: MobileFooterMenuItemProps[]
12
+ ) => {
13
+ const cssContainer: CssProps = {
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ width: '100%',
17
+ height: '100%',
18
+ minHeight: '100%',
19
+ '.frame-top-menu': {
20
+ display: 'flex',
21
+ flexDirection: 'column',
22
+ width: '100vw',
23
+ // height: '72px',
24
+ // position: 'fixed',
25
+ // left: 0,
26
+ // top: 0,
27
+ // zIndex: 'var(--layer-menu)',
28
+ backgroundColor: 'var(--activatable-bg-color-normal)',
29
+ },
30
+ '.frame-content': {
31
+ display: 'flex',
32
+ flex: '1',
33
+ flexDirection: 'column',
34
+ // paddingTop: '100px',
35
+ overflowY: 'auto',
36
+ scrollbarWidth: 'none',
37
+ '&::-webkit-scrollbar': {
38
+ height: '0',
39
+ },
40
+ },
41
+ '.content-block': {
42
+ display: 'flex',
43
+ flex: '1',
44
+ flexDirection: 'column',
45
+ overflowY: 'auto',
46
+ scrollbarWidth: 'none',
47
+ },
48
+ '.content-block .padding-block': {
49
+ padding: '0 16px',
50
+ },
51
+ // '.frame-footer': {
52
+ // paddingTop: '57px', // 应该和底部菜单的高度一致
53
+ // },
54
+ [MediaQueryRange.TabletBelow]: {
55
+ // .header-box,
56
+ '.frame-footer .footer-box, .frame-top-menu .desktop-menu-box': {
57
+ display: 'none',
58
+ },
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
+ },
66
+ };
67
+
68
+ return (
69
+ <div css={cssContainer} class='responsive-frame'>
70
+ <div class='frame-top-menu'>
71
+ {/* <DesktopTopMenu title={title}></DesktopTopMenu> */}
72
+ <MobileHeaderComponent></MobileHeaderComponent>
73
+ </div>
74
+ <div class='frame-content'>
75
+ <div class={'content-block ' + placeholderClassname}>{vnode}</div>
76
+ <div class='frame-footer'>
77
+ <div class='footer-box'>
78
+ <div class='footer-cp'>{footerTitle}</div>
79
+ </div>
80
+ <MobileFooterMenu items={bottomMenu}></MobileFooterMenu>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ );
85
+ };
@@ -0,0 +1,99 @@
1
+ import { VNode, CssProps, RefProps } from 'lupine.web';
2
+ import { stopPropagation } from '../lib';
3
+ import { HtmlVar } from '../components';
4
+ import { MediaQueryRange } from '../styles';
5
+
6
+ export type SliderFramePosition = 'desktop-slide-left' | 'desktop-slide-right';
7
+ export type SliderFrameHookProps = {
8
+ load?: (children: VNode<any>) => void;
9
+ close?: (event: Event) => void;
10
+ addClass?: (className: SliderFramePosition) => void;
11
+ };
12
+
13
+ export type SliderFrameProps = {
14
+ defaultContent?: VNode<any> | string;
15
+ direction?: 'right' | 'bottom';
16
+ hook?: SliderFrameHookProps;
17
+ afterClose?: () => void;
18
+ };
19
+ export const SliderFrame = (props: SliderFrameProps) => {
20
+ if (props.hook) {
21
+ props.hook.load = (children) => {
22
+ dom.value = children;
23
+ ref.current?.classList.remove('d-none');
24
+ setTimeout(() => {
25
+ ref.current?.classList.add('show');
26
+ }, 1);
27
+ };
28
+ props.hook.close = (event: Event) => {
29
+ stopPropagation(event);
30
+ ref.current?.classList.remove('show');
31
+ setTimeout(() => {
32
+ ref.current?.classList.add('d-none');
33
+ dom.value = '';
34
+ if (props.afterClose) {
35
+ props.afterClose();
36
+ }
37
+ }, 400);
38
+ };
39
+ props.hook.addClass = (className) => {
40
+ ref.current?.classList.add(className);
41
+ };
42
+ }
43
+ const dom = HtmlVar(<div class='slider-frame-default'>{props.defaultContent || '(No Content)'}</div>);
44
+ const ref: RefProps = {};
45
+ const css: CssProps = {
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ position: 'fixed',
49
+ top: '0',
50
+ left: '0',
51
+ right: '0',
52
+ bottom: '0',
53
+ zIndex: 'var(--layer-slider)',
54
+ transform: props.direction === 'bottom' ? 'translateY(100%)' : 'translateX(100%)',
55
+ transition: 'transform 0.4s ease-in-out',
56
+ backgroundColor: 'var(--primary-bg-color)',
57
+ '&.show': {
58
+ transform: props.direction === 'bottom' ? 'translateY(0)' : 'translateX(0)',
59
+ },
60
+ '& > fragment': {
61
+ height: '100%',
62
+ },
63
+ '&.desktop-slide-left': {
64
+ [MediaQueryRange.TabletAbove]: {
65
+ '.header-back-content': {
66
+ width: '30%',
67
+ },
68
+ },
69
+ },
70
+ '&.desktop-slide-right': {
71
+ [MediaQueryRange.TabletAbove]: {
72
+ top: '56px',
73
+ left: '30%',
74
+ transform: 'translateX(0)',
75
+ '.header-back-top': {
76
+ width: '100%',
77
+ boxShadow: 'unset',
78
+ },
79
+ '.header-back-content': {
80
+ width: '100%',
81
+ },
82
+ '.header-back-title': {
83
+ fontSize: '15px',
84
+ },
85
+ '.header-back-left, .header-back-right': {
86
+ display: 'none',
87
+ },
88
+ '&.d-none': {
89
+ display: 'unset !important',
90
+ },
91
+ },
92
+ },
93
+ };
94
+ return (
95
+ <div ref={ref} css={css} class='slider-frame d-none'>
96
+ {dom.node}
97
+ </div>
98
+ );
99
+ };
@@ -0,0 +1,28 @@
1
+ import { VNode, CssProps, getWebVersion } from 'lupine.web';
2
+
3
+ export const TopFrame = async (placeholderClassname: string, vnode: VNode<any>) => {
4
+ const cssContainer: CssProps = {
5
+ display: 'flex',
6
+ flexDirection: 'column',
7
+ width: '100%',
8
+ height: '100%',
9
+ position: 'relative',
10
+ '.top-frame-box': {
11
+ display: 'flex',
12
+ flex: '1',
13
+ flexDirection: 'column',
14
+ height: '100%',
15
+ // trick: to put two padding-top properties
16
+ 'padding-top ': 'constant(safe-area-inset-top)',
17
+ 'padding-top': 'env(safe-area-inset-top)',
18
+ },
19
+ };
20
+
21
+ console.log('Web version: ', getWebVersion());
22
+ return (
23
+ <div css={cssContainer}>
24
+ {/* Can't put css on this placeholder node! */}
25
+ <div class={'top-frame-box ' + placeholderClassname}>{vnode}</div>
26
+ </div>
27
+ );
28
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from 'lupine.web';
2
2
  export * from './lib';
3
- export * from './components';
4
3
  export * from './styles';
4
+ export * from './components';
5
+ export * from './frames';
@@ -9,7 +9,7 @@ export const darkThemes: ThemeProps = {
9
9
  '--scrollbar-thumb-bg': '#373636',
10
10
  '--scrollbar-active-thumb-bg': '#5b5b5b',
11
11
 
12
- '--primary-color': '#e5e5e5',
12
+ '--primary-color': '#d8d8d8',
13
13
  '--primary-color-disabled': '#7d7d7d',
14
14
  '--primary-bg-color': '#000000',
15
15
  '--primary-border-color': '#aeaeae',
@@ -62,7 +62,10 @@ export const darkThemes: ThemeProps = {
62
62
 
63
63
  '--cover-mask-bg-color': '#878a9460',
64
64
  '--cover-bg-color': '#202020',
65
+ // for dropdown-menu
65
66
  '--cover-box-shadow': '1px 1px 4px #c6c6c6',
67
+ // for big block, deeper shadow arround, good for dark theme
68
+ '--cover-box-shadow-around': '#ffffff 0 0 6px 1px',
66
69
 
67
70
  '--input-color': '#bdbdbd',
68
71
  '--input-bg-color': '#000000',
@@ -79,6 +82,14 @@ export const darkThemes: ThemeProps = {
79
82
  '--header-color': '#000080',
80
83
  '--header-bg-color': '#000000',
81
84
 
85
+ // for setting-section
86
+ '--ss-group-color': 'var(--primary-color)',
87
+ '--ss-group-bg-color': '#232323',
88
+ '--ss-row-btn-color': '#eee',
89
+ '--ss-row-btn-bg-color': '#262626',
90
+ '--ss-row-btn-warn-color': 'red',
91
+ '--mobile-header-shadow': '0px -1px 4px 1px #848484',
92
+
82
93
  // '--background-primary': '#353536', // Primary background
83
94
  // '--color-primary': '#e0e0e0', // Primary text color
84
95
  // backgroundPrimary: '', // Primary background
@@ -61,7 +61,10 @@ export const lightThemes: ThemeProps = {
61
61
 
62
62
  '--cover-mask-bg-color': '#00000060',
63
63
  '--cover-bg-color': '#f5f5f5',
64
+ // for dropdown-menu
64
65
  '--cover-box-shadow': '3px 3px 8px #767676',
66
+ // for big block, deeper shadow arround, good for dark theme
67
+ '--cover-box-shadow-around': '#000000 2px 4px 20px 1px',
65
68
 
66
69
  // for input, checkbox, radio box, select
67
70
  '--input-color': '#4e4e4e',
@@ -79,6 +82,14 @@ export const lightThemes: ThemeProps = {
79
82
  '--header-color': '#000080',
80
83
  '--header-bg-color': '#ffffff',
81
84
 
85
+ // for setting-section
86
+ '--ss-group-color': 'var(--primary-color)',
87
+ '--ss-group-bg-color': 'var(--activatable-bg-color-selected)',
88
+ '--ss-row-btn-color': '#eee',
89
+ '--ss-row-btn-bg-color': '#333',
90
+ '--ss-row-btn-warn-color': 'red',
91
+ '--mobile-header-shadow': '0 4px 4px var(--primary-border-color)',
92
+
82
93
  // '--po-background': '#e5e5e5', // Background for surfaces on top of primary background
83
94
  // // backgroundSecondary: '#f5f5f5', // Secondary background
84
95
  // // backgroundOnSecondary: '#e5e5e5', // Background for surfaces on top of secondary background