lupine.components 1.1.29 → 1.1.31
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/component-pool/i-editor/i-editor-demo.tsx +1 -1
- package/src/component-pool/p-editor/p-editor-demo.tsx +1 -1
- package/src/component-pool/range/gauge.tsx +2 -2
- package/src/components/action-sheet-date.tsx +133 -20
- package/src/components/action-sheet-demo.tsx +37 -0
- package/src/components/action-sheet.tsx +63 -0
- package/src/components/html-var.tsx +18 -4
- package/src/components/index.ts +6 -0
- package/src/components/mobile-components/mobile-header-component.tsx +1 -1
- package/src/components/mobile-components/mobile-header-title-icon.tsx +1 -0
- package/src/components/mobile-components/mobile-side-menu.tsx +21 -5
- package/src/components/mobile-components/mobile-top-sys-icon.tsx +1 -1
- package/src/components/toggle-base.tsx +7 -2
- package/src/demo/mock/side-menu-mock.tsx +1 -1
- package/src/frames/index.ts +1 -1
- package/src/frames/responsive-frame.tsx +35 -6
- package/src/frames/slider-frame.tsx +9 -3
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import { CssProps, RefProps } from 'lupine.web';
|
|
|
2
2
|
import { DemoStory } from '../../demo/demo-types';
|
|
3
3
|
import { IEditor } from './i-editor';
|
|
4
4
|
|
|
5
|
-
const IEditorDemoPage = () => {
|
|
5
|
+
export const IEditorDemoPage = () => {
|
|
6
6
|
let editor: IEditor | undefined;
|
|
7
7
|
|
|
8
8
|
const ref: RefProps = {
|
|
@@ -3,7 +3,7 @@ import { DemoStory } from '../../demo/demo-types';
|
|
|
3
3
|
import { PEditor } from './p-editor';
|
|
4
4
|
import { PEditorOptions } from './p-editor-types';
|
|
5
5
|
|
|
6
|
-
const PEditorDemoPage = (props: { args: PEditorOptions }) => {
|
|
6
|
+
export const PEditorDemoPage = (props: { args: PEditorOptions }) => {
|
|
7
7
|
let editor: PEditor | undefined;
|
|
8
8
|
|
|
9
9
|
const ref: RefProps = {
|
|
@@ -8,8 +8,8 @@ export type GaugeHighlightRange = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export type GaugeHookProps = {
|
|
11
|
-
setValue
|
|
12
|
-
getValue
|
|
11
|
+
setValue?: (val: number | [number, number]) => void;
|
|
12
|
+
getValue?: () => number | [number, number];
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export type GaugeProps = {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { CssProps, RefProps } from 'lupine.web';
|
|
1
|
+
import { CssProps, RefProps, VNode } from 'lupine.web';
|
|
2
|
+
import { HtmlVar } from './html-var';
|
|
2
3
|
import { ActionSheet, ActionSheetCloseProps, ActionSheetCloseReasonProps } from './action-sheet';
|
|
3
4
|
|
|
4
5
|
export type ActionSheetDatePickerOrder = 'YMD' | 'DMY';
|
|
6
|
+
export type ActionSheetDatePickerFormat = 'YMD' | 'MD' | 'D';
|
|
5
7
|
|
|
6
8
|
export type ActionSheetDatePickerProps = {
|
|
7
|
-
/** Initial date value. Format
|
|
9
|
+
/** Initial date value. Format depends on format prop, defaults to "YYYY-MM-DD" */
|
|
8
10
|
value?: string;
|
|
9
11
|
title?: string;
|
|
12
|
+
/** Format of the picker. Default: 'YMD' */
|
|
13
|
+
format?: ActionSheetDatePickerFormat;
|
|
10
14
|
/** Column order. Default: 'YMD' (Year | Month | Day) */
|
|
11
15
|
order?: ActionSheetDatePickerOrder;
|
|
12
16
|
/** Month labels. Defaults to English month names. Length determines month count. */
|
|
@@ -35,6 +39,7 @@ const PAD = Math.floor(VISIBLE / 2); // 2 rows of padding above/below
|
|
|
35
39
|
export const ActionSheetDatePicker = async ({
|
|
36
40
|
value = '',
|
|
37
41
|
title = '',
|
|
42
|
+
format = 'YMD',
|
|
38
43
|
order = 'YMD',
|
|
39
44
|
months = DEFAULT_MONTHS,
|
|
40
45
|
yearRange = 100,
|
|
@@ -44,7 +49,21 @@ export const ActionSheetDatePicker = async ({
|
|
|
44
49
|
}: ActionSheetDatePickerProps): Promise<string | undefined> => {
|
|
45
50
|
// Parse initial date
|
|
46
51
|
const today = new Date();
|
|
47
|
-
|
|
52
|
+
|
|
53
|
+
let parts: string[] = [];
|
|
54
|
+
if (format === 'MD') {
|
|
55
|
+
parts = value.match(/^(\d{2})-(\d{2})$/) ?? [];
|
|
56
|
+
parts.unshift(''); // Shift empty year so indices [1]/[2] match month/day fallback logic below
|
|
57
|
+
parts.unshift(''); // Padding to push match groups to [2][3]
|
|
58
|
+
} else if (format === 'D') {
|
|
59
|
+
parts = value.match(/^(\d{2})$/) ?? [];
|
|
60
|
+
parts.unshift('');
|
|
61
|
+
parts.unshift('');
|
|
62
|
+
parts.unshift(''); // Push match group to [3]
|
|
63
|
+
} else {
|
|
64
|
+
parts = value.match(/^(\d{4})-(\d{2})-(\d{2})$/) ?? [];
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
const initialYear = parts[1] ? parseInt(parts[1], 10) : today.getFullYear();
|
|
49
68
|
const initialMonth = parts[2] ? parseInt(parts[2], 10) : today.getMonth() + 1;
|
|
50
69
|
const initialDay = parts[3] ? parseInt(parts[3], 10) : today.getDate();
|
|
@@ -60,15 +79,27 @@ export const ActionSheetDatePicker = async ({
|
|
|
60
79
|
const monthRef: RefProps = {};
|
|
61
80
|
const dayRef: RefProps = {};
|
|
62
81
|
|
|
63
|
-
const getIdx = (colRef: RefProps) =>
|
|
82
|
+
const getIdx = (colRef: RefProps) => {
|
|
83
|
+
if (!colRef.current) return 0;
|
|
84
|
+
return Math.round((colRef.current as HTMLElement).scrollTop / ITEM_H);
|
|
85
|
+
};
|
|
64
86
|
|
|
65
87
|
const onConfirm = () => {
|
|
66
88
|
const year = START_YEAR + getIdx(yearRef);
|
|
67
89
|
const month = 1 + getIdx(monthRef);
|
|
68
90
|
const day = 1 + getIdx(dayRef);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
91
|
+
|
|
92
|
+
let result = '';
|
|
93
|
+
if (format === 'D') {
|
|
94
|
+
result = String(day).padStart(2, '0');
|
|
95
|
+
} else if (format === 'MD') {
|
|
96
|
+
result = [String(month).padStart(2, '0'), String(day).padStart(2, '0')].join('-');
|
|
97
|
+
} else {
|
|
98
|
+
result = [String(year).padStart(4, '0'), String(month).padStart(2, '0'), String(day).padStart(2, '0')].join(
|
|
99
|
+
'-'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
resolve(result);
|
|
73
104
|
handleClose('confirm');
|
|
74
105
|
};
|
|
@@ -80,7 +111,7 @@ export const ActionSheetDatePicker = async ({
|
|
|
80
111
|
* Build a scrollable column.
|
|
81
112
|
* Carousel pattern: scroll to item's offsetTop on load; read scrollTop on confirm.
|
|
82
113
|
*/
|
|
83
|
-
const buildColumn = (labels: string[], initialIdx: number, colRef: RefProps) => {
|
|
114
|
+
const buildColumn = (labels: string[], initialIdx: number, colRef: RefProps, onScroll?: () => void) => {
|
|
84
115
|
colRef.onLoad = async () => {
|
|
85
116
|
const el = colRef.current as HTMLElement;
|
|
86
117
|
const items = el.querySelectorAll('.dp-item');
|
|
@@ -91,7 +122,7 @@ export const ActionSheetDatePicker = async ({
|
|
|
91
122
|
};
|
|
92
123
|
|
|
93
124
|
return (
|
|
94
|
-
<div class='col' ref={colRef}>
|
|
125
|
+
<div class='col' ref={colRef} onScroll={onScroll}>
|
|
95
126
|
{Array.from({ length: PAD }, (_, i) => (
|
|
96
127
|
<div class='dp-pad' key={`t${i}`} />
|
|
97
128
|
))}
|
|
@@ -107,18 +138,104 @@ export const ActionSheetDatePicker = async ({
|
|
|
107
138
|
);
|
|
108
139
|
};
|
|
109
140
|
|
|
141
|
+
const buildDynamicColumn = (labelsVar: HtmlVar, initialIdx: number, colRef: RefProps) => {
|
|
142
|
+
colRef.onLoad = async () => {
|
|
143
|
+
const el = colRef.current as HTMLElement;
|
|
144
|
+
const items = el.querySelectorAll('.dp-item');
|
|
145
|
+
if (items[initialIdx]) {
|
|
146
|
+
el.scrollTop = (items[initialIdx] as HTMLElement).offsetTop - PAD * ITEM_H;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div class='col' ref={colRef}>
|
|
152
|
+
{labelsVar.node}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const generateDaysDOM = (maxDays: number) => {
|
|
158
|
+
const labels = Array.from({ length: maxDays }, (_, i) => String(i + 1).padStart(2, '0'));
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
{Array.from({ length: PAD }, (_, i) => (
|
|
162
|
+
<div class='dp-pad' key={`t${i}`} />
|
|
163
|
+
))}
|
|
164
|
+
{labels.map((label: string, i: number) => (
|
|
165
|
+
<div class='dp-item' key={i}>
|
|
166
|
+
{label}
|
|
167
|
+
</div>
|
|
168
|
+
))}
|
|
169
|
+
{Array.from({ length: PAD }, (_, i) => (
|
|
170
|
+
<div class='dp-pad' key={`b${i}`} />
|
|
171
|
+
))}
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const getMaxDays = (year: number, month: number) => {
|
|
177
|
+
if (format === 'D') return 31;
|
|
178
|
+
if (format === 'MD') return new Date(2000, month, 0).getDate(); // Leap year forced max
|
|
179
|
+
return new Date(year, month, 0).getDate();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
let currentMaxDays = 0;
|
|
183
|
+
let scrollTimeout: any;
|
|
184
|
+
const handleDateScroll = () => {
|
|
185
|
+
clearTimeout(scrollTimeout);
|
|
186
|
+
scrollTimeout = setTimeout(() => {
|
|
187
|
+
// Read current selected year and month based on scroll heights
|
|
188
|
+
const curYear = format === 'MD' ? 2000 : START_YEAR + getIdx(yearRef);
|
|
189
|
+
const curMonth = 1 + getIdx(monthRef);
|
|
190
|
+
|
|
191
|
+
// Calculate the maximum allowed days for this selection
|
|
192
|
+
const maxDays = getMaxDays(curYear, curMonth);
|
|
193
|
+
|
|
194
|
+
// If the number of days changed, update the reactive var
|
|
195
|
+
if (currentMaxDays !== maxDays) {
|
|
196
|
+
currentMaxDays = maxDays;
|
|
197
|
+
daysVar.value = generateDaysDOM(maxDays);
|
|
198
|
+
|
|
199
|
+
// If the currently selected day is now out of bounds (e.g. Feb 30th), clamp it down.
|
|
200
|
+
const currentDayIdx = getIdx(dayRef);
|
|
201
|
+
if (currentDayIdx >= maxDays) {
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
if (dayRef.current) {
|
|
204
|
+
const el = dayRef.current as HTMLElement;
|
|
205
|
+
const items = el.querySelectorAll('.dp-item');
|
|
206
|
+
const targetItem = items[maxDays - 1] as HTMLElement;
|
|
207
|
+
if (targetItem) {
|
|
208
|
+
el.scrollTo({ top: targetItem.offsetTop - PAD * ITEM_H, behavior: 'smooth' });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}, 50);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}, 150);
|
|
215
|
+
};
|
|
216
|
+
|
|
110
217
|
const years = Array.from({ length: YEAR_COUNT }, (_, i) => String(START_YEAR + i));
|
|
111
|
-
const days = Array.from({ length: 31 }, (_, i) => String(i + 1).padStart(2, '0'));
|
|
112
218
|
|
|
113
219
|
const initialYearIdx = Math.max(0, Math.min(YEAR_COUNT - 1, initialYear - START_YEAR));
|
|
114
220
|
const initialMonthIdx = Math.max(0, Math.min(months.length - 1, initialMonth - 1));
|
|
115
|
-
|
|
221
|
+
currentMaxDays = getMaxDays(START_YEAR + initialYearIdx, initialMonthIdx + 1);
|
|
116
222
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const
|
|
223
|
+
// Day array is reactive, max based on initial selection constraints
|
|
224
|
+
const daysVar = new HtmlVar(generateDaysDOM(currentMaxDays));
|
|
225
|
+
const initialDayIdx = Math.max(0, Math.min(currentMaxDays - 1, initialDay - 1));
|
|
120
226
|
|
|
121
|
-
const
|
|
227
|
+
const yearCol = buildColumn(years, initialYearIdx, yearRef, handleDateScroll);
|
|
228
|
+
const monthCol = buildColumn(months, initialMonthIdx, monthRef, handleDateScroll);
|
|
229
|
+
const dayCol = buildDynamicColumn(daysVar, initialDayIdx, dayRef);
|
|
230
|
+
|
|
231
|
+
let orderedCols: any[] = [];
|
|
232
|
+
if (format === 'D') {
|
|
233
|
+
orderedCols = [dayCol];
|
|
234
|
+
} else if (format === 'MD') {
|
|
235
|
+
orderedCols = order === 'DMY' ? [dayCol, monthCol] : [monthCol, dayCol];
|
|
236
|
+
} else {
|
|
237
|
+
orderedCols = order === 'DMY' ? [dayCol, monthCol, yearCol] : [yearCol, monthCol, dayCol];
|
|
238
|
+
}
|
|
122
239
|
|
|
123
240
|
const pickerBodyCss: CssProps = {
|
|
124
241
|
borderTop: '1px solid var(--primary-border-color)',
|
|
@@ -162,11 +279,7 @@ export const ActionSheetDatePicker = async ({
|
|
|
162
279
|
|
|
163
280
|
const pickerBody = (
|
|
164
281
|
<div css={pickerBodyCss}>
|
|
165
|
-
<div class='picker-body'>
|
|
166
|
-
{orderedCols[0]}
|
|
167
|
-
{orderedCols[1]}
|
|
168
|
-
{orderedCols[2]}
|
|
169
|
-
</div>
|
|
282
|
+
<div class='picker-body'>{orderedCols}</div>
|
|
170
283
|
</div>
|
|
171
284
|
);
|
|
172
285
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ActionSheetMessage,
|
|
6
6
|
ActionSheetInput,
|
|
7
7
|
ActionSheetSelectOptionsProps,
|
|
8
|
+
ActionSheetSelectWrapPromise,
|
|
8
9
|
} from './action-sheet';
|
|
9
10
|
import { ActionSheetTimePicker } from './action-sheet-time';
|
|
10
11
|
import { ActionSheetDatePicker } from './action-sheet-date';
|
|
@@ -84,6 +85,18 @@ export const actionSheetDemo: DemoStory<any> = {
|
|
|
84
85
|
});
|
|
85
86
|
}}
|
|
86
87
|
/>
|
|
88
|
+
<Button
|
|
89
|
+
text='Show Action Sheet Wrap Select'
|
|
90
|
+
size={ButtonSize.Medium}
|
|
91
|
+
onClick={async () => {
|
|
92
|
+
const days = Array.from({ length: 31 }, (_, i) => String(i + 1));
|
|
93
|
+
const result = await ActionSheetSelectWrapPromise({
|
|
94
|
+
title: 'Select a Day',
|
|
95
|
+
options: days,
|
|
96
|
+
});
|
|
97
|
+
if (result >= 0) console.log('Wrap Select picked:', days[result]);
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
87
100
|
<Button
|
|
88
101
|
text='Show Time Picker (HH:mm)'
|
|
89
102
|
size={ButtonSize.Medium}
|
|
@@ -132,6 +145,30 @@ export const actionSheetDemo: DemoStory<any> = {
|
|
|
132
145
|
if (result) console.log('Date selected (DMY):', result);
|
|
133
146
|
}}
|
|
134
147
|
/>
|
|
148
|
+
<Button
|
|
149
|
+
text='Show Date Picker (Month-Day)'
|
|
150
|
+
size={ButtonSize.Medium}
|
|
151
|
+
onClick={async () => {
|
|
152
|
+
const result = await ActionSheetDatePicker({
|
|
153
|
+
title: 'Pick Month and Day',
|
|
154
|
+
value: '02-24',
|
|
155
|
+
format: 'MD',
|
|
156
|
+
});
|
|
157
|
+
if (result) console.log('Date selected (MD):', result);
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
<Button
|
|
161
|
+
text='Show Date Picker (Day Only)'
|
|
162
|
+
size={ButtonSize.Medium}
|
|
163
|
+
onClick={async () => {
|
|
164
|
+
const result = await ActionSheetDatePicker({
|
|
165
|
+
title: 'Pick a Day',
|
|
166
|
+
value: '24',
|
|
167
|
+
format: 'D',
|
|
168
|
+
});
|
|
169
|
+
if (result) console.log('Date selected (D):', result);
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
135
172
|
<Button
|
|
136
173
|
text='Show Color Picker'
|
|
137
174
|
size={ButtonSize.Medium}
|
|
@@ -141,6 +141,29 @@ export class ActionSheet {
|
|
|
141
141
|
marginTop: 'unset',
|
|
142
142
|
maxWidth: 'unset',
|
|
143
143
|
},
|
|
144
|
+
'.act-sheet-wrap-container': {
|
|
145
|
+
display: 'flex',
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
flexWrap: 'wrap',
|
|
148
|
+
alignItems: 'center',
|
|
149
|
+
justifyContent: 'center',
|
|
150
|
+
padding: '16px',
|
|
151
|
+
},
|
|
152
|
+
'.act-sheet-wrap-item': {
|
|
153
|
+
cursor: 'pointer',
|
|
154
|
+
padding: '8px',
|
|
155
|
+
borderRadius: '8px',
|
|
156
|
+
border: '1px solid var(--primary-border-color)',
|
|
157
|
+
margin: '4px',
|
|
158
|
+
width: 'calc(25% - 8px)',
|
|
159
|
+
minWidth: '60px',
|
|
160
|
+
textAlign: 'center',
|
|
161
|
+
transition: 'all 0.3s ease',
|
|
162
|
+
},
|
|
163
|
+
'.act-sheet-wrap-item:hover': {
|
|
164
|
+
fontWeight: 'bold',
|
|
165
|
+
backgroundColor: 'var(--primary-bg-color-hover, rgba(0,0,0,0.04))',
|
|
166
|
+
},
|
|
144
167
|
},
|
|
145
168
|
};
|
|
146
169
|
const component = (
|
|
@@ -448,6 +471,46 @@ export const ActionSheetSelectPromise = async ({
|
|
|
448
471
|
});
|
|
449
472
|
};
|
|
450
473
|
|
|
474
|
+
export const ActionSheetSelectWrapPromise = async ({
|
|
475
|
+
title,
|
|
476
|
+
contentMaxWidth,
|
|
477
|
+
contentMaxHeight,
|
|
478
|
+
options = ActionSheetSelectOptionsProps.Ok,
|
|
479
|
+
closeWhenClickOutside = true,
|
|
480
|
+
cancelButtonText = 'Cancel',
|
|
481
|
+
zIndex,
|
|
482
|
+
}: ActionSheetSelectPromiseProps): Promise<number> => {
|
|
483
|
+
return new Promise(async (resolve, reject) => {
|
|
484
|
+
const handleClicked = async (index: number, close: ActionSheetCloseProps) => {
|
|
485
|
+
resolve(index);
|
|
486
|
+
close('select');
|
|
487
|
+
};
|
|
488
|
+
const closeEvent = (reason?: ActionSheetCloseReasonProps) => {
|
|
489
|
+
if (reason !== 'select') {
|
|
490
|
+
resolve(-1);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const handleClose = await ActionSheet.show({
|
|
494
|
+
title,
|
|
495
|
+
children: (
|
|
496
|
+
<div class='act-sheet-wrap-container'>
|
|
497
|
+
{options.map((option, index) => (
|
|
498
|
+
<div class='act-sheet-wrap-item' key={index} onClick={() => handleClicked(index, handleClose)}>
|
|
499
|
+
{option}
|
|
500
|
+
</div>
|
|
501
|
+
))}
|
|
502
|
+
</div>
|
|
503
|
+
),
|
|
504
|
+
contentMaxWidth,
|
|
505
|
+
contentMaxHeight,
|
|
506
|
+
cancelButtonText,
|
|
507
|
+
closeEvent,
|
|
508
|
+
closeWhenClickOutside,
|
|
509
|
+
zIndex,
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
|
|
451
514
|
export type ActionSheetMultiSelectPromiseProps = {
|
|
452
515
|
title: string | VNode<any>;
|
|
453
516
|
contentMaxWidth?: string;
|
|
@@ -8,6 +8,7 @@ export class HtmlVar implements HtmlVarResult {
|
|
|
8
8
|
private _ref: RefProps;
|
|
9
9
|
private resolve!: () => void;
|
|
10
10
|
private promise: Promise<void>;
|
|
11
|
+
private updatePromise: Promise<void> | null = null;
|
|
11
12
|
|
|
12
13
|
constructor(initial?: HtmlVarValueProps) {
|
|
13
14
|
this.promise = new Promise<void>((res) => {
|
|
@@ -28,10 +29,23 @@ export class HtmlVar implements HtmlVarResult {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
private async update(): Promise<void> {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (this.updatePromise) {
|
|
33
|
+
return this.updatePromise;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.updatePromise = (async () => {
|
|
37
|
+
while (this._dirty) {
|
|
38
|
+
this._dirty = false;
|
|
39
|
+
const v = typeof this._value === 'function' ? await this._value() : this._value;
|
|
40
|
+
await this._ref.mountInnerComponent!(v);
|
|
41
|
+
if (!this._dirty) {
|
|
42
|
+
this._value = '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
|
|
47
|
+
await this.updatePromise;
|
|
48
|
+
this.updatePromise = null;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
// need to wait before use ref.current
|
package/src/components/index.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
export * from './action-sheet-color';
|
|
2
|
+
export * from './action-sheet-date';
|
|
3
|
+
export * from './action-sheet-time';
|
|
1
4
|
export * from './action-sheet';
|
|
2
5
|
export * from './button-push-animation';
|
|
3
6
|
export * from './button';
|
|
7
|
+
export * from './desktop-footer';
|
|
8
|
+
export * from './desktop-header';
|
|
4
9
|
export * from './drag-refresh';
|
|
5
10
|
export * from './editable-label';
|
|
6
11
|
export * from './float-window';
|
|
7
12
|
export * from './grid';
|
|
8
13
|
export * from './html-load';
|
|
9
14
|
export * from './html-var';
|
|
15
|
+
export * from './input-number';
|
|
10
16
|
export * from './input-with-title';
|
|
11
17
|
export * from './link-item';
|
|
12
18
|
export * from './link-list';
|
|
@@ -82,7 +82,7 @@ export const MobileHeaderComponent = (props: any) => {
|
|
|
82
82
|
width: '100%',
|
|
83
83
|
height: 'auto',
|
|
84
84
|
// padding: '2px 0',
|
|
85
|
-
// boxShadow: 'var(--mobile-header-shadow)', //
|
|
85
|
+
// boxShadow: 'var(--mobile-header-shadow)', // if 2px, then there is a shadow (line) on the top
|
|
86
86
|
'& > *': {
|
|
87
87
|
height: '100%',
|
|
88
88
|
},
|
|
@@ -59,6 +59,7 @@ export const MobileHeaderTitleIcon = ({
|
|
|
59
59
|
color: color || 'var(--primary-color)',
|
|
60
60
|
background: background || 'var(--activatable-bg-color-normal)',
|
|
61
61
|
boxShadow: noShadow ? 'unset' : 'var(--mobile-header-shadow)',
|
|
62
|
+
position: 'relative',
|
|
62
63
|
zIndex: 'var(--layer-inside)', // bring boxShadow to front
|
|
63
64
|
'.mhti-title': {
|
|
64
65
|
display: 'flex',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { backActionHelper, CssProps, RefProps, VNode } from 'lupine.components';
|
|
1
|
+
import { backActionHelper, CssProps, RefProps, VNode, MediaQueryRange } from 'lupine.components';
|
|
2
2
|
|
|
3
3
|
export class MobileSideMenuHelper {
|
|
4
4
|
static show() {
|
|
5
5
|
const ref = document.querySelector('.mobile-side-menu-mask') as HTMLDivElement;
|
|
6
|
-
if (!ref) return;
|
|
6
|
+
if (!ref || ref.clientWidth > 100) return;
|
|
7
7
|
|
|
8
8
|
ref.classList.add('show');
|
|
9
9
|
|
|
@@ -170,7 +170,7 @@ export class MobileSideMenuHelper {
|
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
export const MobileSideMenu = (props: { children: VNode<any
|
|
173
|
+
export const MobileSideMenu = (props: { children: VNode<any>; autoExtend?: boolean }) => {
|
|
174
174
|
const css: CssProps = {
|
|
175
175
|
'.mobile-side-menu-mask': {
|
|
176
176
|
display: 'none',
|
|
@@ -206,10 +206,26 @@ export const MobileSideMenu = (props: { children: VNode<any> }) => {
|
|
|
206
206
|
overflowY: 'auto',
|
|
207
207
|
transform: 'translateX(-100%)',
|
|
208
208
|
boxShadow: 'var(--cover-box-shadow)',
|
|
209
|
-
// trick: to put two padding-top properties
|
|
210
209
|
'padding-top ': 'constant(safe-area-inset-top)',
|
|
211
210
|
'padding-top': 'env(safe-area-inset-top)',
|
|
212
211
|
},
|
|
212
|
+
[MediaQueryRange.TabletAbove]: {
|
|
213
|
+
'&.auto-extend .mobile-side-menu-mask': {
|
|
214
|
+
display: 'flex !important',
|
|
215
|
+
width: 'var(--auto-sidemenu-left-offset, 260px)',
|
|
216
|
+
right: 'auto',
|
|
217
|
+
backgroundColor: 'transparent !important',
|
|
218
|
+
pointerEvents: 'none',
|
|
219
|
+
},
|
|
220
|
+
'&.auto-extend .mobile-side-menu': {
|
|
221
|
+
transform: 'translateX(0) !important',
|
|
222
|
+
width: '100%',
|
|
223
|
+
maxWidth: 'unset',
|
|
224
|
+
boxShadow: 'none',
|
|
225
|
+
borderRight: '1px solid var(--primary-border-color)',
|
|
226
|
+
pointerEvents: 'auto',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
213
229
|
};
|
|
214
230
|
|
|
215
231
|
const onClickContainer = (event: Event) => {
|
|
@@ -226,7 +242,7 @@ export const MobileSideMenu = (props: { children: VNode<any> }) => {
|
|
|
226
242
|
},
|
|
227
243
|
};
|
|
228
244
|
return (
|
|
229
|
-
<div css={css} ref={ref}>
|
|
245
|
+
<div css={css} ref={ref} class={['mobile-side-menu-top', props.autoExtend ? 'auto-extend' : ''].join(' ').trim()}>
|
|
230
246
|
{/* <SliderFrame hook={props.sliderFrameHook} /> */}
|
|
231
247
|
<div class='mobile-side-menu-mask' onClick={onClickContainer}>
|
|
232
248
|
<div class='mobile-side-menu'>{props.children}</div>
|
|
@@ -19,7 +19,7 @@ export const MobileTopSysIcon = () => {
|
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
return (
|
|
22
|
-
<div css={css} onClick={() => MobileSideMenuHelper.show()}>
|
|
22
|
+
<div css={css} onClick={() => MobileSideMenuHelper.show()} class='mobile-top-sys-icon'>
|
|
23
23
|
<i class='ifc-icon bs-list'></i>
|
|
24
24
|
</div>
|
|
25
25
|
);
|
|
@@ -23,6 +23,7 @@ export type ToggleBaseProps = {
|
|
|
23
23
|
hook?: ToggleBaseHookProps;
|
|
24
24
|
noToggle?: boolean; // if true, it will be like a button
|
|
25
25
|
children: VNode<any>;
|
|
26
|
+
className?: string;
|
|
26
27
|
};
|
|
27
28
|
export const ToggleBase = (props: ToggleBaseProps) => {
|
|
28
29
|
const applyToggle = (checked: boolean, disabled: boolean) => {
|
|
@@ -70,6 +71,7 @@ export const ToggleBase = (props: ToggleBaseProps) => {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const css: CssProps = {
|
|
74
|
+
display: 'inline-block',
|
|
73
75
|
'.toggle-base-box, .toggle-base-container': {
|
|
74
76
|
position: 'relative',
|
|
75
77
|
width: `100%`,
|
|
@@ -91,7 +93,7 @@ export const ToggleBase = (props: ToggleBaseProps) => {
|
|
|
91
93
|
width: `${typeof props.size.w === 'number' ? props.size.w + 'px' : props.size.w}`,
|
|
92
94
|
height: `${typeof props.size.h === 'number' ? props.size.h + 'px' : props.size.h}`,
|
|
93
95
|
}}
|
|
94
|
-
class='toggle-base-component'
|
|
96
|
+
class={['toggle-base-component', props.className].join(' ').trim()}
|
|
95
97
|
>
|
|
96
98
|
<label class='toggle-base-box'>
|
|
97
99
|
<div class='toggle-base-container'>{props.children}</div>
|
|
@@ -192,6 +194,7 @@ export type TogglePlayButtonProps = {
|
|
|
192
194
|
textColor?: string;
|
|
193
195
|
backgroundColor?: string;
|
|
194
196
|
noWave?: boolean;
|
|
197
|
+
className?: string;
|
|
195
198
|
};
|
|
196
199
|
export const TogglePlayButton = (props: TogglePlayButtonProps) => {
|
|
197
200
|
const css: CssProps = {
|
|
@@ -258,6 +261,7 @@ export type ToggleButtonProps = {
|
|
|
258
261
|
checked?: boolean;
|
|
259
262
|
onClick?: (checked: boolean) => void;
|
|
260
263
|
hook?: ToggleBaseHookProps;
|
|
264
|
+
className?: string;
|
|
261
265
|
};
|
|
262
266
|
export const ToggleButton = (props: ToggleButtonProps) => {
|
|
263
267
|
const css: CssProps = {
|
|
@@ -350,6 +354,7 @@ export type ToggleIconProps = {
|
|
|
350
354
|
onClick?: (checked: boolean) => void;
|
|
351
355
|
hook?: ToggleBaseHookProps;
|
|
352
356
|
noToggle?: boolean; // if true, it will be like a button
|
|
357
|
+
className?: string;
|
|
353
358
|
};
|
|
354
359
|
export const ToggleIcon = (props: ToggleIconProps) => {
|
|
355
360
|
const css: CssProps = {
|
|
@@ -421,7 +426,7 @@ export const ToggleIcon = (props: ToggleIconProps) => {
|
|
|
421
426
|
};
|
|
422
427
|
bindGlobalStyle('toggle-icon-component', css);
|
|
423
428
|
const iconStyle = {
|
|
424
|
-
fontSize: typeof props.size.h === 'number' ? `${Number((props.size.h * 0.
|
|
429
|
+
fontSize: typeof props.size.h === 'number' ? `${Number((props.size.h * 0.75).toFixed(2))}px` : '1rem',
|
|
425
430
|
padding: props.size.padding || '4px 12px',
|
|
426
431
|
};
|
|
427
432
|
|
package/src/frames/index.ts
CHANGED
|
@@ -15,10 +15,12 @@ export interface ResponsiveFrameProps {
|
|
|
15
15
|
mainContent: VNode<any>;
|
|
16
16
|
desktopHeaderTitle: string;
|
|
17
17
|
desktopFooterTitle?: string;
|
|
18
|
-
desktopTopMenu
|
|
19
|
-
mobileBottomMenu
|
|
18
|
+
desktopTopMenu?: IconMenuItemProps[];
|
|
19
|
+
mobileBottomMenu?: IconMenuItemProps[];
|
|
20
20
|
mobileSideMenuContent: VNode<any>;
|
|
21
21
|
sharedContents: VNode<any>;
|
|
22
|
+
maxWidth?: string;
|
|
23
|
+
autoExtendSidemenu?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export const ResponsiveFrame = (props: ResponsiveFrameProps) => {
|
|
24
26
|
const cssContainer: CssProps = {
|
|
@@ -27,6 +29,11 @@ export const ResponsiveFrame = (props: ResponsiveFrameProps) => {
|
|
|
27
29
|
width: '100%',
|
|
28
30
|
height: '100%',
|
|
29
31
|
minHeight: '100%',
|
|
32
|
+
maxWidth: props.maxWidth || '',
|
|
33
|
+
borderRight: props.maxWidth ? '1px solid var(--primary-border-color)' : 'none',
|
|
34
|
+
borderLeft: props.maxWidth ? '1px solid var(--primary-border-color)' : 'none',
|
|
35
|
+
margin: '0 auto',
|
|
36
|
+
overflowX: 'hidden',
|
|
30
37
|
'.frame-top-menu': {
|
|
31
38
|
display: 'flex',
|
|
32
39
|
flexDirection: 'column',
|
|
@@ -56,26 +63,48 @@ export const ResponsiveFrame = (props: ResponsiveFrameProps) => {
|
|
|
56
63
|
'.content-block .padding-block': {
|
|
57
64
|
padding: '0 16px',
|
|
58
65
|
},
|
|
66
|
+
[MediaQueryRange.DesktopAbove]: {
|
|
67
|
+
// for big screens, remove the shadow on the top of mobile header
|
|
68
|
+
'.mobile-header-title-icon-top': {
|
|
69
|
+
boxShadow: 'unset',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
59
72
|
[MediaQueryRange.TabletBelow]: {
|
|
60
73
|
'.frame-footer .d-footer-box, .frame-top-menu .desktop-menu-box': {
|
|
61
74
|
display: 'none',
|
|
62
75
|
},
|
|
63
76
|
},
|
|
77
|
+
[MediaQueryRange.TabletAbove]: {
|
|
78
|
+
transform: 'translateX(0)', // Traps position: fixed children inside this container's dimensions
|
|
79
|
+
'&.auto-extend': {
|
|
80
|
+
'--auto-sidemenu-left-offset': '260px',
|
|
81
|
+
paddingLeft: 'var(--auto-sidemenu-left-offset)',
|
|
82
|
+
boxSizing: 'border-box',
|
|
83
|
+
},
|
|
84
|
+
'.mobile-top-sys-icon': {
|
|
85
|
+
display: 'none',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
64
88
|
};
|
|
65
89
|
|
|
66
90
|
return (
|
|
67
|
-
<div
|
|
91
|
+
<div
|
|
92
|
+
css={cssContainer}
|
|
93
|
+
class={['responsive-frame', props.autoExtendSidemenu ? 'auto-extend' : ''].join(' ').trim()}
|
|
94
|
+
>
|
|
68
95
|
{props.sharedContents}
|
|
69
96
|
<div class='frame-top-menu'>
|
|
70
|
-
|
|
97
|
+
{props.desktopTopMenu && (
|
|
98
|
+
<DesktopHeader title={props.desktopHeaderTitle} items={props.desktopTopMenu}></DesktopHeader>
|
|
99
|
+
)}
|
|
71
100
|
<MobileHeaderComponent></MobileHeaderComponent>
|
|
72
101
|
</div>
|
|
73
102
|
<div class='frame-content'>
|
|
74
|
-
<MobileSideMenu>{props.mobileSideMenuContent}</MobileSideMenu>
|
|
103
|
+
<MobileSideMenu autoExtend={props.autoExtendSidemenu}>{props.mobileSideMenuContent}</MobileSideMenu>
|
|
75
104
|
<div class={'content-block ' + props.placeholderClassname}>{props.mainContent}</div>
|
|
76
105
|
<div class='frame-footer'>
|
|
77
106
|
{props.desktopFooterTitle && <DesktopFooter title={props.desktopFooterTitle}></DesktopFooter>}
|
|
78
|
-
<MobileFooterMenu items={props.mobileBottomMenu}></MobileFooterMenu>
|
|
107
|
+
{props.mobileBottomMenu && <MobileFooterMenu items={props.mobileBottomMenu}></MobileFooterMenu>}
|
|
79
108
|
</div>
|
|
80
109
|
</div>
|
|
81
110
|
</div>
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { VNode, CssProps, HtmlVar, RefProps, stopPropagation, MediaQueryRange } from 'lupine.components';
|
|
16
16
|
|
|
17
|
+
// addClass(SliderFramePosition) is used to show two SliderFrames for big screens,
|
|
18
|
+
// so when the second is showing, it needs to set this on the first one
|
|
17
19
|
export type SliderFramePosition = 'desktop-slide-left' | 'desktop-slide-right';
|
|
18
20
|
export type SliderFrameHookProps = {
|
|
19
21
|
load?: (children: VNode<any>) => void;
|
|
@@ -62,7 +64,7 @@ export const SliderFrame = (props: SliderFrameProps) => {
|
|
|
62
64
|
flexDirection: 'column',
|
|
63
65
|
position: 'fixed',
|
|
64
66
|
top: '0',
|
|
65
|
-
left: '
|
|
67
|
+
left: 'var(--auto-sidemenu-left-offset, 0px)',
|
|
66
68
|
right: '0',
|
|
67
69
|
bottom: '0',
|
|
68
70
|
zIndex: 'var(--layer-slider)',
|
|
@@ -75,8 +77,12 @@ export const SliderFrame = (props: SliderFrameProps) => {
|
|
|
75
77
|
'&.show': {
|
|
76
78
|
transform: props.direction === 'bottom' ? 'translateY(0)' : 'translateX(0)',
|
|
77
79
|
},
|
|
80
|
+
'& > *': {
|
|
81
|
+
'--auto-sidemenu-left-offset': '0px',
|
|
82
|
+
},
|
|
78
83
|
'& > fragment': {
|
|
79
84
|
height: '100%',
|
|
85
|
+
'--auto-sidemenu-left-offset': '0px',
|
|
80
86
|
},
|
|
81
87
|
'&.desktop-slide-left': {
|
|
82
88
|
[MediaQueryRange.TabletAbove]: {
|
|
@@ -87,8 +93,8 @@ export const SliderFrame = (props: SliderFrameProps) => {
|
|
|
87
93
|
},
|
|
88
94
|
'&.desktop-slide-right': {
|
|
89
95
|
[MediaQueryRange.TabletAbove]: {
|
|
90
|
-
top: '59px',
|
|
91
|
-
left: '30%',
|
|
96
|
+
top: '59px', // Just below DesktopHeader
|
|
97
|
+
left: 'calc(max(var(--auto-sidemenu-left-offset, 0px), 30%))',
|
|
92
98
|
transform: 'translateX(0)',
|
|
93
99
|
// notice: here is connected with mobile-header-title-icon.tsx
|
|
94
100
|
'.mobile-header-title-icon-top': {
|