mertani-web-toolkit 0.1.66 → 0.1.68

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.
@@ -7,7 +7,7 @@
7
7
  interface Props extends Omit<HTMLButtonAttributes, 'type'> {
8
8
  // ===Styles===
9
9
  type?: 'solid' | 'outline';
10
- size?: 48 | 40 | 32;
10
+ size?: 48 | 40 | 32 | 24 | 20 | 16;
11
11
  backgroundColor?: string;
12
12
  textColor?: string;
13
13
  borderColor?: string;
@@ -25,12 +25,16 @@
25
25
  // Events
26
26
  onclick?: () => void;
27
27
  onmouseenter?: () => void;
28
+ onmouseleave?: () => void;
28
29
 
29
30
  // Additional Actions
30
31
  isLoading?: boolean;
31
32
  isShow?: boolean;
32
33
  disabled?: boolean;
33
34
  tooltip?: string;
35
+ tooltipPosition?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
36
+ tooltipBgColor?: string;
37
+ tooltipTextColor?: string;
34
38
 
35
39
  // Any
36
40
  class?: string;
@@ -53,18 +57,22 @@
53
57
 
54
58
  // ===Properties===
55
59
  // Data
56
- label = 'Button',
60
+ label = '',
57
61
 
58
62
  // Additional Actions
59
63
  isLoading = false,
60
64
  isShow = true,
61
65
  disabled = false,
62
66
  tooltip = '',
67
+ tooltipPosition = 'auto',
68
+ tooltipBgColor = 'var(--color-bg-inverse)',
69
+ tooltipTextColor = 'var(--color-text-inverse)',
63
70
 
64
71
  class: className = '',
65
72
  style: customStyle = '',
66
73
  onclick,
67
74
  onmouseenter,
75
+ onmouseleave,
68
76
  ...props
69
77
  }: Props = $props();
70
78
 
@@ -72,13 +80,61 @@
72
80
  const sizeConfig = $derived(() => {
73
81
  switch (size) {
74
82
  case 48:
75
- return { height: '48px', padding: '10px 24px', fontSize: '18px', borderRadius: '8px' };
83
+ return {
84
+ height: '48px',
85
+ width: label ? 'auto' : '48px',
86
+ padding: label ? '10px 24px' : '10px',
87
+ fontSize: '18px',
88
+ borderRadius: '8px'
89
+ };
76
90
  case 40:
77
- return { height: '40px', padding: '8px 16px', fontSize: '16px', borderRadius: '8px' };
91
+ return {
92
+ height: '40px',
93
+ width: label ? 'auto' : '40px',
94
+ padding: label ? '8px 16px' : '8px',
95
+ fontSize: '16px',
96
+ borderRadius: '8px'
97
+ };
78
98
  case 32:
79
- return { height: '32px', padding: '6px 12px', fontSize: '14px', borderRadius: '6px' };
99
+ return {
100
+ height: '32px',
101
+ width: label ? 'auto' : '32px',
102
+ padding: label ? '6px 12px' : '6px',
103
+ fontSize: '14px',
104
+ borderRadius: '6px'
105
+ };
106
+ case 24:
107
+ return {
108
+ height: '24px',
109
+ width: label ? 'auto' : '24px',
110
+ padding: label ? '4px 8px' : '4px',
111
+ fontSize: '12px',
112
+ borderRadius: '4px'
113
+ };
114
+ case 20:
115
+ return {
116
+ height: '20px',
117
+ width: label ? 'auto' : '20px',
118
+ padding: label ? '2px 6px' : '2px',
119
+ fontSize: '10px',
120
+ borderRadius: '4px'
121
+ };
122
+ case 16:
123
+ return {
124
+ height: '16px',
125
+ width: label ? 'auto' : '16px',
126
+ padding: label ? '0px 4px' : '0px',
127
+ fontSize: '10px',
128
+ borderRadius: '4px'
129
+ };
80
130
  default:
81
- return { height: '40px', padding: '8px 16px', fontSize: '16px', borderRadius: '8px' };
131
+ return {
132
+ height: '40px',
133
+ width: label ? 'auto' : '40px',
134
+ padding: label ? '8px 16px' : '8px',
135
+ fontSize: '16px',
136
+ borderRadius: '8px'
137
+ };
82
138
  }
83
139
  });
