mertani-web-toolkit 0.1.66 → 0.1.67

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,14 @@
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';
34
36
 
35
37
  // Any
36
38
  class?: string;
@@ -53,18 +55,20 @@
53
55
 
54
56
  // ===Properties===
55
57
  // Data
56
- label = 'Button',
58
+ label = '',
57
59
 
58
60
  // Additional Actions
59
61
  isLoading = false,
60
62
  isShow = true,
61
63
  disabled = false,
62
64
  tooltip = '',
65
+ tooltipPosition = 'auto',
63
66
 
64
67
  class: className = '',
65
68
  style: customStyle = '',
66
69
  onclick,
67
70
  onmouseenter,
71
+ onmouseleave,
68
72
  ...props
69
73
  }: Props = $props();
70
74
 
@@ -72,13 +76,61 @@
72
76
  const sizeConfig = $derived(() => {
73
77
  switch (size) {
74
78
  case 48:
75
- return { height: '48px', padding: '10px 24px', fontSize: '18px', borderRadius: '8px' };
79
+ return {
80
+ height: '48px',
81
+ width: label ? 'auto' : '48px',
82
+ padding: label ? '10px 24px' : '10px',
83
+ fontSize: '18px',
84
+ borderRadius: '8px'
85
+ };
76
86
  case 40:
77
- return { height: '40px', padding: '8px 16px', fontSize: '16px', borderRadius: '8px' };
87
+ return {
88
+ height: '40px',
89
+ width: label ? 'auto' : '40px',
90
+ padding: label ? '8px 16px' : '8px',
91
+ fontSize: '16px',
92
+ borderRadius: '8px'
93
+ };
78
94
  case 32:
79
- return { height: '32px', padding: '6px 12px', fontSize: '14px', borderRadius: '6px' };
95
+ return {
96
+ height: '32px',
97
+ width: label ? 'auto' : '32px',
98
+ padding: label ? '6px 12px' : '6px',
99
+ fontSize: '14px',
100
+ borderRadius: '6px'
101
+ };
102
+ case 24:
103
+ return {
104
+ height: '24px',
105
+ width: label ? 'auto' : '24px',
106
+ padding: label ? '4px 8px' : '4px',
107
+ fontSize: '12px',
108
+ borderRadius: '4px'
109
+ };
110
+ case 20:
111
+ return {
112
+ height: '20px',
113
+ width: label ? 'auto' : '20px',
114
+ padding: label ? '2px 6px' : '2px',
115
+ fontSize: '10px',
116
+ borderRadius: '4px'
117
+ };
118
+ case 16:
119
+ return {
120
+ height: '16px',
121
+ width: label ? 'auto' : '16px',
122
+ padding: label ? '0px 4px' : '0px',
123
+ fontSize: '10px',
124
+ borderRadius: '4px'
125
+ };
80
126
  default:
81
- return { height: '40px', padding: '8px 16px', fontSize: '16px', borderRadius: '8px' };
127
+ return {
128
+ height: '40px',
129
+ width: label ? 'auto' : '40px',
130
+ padding: label ? '8px 16px' : '8px',
131
+ fontSize: '16px',
132
+ borderRadius: '8px'
133
+ };
82
134
  }
83
135
  });
84
136
 
@@ -88,6 +140,7 @@
88
140
 
89
141
  // Size styles
90
142
  styles.push(`height: ${sizeConfig().height};`);
143
+ styles.push(`width: ${sizeConfig().width};`);
91
144
  styles.push(`padding: ${sizeConfig().padding};`);
92
145
  styles.push(`font-size: ${sizeConfig().fontSize};`);
93
146
  styles.push(`border-radius: ${sizeConfig().borderRadius};`);
@@ -152,6 +205,38 @@
152
205
  return classes.join(' ');
153
206
  });
154
207
 
208
+ let showTooltip = $state(false);
209
+ let actualPosition = $state<'top' | 'bottom' | 'left' | 'right'>('right');
210
+ let buttonEl: HTMLButtonElement | null = $state(null);
211
+
212
+ function updateTooltipPosition() {
213
+ if (tooltipPosition !== 'auto') {
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ actualPosition = tooltipPosition as any;
216
+ return;
217
+ }
218
+
219
+ if (!buttonEl) return;
220
+
221
+ const rect = buttonEl.getBoundingClientRect();
222
+ const viewportWidth = window.innerWidth;
223
+ // const viewportHeight = window.innerHeight;
224
+ const tooltipMargin = 12;
225
+ const estimatedWidth = 120; // heuristic
226
+ const estimatedHeight = 40; // heuristic
227
+
228
+ // Priority: Right -> Left -> Top -> Bottom
229
+ if (rect.right + estimatedWidth + tooltipMargin < viewportWidth) {
230
+ actualPosition = 'right';
231
+ } else if (rect.left - estimatedWidth - tooltipMargin > 0) {
232
+ actualPosition = 'left';
233
+ } else if (rect.top - estimatedHeight - tooltipMargin > 0) {
234
+ actualPosition = 'top';
235
+ } else {
236
+ actualPosition = 'bottom';
237
+ }
238
+ }
239
+
155
240
  function handleClick() {
156
241
  if (!disabled && !isLoading && onclick) {
157
242
  onclick();
@@ -159,21 +244,33 @@
159
244
  }
160
245
 
161
246
  function handleMouseEnter() {
247
+ if (tooltip) {
248
+ updateTooltipPosition();
249
+ showTooltip = true;
250
+ }
162
251
  if (!disabled && !isLoading && onmouseenter) {
163
252
  onmouseenter();
164
253
  }
165
254
  }
255
+
256
+ function handleMouseLeave() {
257
+ showTooltip = false;
258
+ if (!disabled && !isLoading && onmouseleave) {
259
+ onmouseleave();
260
+ }
261
+ }
166
262
  </script>
167
263
 
168
264
  {#if isShow}
169
265
  <button
266
+ bind:this={buttonEl}
170
267
  type="button"
171
268
  class={buttonClasses()}
172
269
  style={buttonStyles()}
173
270
  disabled={disabled || isLoading}
174
271
  onclick={handleClick}
175
272
  onmouseenter={handleMouseEnter}
176
- title={tooltip || ''}
273
+ onmouseleave={handleMouseLeave}
177
274
  {...props}
178
275
  >
179
276
  {#if isLoading}
@@ -194,5 +291,14 @@
194
291
  {#if !isLoading && icon && iconPosition === 'right'}
195
292
  <Icon name={icon} color={iconColor || 'currentColor'} width={16} height={16} />
196
293
  {/if}
294
+
295
+ {#if tooltip && showTooltip}
296
+ <div class="btn-tooltip-container tooltip-{actualPosition}">
297
+ <div class="btn-tooltip">
298
+ {tooltip}
299
+ <div class="tooltip-arrow"></div>
300
+ </div>
301
+ </div>
302
+ {/if}
197
303
  </button>
198
304
  {/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,12 @@ 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';
23
25
  class?: string;
24
26
  style?: string;
25
27
  }
@@ -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(--color-bg-inverse);
119
+ color: var(--color-text-inverse);
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(--color-bg-inverse);
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.67",
4
4
  "homepage": "https://storybook.mertani.com/",
5
5
  "scripts": {
6
6
  "dev": "vite dev",