lupine.components 1.1.57 → 1.1.59

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.
@@ -1,130 +1,209 @@
1
1
  import { CssProps, RefProps, VNode, bindGlobalStyle } from 'lupine.components';
2
2
 
3
+ export interface SlideTabHookProps {
4
+ moveTo?: (index: number) => void;
5
+ getIndex?: () => number;
6
+ }
7
+
8
+ export interface SlideTabPage {
9
+ id?: string;
10
+ title: string;
11
+ content: VNode<any> | any;
12
+ }
13
+
3
14
  export interface SlideTabProps {
4
- pages: { title: string; content: VNode<any> }[];
15
+ pages: SlideTabPage[];
16
+ hook?: SlideTabHookProps;
17
+ initialIndex?: number;
18
+ onChange?: (index: number, page: SlideTabPage) => void;
19
+ navClass?: string;
20
+ class?: string;
5
21
  }
22
+
6
23
  export const SlideTabComponent = (props: SlideTabProps) => {
7
24
  const css: CssProps = {
8
25
  display: 'flex',
9
26
  flexDirection: 'column',
10
27
  flex: 1,
11
- fontSize: '12px',
12
- borderRadius: '6px',
13
- padding: '0px 8px 4px 8px',
14
- // marginBottom: '8px',
28
+ width: '100%',
15
29
  height: '100%',
16
- '.slide-tab-c-list': {
17
- flex: 1,
18
- borderRadius: '6px',
19
- display: 'flex',
20
- overflowX: 'auto',
21
- width: '100%',
22
- scrollSnapType: 'x mandatory',
23
- gap: '8px',
24
- paddingBottom: '10px',
25
- scrollBehavior: 'smooth',
26
- WebkitOverflowScrolling: 'touch',
27
- },
28
- '.slide-tab-c-slide': {
29
- width: '100%',
30
- overflow: 'hidden',
31
- position: 'relative',
32
- minWidth: '100%',
33
- flexShrink: 0,
34
- scrollSnapAlign: 'start',
35
- height: '100%',
36
- overflowY: 'auto',
37
- },
30
+ overflow: 'hidden',
31
+ position: 'relative',
32
+ boxSizing: 'border-box',
38
33
 
39
34
  '.slide-tab-c-nav': {
40
35
  display: 'flex',
41
36
  flexDirection: 'row',
42
- justifyContent: 'center',
37
+ alignItems: 'center',
43
38
  backgroundColor: 'var(--primary-bg-color)',
44
- position: 'sticky',
45
- top: 0,
46
- zIndex: 1,
39
+ borderBottom: '1px solid var(--secondary-border-color)',
40
+ padding: '10px 16px 8px 16px',
41
+ overflowX: 'auto',
42
+ WebkitOverflowScrolling: 'touch',
43
+ flexShrink: 0,
44
+ zIndex: 10,
45
+ scrollbarWidth: 'none',
46
+ '&::-webkit-scrollbar': { display: 'none' },
47
47
  },
48
48
  '.slide-tab-c-nav-wrap': {
49
49
  display: 'flex',
50
50
  flexDirection: 'row',
51
- justifyContent: 'center',
52
- padding: '2px 4px',
53
- borderRadius: '4px',
54
- backgroundColor: 'var(--secondary-bg-color)',
51
+ alignItems: 'center',
52
+ gap: '8px',
53
+ flexShrink: 0,
54
+ paddingRight: '16px',
55
55
  },
56
56
  '.slide-tab-c-nav-item': {
57
57
  cursor: 'pointer',
58
- padding: '4px 8px',
59
- borderRadius: '4px',
60
- marginRight: '8px',
58
+ padding: '7px 16px',
59
+ borderRadius: '20px',
60
+ fontSize: '13px',
61
+ fontWeight: 600,
62
+ color: 'var(--secondary-color)',
63
+ backgroundColor: 'var(--secondary-bg-color)',
64
+ whiteSpace: 'nowrap',
65
+ transition: 'all 0.18s ease',
66
+ WebkitTapHighlightColor: 'transparent',
67
+ flexShrink: 0,
68
+ userSelect: 'none',
69
+ WebkitUserSelect: 'none',
70
+ '&:active': {
71
+ transform: 'scale(0.96)',
72
+ opacity: 0.85,
73
+ },
61
74
  },
62
75
  '.slide-tab-c-nav-item.active': {
63
76
  backgroundColor: 'var(--primary-accent-color)',
64
- color: 'white',
77
+ color: '#ffffff',
78
+ boxShadow: '0 2px 8px rgba(2, 132, 199, 0.35)',
79
+ },
80
+
81
+ '.slide-tab-c-list': {
82
+ flex: 1,
83
+ display: 'flex',
84
+ flexDirection: 'row',
85
+ overflowX: 'auto',
86
+ overflowY: 'hidden',
87
+ width: '100%',
88
+ height: '100%',
89
+ scrollSnapType: 'x mandatory',
90
+ scrollBehavior: 'smooth',
91
+ WebkitOverflowScrolling: 'touch',
92
+ boxSizing: 'border-box',
93
+ scrollbarWidth: 'none',
94
+ '&::-webkit-scrollbar': { display: 'none' },
95
+ },
96
+ '.slide-tab-c-slide': {
97
+ width: '100%',
98
+ minWidth: '100%',
99
+ maxWidth: '100%',
100
+ flexShrink: 0,
101
+ height: '100%',
102
+ overflowY: 'auto',
103
+ position: 'relative',
104
+ scrollSnapAlign: 'start',
105
+ scrollSnapStop: 'always',
106
+ WebkitOverflowScrolling: 'touch',
107
+ boxSizing: 'border-box',
108
+ scrollbarWidth: 'none',
109
+ '&::-webkit-scrollbar': { display: 'none' },
65
110
  },
66
111
  };
67
112
  bindGlobalStyle('slide-tab-c-box', css);
68
113
 
69
114
  const ref: RefProps = {};
70
- let slideIndex = 0;
115
+ let slideIndex = props.initialIndex || 0;
71
116
  let manualScroll = false;
72
- let scrollEndTimer: NodeJS.Timeout | null = null;
73
- const drawerScroll = () => {
74
- if (manualScroll) {
75
- return;
76
- }
77
- if (scrollEndTimer) {
78
- clearTimeout(scrollEndTimer);
79
- }
80
- scrollEndTimer = setTimeout(() => {
81
- drawerScrollStop();
82
- }, 100);
83
- };
117
+ let scrollEndTimer: any = null;
118
+
84
119
  const resetSlides = (index: number) => {
85
- const dots = ref.$all('.slide-tab-c-nav-item');
86
- for (let i = 0; i < dots.length; i++) {
87
- dots[i].classList.toggle('active', i === index);
120
+ const dots = ref.$all?.('.slide-tab-c-nav-item');
121
+ if (dots) {
122
+ for (let i = 0; i < dots.length; i++) {
123
+ dots[i].classList.toggle('active', i === index);
124
+ }
125
+ dots[index]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
126
+ }
127
+ if (props.onChange && props.pages[index]) {
128
+ props.onChange(index, props.pages[index]);
88
129
  }
89
130
  };
131
+
90
132
  const drawerScrollStop = () => {
91
- const drawer = ref.$('.slide-tab-c-list');
133
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
134
+ if (!drawer) return;
92
135
  const width = drawer.clientWidth;
136
+ if (width <= 0) return;
93
137
  const currentScrollIndex = Math.round(drawer.scrollLeft / width);
94
- slideIndex = currentScrollIndex;
95
- resetSlides(slideIndex);
96
- };
97
- const moveSlide = (slideIndex: number) => {
98
- const drawer = ref.$('.slide-tab-c-list');
99
- const children = ref.$all('.slide-tab-c-slide');
100
- if (!drawer || !children || children.length === 0) {
101
- return;
138
+ if (currentScrollIndex !== slideIndex && currentScrollIndex >= 0 && currentScrollIndex < props.pages.length) {
139
+ slideIndex = currentScrollIndex;
140
+ resetSlides(slideIndex);
102
141
  }
103
- const target = children[slideIndex];
104
- if (!target) {
105
- return;
106
- }
107
- const offsetLeft = target.offsetLeft;
142
+ };
143
+
144
+ const drawerScroll = () => {
145
+ if (manualScroll) return;
146
+ if (scrollEndTimer) clearTimeout(scrollEndTimer);
147
+ scrollEndTimer = setTimeout(() => {
148
+ drawerScrollStop();
149
+ }, 60);
150
+ };
151
+
152
+ const moveSlide = (targetIndex: number) => {
153
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
154
+ if (!drawer) return;
155
+ const width = drawer.clientWidth;
156
+ if (width <= 0) return;
157
+
108
158
  manualScroll = true;
159
+ slideIndex = targetIndex;
160
+ resetSlides(slideIndex);
161
+
109
162
  drawer.scrollTo({
110
- left: offsetLeft,
163
+ left: targetIndex * width,
111
164
  behavior: 'smooth',
112
165
  });
113
166
 
114
- resetSlides(slideIndex);
115
167
  setTimeout(() => {
116
168
  manualScroll = false;
117
- }, 300);
169
+ }, 350);
170
+ };
171
+
172
+ if (props.hook) {
173
+ props.hook.moveTo = moveSlide;
174
+ props.hook.getIndex = () => slideIndex;
175
+ }
176
+
177
+ ref.onLoad = async () => {
178
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
179
+ if (drawer) {
180
+ drawer.addEventListener('scrollend', drawerScrollStop);
181
+ }
182
+ if (slideIndex > 0) {
183
+ setTimeout(() => {
184
+ moveSlide(slideIndex);
185
+ }, 50);
186
+ }
187
+ };
188
+
189
+ ref.onUnload = async () => {
190
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
191
+ if (drawer) {
192
+ drawer.removeEventListener('scrollend', drawerScrollStop);
193
+ }
194
+ if (scrollEndTimer) {
195
+ clearTimeout(scrollEndTimer);
196
+ }
118
197
  };
119
198
 
120
199
  return (
121
- <section class='slide-tab-c-box' ref={ref}>
122
- <div class='slide-tab-c-nav'>
123
- <div class='slide-tab-c-nav-wrap'>
200
+ <section class={`slide-tab-c-box ${props.class || ''}`} ref={ref}>
201
+ <div class={`slide-tab-c-nav no-scrollbar-container ${props.navClass || ''}`}>
202
+ <div class="slide-tab-c-nav-wrap">
124
203
  {props.pages.map((page, index) => (
125
204
  <div
126
- class={`slide-tab-c-nav-item ${index === 0 ? 'active' : ''}`}
127
- onClick={(event) => {
205
+ class={`slide-tab-c-nav-item ${index === slideIndex ? 'active' : ''}`}
206
+ onClick={(event: any) => {
128
207
  event.preventDefault();
129
208
  moveSlide(index);
130
209
  }}
@@ -134,9 +213,9 @@ export const SlideTabComponent = (props: SlideTabProps) => {
134
213
  ))}
135
214
  </div>
136
215
  </div>
137
- <div class='slide-tab-c-list no-scrollbar-container' onScroll={drawerScroll}>
216
+ <div class="slide-tab-c-list no-scrollbar-container" onScroll={drawerScroll}>
138
217
  {props.pages.map((page) => (
139
- <div class='slide-tab-c-slide no-scrollbar-container'>{page.content}</div>
218
+ <div class="slide-tab-c-slide no-scrollbar-container">{page.content}</div>
140
219
  ))}
141
220
  </div>
142
221
  </section>
@@ -54,6 +54,7 @@ import { searchInputDemo } from '../component-pool/search-input';
54
54
  import { hEditorDemo } from '../component-pool/h-editor';
55
55
  import { pagingLinkDemo } from '../components/paging-link-demo';
56
56
  import { slideTabDemo } from '../components/slide-tab-component-demo';
57
+ import { slidePageDemo } from '../components/slide-page-component-demo';
57
58
  import { qrcodeDemo } from '../component-pool/qrcode';
58
59
  import { iEditorDemo } from '../component-pool/i-editor';
59
60
  import { youtubePlayerDemo } from '../component-pool/youtube-player';
@@ -336,6 +337,12 @@ export class DemoFrameHelper {
336
337
  url: '',
337
338
  js: () => this.addPanel(slideTabDemo.text, <DemoPage story={slideTabDemo} />),
338
339
  },
340
+ {
341
+ id: slidePageDemo.id,
342
+ text: slidePageDemo.text,
343
+ url: '',
344
+ js: () => this.addPanel(slidePageDemo.text, <DemoPage story={slidePageDemo} />),
345
+ },
339
346
  {
340
347
  id: pagingLinkDemo.id,
341
348
  text: pagingLinkDemo.text,
@@ -52,6 +52,7 @@ import { searchInputDemo } from '../component-pool/search-input';
52
52
  import { pagingLinkDemo } from '../components/paging-link-demo';
53
53
  import { hEditorDemo } from '../component-pool/h-editor';
54
54
  import { slideTabDemo } from '../components/slide-tab-component-demo';
55
+ import { slidePageDemo } from '../components/slide-page-component-demo';
55
56
  import { qrcodeDemo } from '../component-pool/qrcode';
56
57
  import { iEditorDemo } from '../component-pool/i-editor';
57
58
  import { youtubePlayerDemo } from '../component-pool/youtube-player';
@@ -125,6 +126,7 @@ export const demoRegistry: Record<string, DemoStory<any>> = {
125
126
  [pagingLinkDemo.id]: pagingLinkDemo,
126
127
  [hEditorDemo.id]: hEditorDemo,
127
128
  [slideTabDemo.id]: slideTabDemo,
129
+ [slidePageDemo.id]: slidePageDemo,
128
130
  [qrcodeDemo.id]: qrcodeDemo,
129
131
  [iEditorDemo.id]: iEditorDemo,
130
132
  [youtubePlayerDemo.id]: youtubePlayerDemo,
@@ -38,7 +38,6 @@ export const ResponsiveFrame = (props: ResponsiveFrameProps) => {
38
38
  margin: '0 auto',
39
39
  overflowX: 'hidden',
40
40
  ...(!props.noSafeAreaTop ? {
41
- 'padding-top ': isSafeAreaSupported() ? 'constant(safe-area-inset-top)' : '',
42
41
  'padding-top': isSafeAreaSupported() ? 'env(safe-area-inset-top)' : '',
43
42
  } : {}),
44
43
  '.frame-top-menu': {
@@ -24,6 +24,7 @@ export const sliderFrameDemo: DemoStory<any> = {
24
24
  text: 'Slider Frame Demo',
25
25
  args: {
26
26
  direction: 'right',
27
+ addBottomSafearea: false,
27
28
  },
28
29
  argTypes: {
29
30
  direction: {
@@ -31,6 +32,10 @@ export const sliderFrameDemo: DemoStory<any> = {
31
32
  options: ['right', 'bottom'],
32
33
  description: 'Direction the slider enters from',
33
34
  },
35
+ addBottomSafearea: {
36
+ control: 'boolean',
37
+ description: 'Add safe area padding at bottom',
38
+ },
34
39
  },
35
40
  render: (args) => {
36
41
  const sliderHook: SliderFrameHookProps = {};
@@ -54,6 +59,7 @@ export const sliderFrameDemo: DemoStory<any> = {
54
59
  let close: SliderHelperCloseProps;
55
60
  close = await SliderHelper.show({
56
61
  direction: args.direction,
62
+ addBottomSafearea: args.addBottomSafearea,
57
63
  children: (
58
64
  <HeaderWithBackFrame title='Slider Helper Inner Page' onBack={() => close()}>
59
65
  <div style={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '20px' }}>
@@ -71,7 +77,12 @@ export const sliderFrameDemo: DemoStory<any> = {
71
77
  </div>
72
78
 
73
79
  {/* The SliderFrame is placed here but initially hidden */}
74
- <SliderFrame hook={sliderHook} direction={args.direction} defaultContent='' />
80
+ <SliderFrame
81
+ hook={sliderHook}
82
+ direction={args.direction}
83
+ addBottomSafearea={args.addBottomSafearea}
84
+ defaultContent=''
85
+ />
75
86
  </div>
76
87
  );
77
88
  },
@@ -27,6 +27,7 @@ export type SliderFrameProps = {
27
27
  hook?: SliderFrameHookProps;
28
28
  afterClose?: () => void | Promise<void>;
29
29
  maxWidth?: string;
30
+ addBottomSafearea?: boolean;
30
31
  };
31
32
  export const SliderFrame = (props: SliderFrameProps) => {
32
33
  let closeSlider: SliderHelperCloseProps | undefined;
@@ -39,6 +40,7 @@ export const SliderFrame = (props: SliderFrameProps) => {
39
40
  direction: props.direction || 'right',
40
41
  children,
41
42
  maxWidth: props.maxWidth,
43
+ addBottomSafearea: props.addBottomSafearea ?? false,
42
44
  closeEvent: () => {
43
45
  opened = false;
44
46
  closeSlider = undefined;
@@ -16,6 +16,7 @@ export type SliderHelperShowProps = {
16
16
  className?: string;
17
17
  contentClassName?: string;
18
18
  mountTarget?: HTMLElement;
19
+ addBottomSafearea?: boolean;
19
20
  };
20
21
 
21
22
  export class SliderHelper {
@@ -29,6 +30,7 @@ export class SliderHelper {
29
30
  className = '',
30
31
  contentClassName = '',
31
32
  mountTarget,
33
+ addBottomSafearea = false,
32
34
  }: SliderHelperShowProps): Promise<SliderHelperCloseProps> {
33
35
  const base = document.createElement('div');
34
36
  const isHorizontal = direction === 'left' || direction === 'right';
@@ -157,8 +159,12 @@ export class SliderHelper {
157
159
  transform: startTransform,
158
160
  transition: 'transform 0.4s ease-in-out',
159
161
  // trick: to put two padding-top properties
160
- 'padding-top ': isSafeAreaSupported() ? 'constant(safe-area-inset-top)' : '0',
161
162
  'padding-top': isSafeAreaSupported() ? 'env(safe-area-inset-top)' : '0',
163
+ ...(addBottomSafearea
164
+ ? {
165
+ 'padding-bottom': isSafeAreaSupported() ? 'env(safe-area-inset-bottom)' : '0',
166
+ }
167
+ : {}),
162
168
  ...contentCss,
163
169
  '&::-webkit-scrollbar': {
164
170
  display: 'none',
@@ -28,7 +28,6 @@ export const TopFrame = async ({
28
28
  // trick: to put two padding-top properties
29
29
  ...(!noSafeAreaTop
30
30
  ? {
31
- 'padding-top ': isSafeAreaSupported() ? 'constant(safe-area-inset-top)' : '',
32
31
  'padding-top': isSafeAreaSupported() ? 'env(safe-area-inset-top)' : '',
33
32
  }
34
33
  : {}),