lupine.components 1.1.42 → 1.1.43

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.1.42",
3
+ "version": "1.1.43",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -20,8 +20,9 @@ declare global {
20
20
  export const chartCommonCss: CssProps = {
21
21
  position: 'relative',
22
22
  width: '100%',
23
- height: '100%',
24
- minHeight: '200px', // Default min height
23
+ height: 'auto',
24
+ minHeight: '0', // Default min height
25
+ maxHeight: '100%',
25
26
  display: 'flex',
26
27
  flexDirection: 'column',
27
28
 
@@ -94,7 +94,7 @@ export const YouTubePlayer = (props: YouTubePlayerProps) => {
94
94
  }
95
95
 
96
96
  return (
97
- <div class={className} css={{ position: 'relative', width, height, ...style }}>
97
+ <div class={className} css={{ position: 'relative', width, height, display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', ...style }}>
98
98
  {src ? (
99
99
  <iframe
100
100
  width='100%'
@@ -104,7 +104,7 @@ export const YouTubePlayer = (props: YouTubePlayerProps) => {
104
104
  frameBorder='0'
105
105
  allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
106
106
  allowFullScreen={allowFullScreen}
107
- style={{ border: 'none', display: 'block' }}
107
+ style={{ border: 'none', display: 'block', aspectRatio: '16 / 9', height: 'auto', maxHeight: '100%' }}
108
108
  ></iframe>
109
109
  ) : (
110
110
  <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0f0f0', color: '#999', border: '1px solid #ddd' }}>
@@ -10,6 +10,7 @@ import {
10
10
  import { ActionSheetTimePicker } from './action-sheet-time';
11
11
  import { ActionSheetDatePicker } from './action-sheet-date';
12
12
  import { ActionSheetColorPicker } from './action-sheet-color';
13
+ import { ActionSheetThemePicker } from './action-sheet-theme';
13
14
  import { Button, ButtonSize } from './button';
14
15
 
15
16
  export const actionSheetDemo: DemoStory<any> = {
@@ -180,6 +181,17 @@ export const actionSheetDemo: DemoStory<any> = {
180
181
  if (result) console.log('Color selected:', result);
181
182
  }}
182
183
  />
184
+ <Button
185
+ text='Show Theme Picker'
186
+ size={ButtonSize.Medium}
187
+ onClick={async () => {
188
+ const result = await ActionSheetThemePicker({
189
+ title: 'Pick a theme variable',
190
+ value: 'var(--primary-color)',
191
+ });
192
+ if (result) console.log('Theme variable selected:', result);
193
+ }}
194
+ />
183
195
  </div>
184
196
  );
185
197
  },
@@ -0,0 +1,150 @@
1
+ import { CssProps, getCurrentTheme } from 'lupine.web';
2
+ import { ActionSheet, ActionSheetCloseProps, ActionSheetCloseReasonProps } from './action-sheet';
3
+
4
+ export type ActionSheetThemePickerProps = {
5
+ value?: string;
6
+ title?: string;
7
+ cancelButtonText?: string;
8
+ zIndex?: string;
9
+ };
10
+
11
+ const getThemeVarNameFromValue = (value: string) => {
12
+ const match = String(value || '').match(/var\((--[^),\s]+)/);
13
+ return match ? match[1] : '';
14
+ };
15
+
16
+ const getThemeValue = (themes: any, themeName: string, key: string) => {
17
+ const themeValue = themes?.[themeName]?.[key];
18
+ if (themeValue !== undefined) return String(themeValue);
19
+
20
+ const fallbackThemeName = Object.keys(themes || {})[0];
21
+ const fallbackValue = themes?.[fallbackThemeName]?.[key];
22
+ return fallbackValue !== undefined ? String(fallbackValue) : '';
23
+ };
24
+
25
+ const getThemeItems = () => {
26
+ const { themeName, themes } = getCurrentTheme();
27
+ const keys = new Set<string>();
28
+
29
+ Object.values(themes || {}).forEach((theme: any) => {
30
+ Object.keys(theme || {}).forEach((key) => {
31
+ if (key.startsWith('--')) keys.add(key);
32
+ });
33
+ });
34
+
35
+ return Array.from(keys)
36
+ .sort()
37
+ .map((key) => ({
38
+ key,
39
+ value: getThemeValue(themes, themeName, key),
40
+ }));
41
+ };
42
+
43
+ export const ActionSheetThemePicker = async ({
44
+ value = '',
45
+ title = 'Select Theme Variable',
46
+ cancelButtonText = 'Cancel',
47
+ zIndex,
48
+ }: ActionSheetThemePickerProps): Promise<string | undefined> => {
49
+ return new Promise(async (resolve) => {
50
+ let handleClose: ActionSheetCloseProps;
51
+ const selectedKey = getThemeVarNameFromValue(value);
52
+ const items = getThemeItems();
53
+
54
+ const closeEvent = (reason?: ActionSheetCloseReasonProps) => {
55
+ if (reason !== 'confirm') resolve(undefined);
56
+ };
57
+
58
+ const css: CssProps = {
59
+ borderTop: '1px solid var(--primary-border-color)',
60
+ overflow: 'hidden',
61
+ '.theme-list': {
62
+ maxHeight: '60vh',
63
+ overflowY: 'auto',
64
+ padding: '8px 0',
65
+ },
66
+ '.theme-empty': {
67
+ padding: '24px 16px',
68
+ textAlign: 'center',
69
+ color: 'var(--secondary-color, #888)',
70
+ fontSize: '13px',
71
+ },
72
+ '.theme-item': {
73
+ display: 'grid',
74
+ gridTemplateColumns: '28px minmax(0, 1fr) auto',
75
+ alignItems: 'center',
76
+ gap: '10px',
77
+ padding: '9px 14px',
78
+ cursor: 'pointer',
79
+ borderBottom: '1px solid var(--primary-border-color)',
80
+ color: 'var(--primary-color)',
81
+ '&:hover': {
82
+ backgroundColor: 'var(--activatable-bg-color-hover, rgba(0,0,0,0.06))',
83
+ },
84
+ },
85
+ '.theme-item-selected': {
86
+ backgroundColor: 'var(--activatable-bg-color-selected, rgba(24,144,255,0.12))',
87
+ },
88
+ '.theme-swatch': {
89
+ width: '22px',
90
+ height: '22px',
91
+ borderRadius: '50%',
92
+ border: '1px solid var(--primary-border-color)',
93
+ boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.08)',
94
+ },
95
+ '.theme-key': {
96
+ minWidth: 0,
97
+ overflow: 'hidden',
98
+ textOverflow: 'ellipsis',
99
+ whiteSpace: 'nowrap',
100
+ fontFamily: 'monospace',
101
+ fontSize: '13px',
102
+ },
103
+ '.theme-value': {
104
+ maxWidth: '160px',
105
+ overflow: 'hidden',
106
+ textOverflow: 'ellipsis',
107
+ whiteSpace: 'nowrap',
108
+ color: 'var(--secondary-color, #888)',
109
+ fontFamily: 'monospace',
110
+ fontSize: '12px',
111
+ },
112
+ };
113
+
114
+ const pickerBody = (
115
+ <div css={css}>
116
+ <div class='theme-list'>
117
+ {items.length === 0 && <div class='theme-empty'>No theme variables found.</div>}
118
+ {items.map((item) => {
119
+ const result = `var(${item.key})`;
120
+ return (
121
+ <div
122
+ class={`theme-item ${item.key === selectedKey ? 'theme-item-selected' : ''}`}
123
+ title={`${item.key}: ${item.value}`}
124
+ onClick={() => {
125
+ resolve(result);
126
+ handleClose('confirm');
127
+ }}
128
+ >
129
+ <div class='theme-swatch' style={{ background: result }} />
130
+ <div class='theme-key'>{item.key}</div>
131
+ <div class='theme-value'>{item.value}</div>
132
+ </div>
133
+ );
134
+ })}
135
+ </div>
136
+ </div>
137
+ );
138
+
139
+ handleClose = await ActionSheet.show({
140
+ title,
141
+ children: pickerBody,
142
+ contentMaxHeight: '70vh',
143
+ closeEvent,
144
+ closeWhenClickOutside: true,
145
+ zIndex,
146
+ cancelButtonText,
147
+ buttonsPosition: 'header',
148
+ });
149
+ });
150
+ };
@@ -1,5 +1,6 @@
1
1
  export * from './action-sheet-color';
2
2
  export * from './action-sheet-date';
3
+ export * from './action-sheet-theme';
3
4
  export * from './action-sheet-time';
4
5
  export * from './action-sheet';
5
6
  export * from './button-push-animation';
@@ -100,7 +100,7 @@ export const MenuSidebar = ({
100
100
  '.menu-sidebar-sub-box.open > .menu-sidebar-item::after': {
101
101
  transform: 'rotate(0deg)',
102
102
  },
103
- '&.mobile .menu-sidebar-sub-box > .menu-sidebar-item::after': {
103
+ '&.mobile .menu-sidebar-sub-box.open > .menu-sidebar-item::after': {
104
104
  transform: 'rotate(0deg)',
105
105
  },
106
106
  // '.menu-sidebar-sub-box .menu-sidebar-sub > .menu-sidebar-item': {
@@ -170,15 +170,17 @@ export const MenuSidebar = ({
170
170
  display: 'block',
171
171
  },
172
172
  '&.mobile.open': {
173
- position: 'absolute',
173
+ position: 'fixed',
174
+ inset: 0,
174
175
  width: '100%',
175
176
  height: '100%',
177
+ minHeight: '100dvh',
176
178
  top: 0,
177
179
  left: 0,
178
180
  display: 'flex',
179
181
  flexDirection: 'column',
180
182
  backgroundColor: '#ccccccc2',
181
- zIndex: 'var(--layer-sidebar)',
183
+ zIndex: 'var(--layer-menu-sub)',
182
184
  },
183
185
  '.menu-sidebar-top': {
184
186
  display: 'none',
@@ -195,7 +197,7 @@ export const MenuSidebar = ({
195
197
  marginLeft: 'unset',
196
198
  justifyContent: 'start',
197
199
  },
198
- '.menu-sidebar-top.open .menu-sidebar-sub-box > .menu-sidebar-sub': {
200
+ '.menu-sidebar-top.open .menu-sidebar-sub-box.open > .menu-sidebar-sub': {
199
201
  display: 'flex',
200
202
  position: 'unset',
201
203
  '.menu-sidebar-item': {
@@ -230,7 +232,7 @@ export const MenuSidebar = ({
230
232
  }
231
233
  let ref: RefProps = {};
232
234
  return item.items ? (
233
- <div ref={ref} class={`menu-sidebar-sub-box ${defaultOpenAll ? 'open' : ''}`} onClick={() => onItemToggleClick(ref)}>
235
+ <div ref={ref} class={`menu-sidebar-sub-box ${defaultOpenAll ? 'open' : ''}`} onClick={(event) => onItemToggleClick(event, ref)}>
234
236
  <div class='menu-sidebar-item'>{item.text}</div>
235
237
  {renderItems(item.items, 'menu-sidebar-sub')}
236
238
  </div>
@@ -241,8 +243,10 @@ export const MenuSidebar = ({
241
243
  alt={item.alt || item.text}
242
244
  onClick={(event) => {
243
245
  stopPropagation(event);
244
- // hide menu
245
- onToggleClick(event);
246
+ // hide menu only when the mobile overlay is open
247
+ if (ref.current?.classList.contains('open')) {
248
+ onToggleClick(event);
249
+ }
246
250
  item.js && item.js();
247
251
  }}
248
252
  >
@@ -280,17 +284,23 @@ export const MenuSidebar = ({
280
284
  const topMenu = ref.$('.menu-sidebar-top');
281
285
  topMenu.classList.toggle('open');
282
286
  const isOpen = ref.current.classList.toggle('open');
283
-
287
+
284
288
  if (isOpen) {
289
+ if (ref.current.classList.contains('mobile')) {
290
+ ref.current.querySelectorAll('.menu-sidebar-sub-box').forEach((item: Element) => {
291
+ item.classList.add('open');
292
+ });
293
+ }
285
294
  ref.current.setAttribute('data-back-action', backActionHelper.genBackActionId());
286
295
  } else {
287
296
  ref.current.removeAttribute('data-back-action');
288
297
  }
289
298
  };
290
- const onItemToggleClick = (ref: RefProps) => {
291
- // if (event.target != ref.current && (event.target as any).parentNode != ref.current) {
292
- // return;
293
- // }
299
+ const onItemToggleClick = (event: Event, ref: RefProps) => {
300
+ stopPropagation(event);
301
+ if (event.target != ref.current && (event.target as any).parentNode != ref.current) {
302
+ return;
303
+ }
294
304
  ref.current.classList.toggle('open');
295
305
  };
296
306
 
@@ -10,6 +10,24 @@ export type MessageBoxProps = FloatWindowShowProps & {
10
10
  };
11
11
 
12
12
  export class MessageBox {
13
+ static async showPromise(props: MessageBoxProps): Promise<number> {
14
+ return new Promise(async (resolve) => {
15
+ const handleClicked = (index: number, close: FloatWindowCloseProps) => {
16
+ resolve(index);
17
+ close();
18
+ };
19
+ const closeEvent = () => {
20
+ resolve(-1);
21
+ props.closeEvent?.();
22
+ };
23
+ await MessageBox.show({
24
+ ...props,
25
+ handleClicked,
26
+ closeEvent,
27
+ });
28
+ });
29
+ }
30
+
13
31
  static async show({
14
32
  title,
15
33
  children,
@@ -502,9 +502,9 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
502
502
  onClick={() => {
503
503
  hook.openMenu && hook.openMenu();
504
504
  }}
505
- css={{ '>div': { float: 'right', textAlign: 'left' } }}
505
+ css={{ display: 'flex', flexDirection: 'row' }}
506
506
  >
507
- {props.label}:{' '}
507
+ {props.label ? <div style={{ marginRight: '5px' }}>{`${props.label}:`}</div> : ''}
508
508
  <PopupMenu
509
509
  list={props.list}
510
510
  defaultValue={props.defaultValue}
@@ -535,9 +535,9 @@ export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
535
535
  onClick={() => {
536
536
  hook.openMenu && hook.openMenu();
537
537
  }}
538
- css={{ cursor: 'pointer', '>div': { float: 'right', textAlign: 'left' } }}
538
+ css={{ display: 'flex', flexDirection: 'row', cursor: 'pointer' }}
539
539
  >
540
- {props.label}:{' '}
540
+ {props.label ? <div style={{ marginRight: '5px' }}>{`${props.label}:`}</div> : ''}
541
541
  <PopupMenu
542
542
  list={props.list}
543
543
  defaultValue={props.defaultValue}