84
140
 
@@ -88,6 +144,7 @@
88
144
 
89
145
  // Size styles
90
146
  styles.push(`height: ${sizeConfig().height};`);
147
+ styles.push(`width: ${sizeConfig().width};`);
91
148
  styles.push(`padding: ${sizeConfig().padding};`);
92
149
  styles.push(`font-size: ${sizeConfig().fontSize};`);
93
150
  styles.push(`border-radius: ${sizeConfig().borderRadius};`);
@@ -152,6 +209,38 @@
152
209
  return classes.join(' ');
153
210
  });
154
211
 
212
+ let showTooltip = $state(false);
213
+ let actualPosition = $state<'top' | 'bottom' | 'left' | 'right'>('right');
214
+ let buttonEl: HTMLButtonElement | null = $state(null);
215
+
216
+ function updateTooltipPosition() {
217
+ if (tooltipPosition !== 'auto') {
218
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
+ actualPosition = tooltipPosition as any;
220
+ return;
221
+ }
222
+
223
+ if (!buttonEl) return;
224
+
225
+ const rect = buttonEl.getBoundingClientRect();
226
+ const viewportWidth = window.innerWidth;
227
+ // const viewportHeight = window.innerHeight;
228
+ const tooltipMargin = 12;
229
+ const estimatedWidth = 120; // heuristic
230
+ const estimatedHeight = 40; // heuristic
231
+
232
+ // Priority: Right -> Left -> Top -> Bottom
233
+ if (rect.right + estimatedWidth + tooltipMargin < viewportWidth) {
234
+ actualPosition = 'right';
235
+ } else if (rect.left - estimatedWidth - tooltipMargin > 0) {
236
+ actualPosition = 'left';
237
+ } else if (rect.top - estimatedHeight - tooltipMargin > 0) {
238
+ actualPosition = 'top';
239
+ } else {
240
+ actualPosition = 'bottom';
241
+ }
242
+ }
243
+
155
244
  function handleClick() {
156
245
  if (!disabled && !isLoading && onclick) {
157
246
  onclick();
@@ -159,21 +248,33 @@
159
248
  }
160
249
 
161
250
  function handleMouseEnter() {
251
+ if (tooltip) {
252
+ updateTooltipPosition();
253
+ showTooltip = true;
254
+ }
162
255
  if (!disabled && !isLoading && onmouseenter) {
163
256
  onmouseenter();
164
257
  }
165
258
  }
259
+
260
+ function handleMouseLeave() {
261
+ showTooltip = false;
262
+ if (!disabled && !isLoading && onmouseleave) {
263
+ onmouseleave();
264
+ }
265
+ }
166
266
  </script>
167
267
 
168
268
  {#if isShow}
169
269
  <button
270
+ bind:this={buttonEl}
170
271
  type="button"
171
272
  class={buttonClasses()}
172
273
  style={buttonStyles()}
173
274
  disabled={disabled || isLoading}
174
275
  onclick={handleClick}
175
276
  onmouseenter={handleMouseEnter}
176
- title={tooltip || ''}
277
+ onmouseleave={handleMouseLeave}
177
278
  {...props}
178
279
  >
179
280
  {#if isLoading}
@@ -194,5 +295,17 @@
194
295
  {#if !isLoading && icon && iconPosition === 'right'}
195
296
  <Icon name={icon} color={iconColor || 'currentColor'} width={16} height={16} />
196
297
  {/if}
298
+
299
+ {#if tooltip && showTooltip}
300
+ <div class="btn-tooltip-container tooltip-{actualPosition}">
301
+ <div
302
+ class="btn-tooltip"
303
+ style={`--tooltip-bg-color: ${tooltipBgColor}; --tooltip-text-color: ${tooltipTextColor};`}
304
+ >
305
+ {tooltip}
306
+ <div class="tooltip-arrow"></div>
307
+ </div>
308
+ </div>
309
+ {/if}
197
310
  </button>
198
311
  {/if}
@@ -3,7 +3,7 @@ import type { TIconName } from '../../icons/index.js';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
4
  interface Props extends Omit<HTMLButtonAttributes, 'type'> {
5
5
  type?: 'solid' | 'outline';
6
- size?: 48 | 40 | 32;
6
+ size?: 48 | 40 | 32 | 24 | 20 | 16;
7
7
  backgroundColor?: string;
8
8
  textColor?: string;
9
9
  borderColor?: string;
@@ -16,10 +16,14 @@ interface Props extends Omit<HTMLButtonAttributes, 'type'> {
16
16
  label?: string;
17
17
  onclick?: () => void;
18
18
  onmouseenter?: () => void;
19
+ onmouseleave?: () => void;
19
20
  isLoading?: boolean;
20
21
  isShow?: boolean;
21
22
  disabled?: boolean;
22
23
  tooltip?: string;
24
+ tooltipPosition?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
25
+ tooltipBgColor?: string;
26
+ tooltipTextColor?: string;
23
27
  class?: string;
24
28
  style?: string;
25
29
  }
@@ -92,3 +92,86 @@
92
92
  display: inline-flex;
93
93
  align-items: center;
94
94
  }
95
+
96
+ /* Tooltip */
97
+ .btn-tooltip-container {
98
+ position: absolute;
99
+ z-index: 50;
100
+ pointer-events: none;
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ opacity: 0;
105
+ animation: tooltip-fade-in 0.2s forwards;
106
+ }
107
+
108
+ @keyframes tooltip-fade-in {
109
+ from {
110
+ opacity: 0;
111
+ }
112
+ to {
113
+ opacity: 1;
114
+ }
115
+ }
116
+
117
+ .btn-tooltip {
118
+ background-color: var(--tooltip-bg-color);
119
+ color: var(--tooltip-text-color);
120
+ padding: 8px 14px;
121
+ border-radius: 10px;
122
+ font-size: 13px;
123
+ font-weight: 500;
124
+ white-space: nowrap;
125
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
126
+ position: relative;
127
+ line-height: 1.2;
128
+ }
129
+
130
+ .tooltip-arrow {
131
+ position: absolute;
132
+ width: 8px;
133
+ height: 8px;
134
+ background-color: var(--tooltip-bg-color);
135
+ transform: rotate(45deg);
136
+ }
137
+
138
+ /* Positions */
139
+ .tooltip-top {
140
+ bottom: 100%;
141
+ left: 50%;
142
+ transform: translateX(-50%) translateY(-10px);
143
+ }
144
+ .tooltip-top .tooltip-arrow {
145
+ bottom: -4px;
146
+ left: calc(50% - 4px);
147
+ }
148
+
149
+ .tooltip-bottom {
150
+ top: 100%;
151
+ left: 50%;
152
+ transform: translateX(-50%) translateY(10px);
153
+ }
154
+ .tooltip-bottom .tooltip-arrow {
155
+ top: -4px;
156
+ left: calc(50% - 4px);
157
+ }
158
+
159
+ .tooltip-left {
160
+ right: 100%;
161
+ top: 50%;
162
+ transform: translateY(-50%) translateX(-10px);
163
+ }
164
+ .tooltip-left .tooltip-arrow {
165
+ right: -4px;
166
+ top: calc(50% - 4px);
167
+ }
168
+
169
+ .tooltip-right {
170
+ left: 100%;
171
+ top: 50%;
172
+ transform: translateY(-50%) translateX(10px);
173
+ }
174
+ .tooltip-right .tooltip-arrow {
175
+ left: -4px;
176
+ top: calc(50% - 4px);
177
+ }
@@ -86,6 +86,7 @@
86
86
 
87
87
  if (!L) {
88
88
  const leafletMod = await import('leaflet');
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
90
  L = (leafletMod as any).default ?? leafletMod;
90
91
 
91
92
  await import('leaflet.markercluster');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mertani-web-toolkit",
3
- "version": "0.1.66",
3
+ "version": "0.1.68",
4
4
  "homepage": "https://storybook.mertani.com/",
5
5
  "scripts": {
6
6
  "dev": "vite dev",