Skip to content

Commit 7f5bc9b

Browse files
Make widget resizable and centerable
1 parent 3236475 commit 7f5bc9b

4 files changed

Lines changed: 261 additions & 39 deletions

File tree

dist/buzzwald-widget.js

Lines changed: 37 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"timestamp": 1753219408858
2+
"timestamp": 1753361111288
33
}

src/widget.js

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class BuzzwaldWidget {
1313
position: 'bottom-right',
1414
backgroundColor: '#FFFF00',
1515
iconColor: '#000000',
16+
size: 'medium', // New: size option (small, medium, large, custom)
17+
customSize: null, // New: custom size in pixels for when size is 'custom'
1618
...config
1719
};
1820

@@ -119,13 +121,29 @@ export class BuzzwaldWidget {
119121
throw new Error('ID must be a string');
120122
}
121123

122-
// Validate position
123-
const validPositions = ['bottom-right', 'bottom-left', 'top-right', 'top-left'];
124+
// Validate position - now includes center
125+
const validPositions = ['bottom-right', 'bottom-left', 'top-right', 'top-left', 'center'];
124126
if (!validPositions.includes(this.config.position)) {
125127
console.warn(`Buzzwald: Invalid position "${this.config.position}". Using default "bottom-right"`);
126128
this.config.position = 'bottom-right';
127129
}
128130

131+
// Validate size
132+
const validSizes = ['small', 'medium', 'large', 'custom'];
133+
if (!validSizes.includes(this.config.size)) {
134+
console.warn(`Buzzwald: Invalid size "${this.config.size}". Using default "medium"`);
135+
this.config.size = 'medium';
136+
}
137+
138+
// Validate custom size if size is 'custom'
139+
if (this.config.size === 'custom') {
140+
if (!this.config.customSize || typeof this.config.customSize !== 'number' || this.config.customSize < 40 || this.config.customSize > 120) {
141+
console.warn(`Buzzwald: Invalid custom size "${this.config.customSize}". Using default "medium"`);
142+
this.config.size = 'medium';
143+
this.config.customSize = null;
144+
}
145+
}
146+
129147
// Validate colors
130148
if (this.config.backgroundColor && !this.isValidColor(this.config.backgroundColor)) {
131149
console.warn(`Buzzwald: Invalid background color "${this.config.backgroundColor}". Using default`);
@@ -144,6 +162,36 @@ export class BuzzwaldWidget {
144162
return div.style.color !== '';
145163
}
146164

165+
getButtonDimensions() {
166+
let buttonSize, fontSize, iconSize;
167+
168+
switch (this.config.size) {
169+
case 'small':
170+
buttonSize = 50;
171+
fontSize = 20;
172+
iconSize = 20;
173+
break;
174+
case 'large':
175+
buttonSize = 80;
176+
fontSize = 32;
177+
iconSize = 32;
178+
break;
179+
case 'custom':
180+
buttonSize = this.config.customSize;
181+
fontSize = Math.round(this.config.customSize * 0.4); // 40% of button size
182+
iconSize = Math.round(this.config.customSize * 0.4);
183+
break;
184+
case 'medium':
185+
default:
186+
buttonSize = 60;
187+
fontSize = 24;
188+
iconSize = 24;
189+
break;
190+
}
191+
192+
return { buttonSize, fontSize, iconSize };
193+
}
194+
147195
checkBrowserSupport() {
148196
// Check for required browser features
149197
if (!window.fetch) {
@@ -288,6 +336,9 @@ export class BuzzwaldWidget {
288336
case 'top-left':
289337
positionStyles = 'top: 90px; left: 20px;';
290338
break;
339+
case 'center':
340+
positionStyles = 'top: calc(50% + 60px); left: 50%; transform: translateX(-50%);';
341+
break;
291342
default:
292343
positionStyles = 'bottom: 90px; right: 20px;';
293344
}
@@ -377,6 +428,9 @@ export class BuzzwaldWidget {
377428
case 'top-left':
378429
positionStyles = 'top: 90px; left: 20px;';
379430
break;
431+
case 'center':
432+
positionStyles = 'top: calc(50% + 60px); left: 50%; transform: translateX(-50%);';
433+
break;
380434
default:
381435
positionStyles = 'bottom: 90px; right: 20px;';
382436
}
@@ -426,6 +480,8 @@ export class BuzzwaldWidget {
426480
const styleId = 'buzzwald-widget-styles';
427481
if (document.getElementById(styleId)) return;
428482

483+
const { buttonSize, fontSize, iconSize } = this.getButtonDimensions();
484+
429485
const style = document.createElement('style');
430486
style.id = styleId;
431487
style.textContent = `
@@ -444,8 +500,8 @@ export class BuzzwaldWidget {
444500
}
445501
446502
.buzzwald-button {
447-
width: 60px;
448-
height: 60px;
503+
width: ${buttonSize}px;
504+
height: ${buttonSize}px;
449505
border-radius: 50%;
450506
border: none;
451507
background-color: ${this.config.backgroundColor};
@@ -457,7 +513,7 @@ export class BuzzwaldWidget {
457513
transition: transform 0.2s ease, box-shadow 0.2s ease;
458514
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
459515
outline: none;
460-
font-size: 24px;
516+
font-size: ${fontSize}px;
461517
line-height: 1;
462518
}
463519
@@ -495,6 +551,12 @@ export class BuzzwaldWidget {
495551
left: 20px;
496552
}
497553
554+
.buzzwald-widget.center {
555+
top: 50%;
556+
left: 50%;
557+
transform: translate(-50%, -50%);
558+
}
559+
498560
.buzzwald-button.connecting {
499561
animation: buzzwald-pulse 1.5s infinite;
500562
}
@@ -544,8 +606,8 @@ export class BuzzwaldWidget {
544606
}
545607
546608
.buzzwald-phone-icon {
547-
width: 24px;
548-
height: 24px;
609+
width: ${iconSize}px;
610+
height: ${iconSize}px;
549611
fill: currentColor;
550612
}
551613
@@ -570,6 +632,12 @@ export class BuzzwaldWidget {
570632
.buzzwald-widget.top-left {
571633
left: 15px;
572634
}
635+
636+
.buzzwald-widget.center {
637+
top: 50%;
638+
left: 50%;
639+
transform: translate(-50%, -50%);
640+
}
573641
}
574642
575643
/* Hide Vapi's default button */
@@ -715,10 +783,32 @@ export class BuzzwaldWidget {
715783
showErrorMessage(message) {
716784
// Create temporary error message
717785
const errorMsg = document.createElement('div');
786+
787+
// Position the message based on widget position
788+
let positionStyles = '';
789+
switch (this.config.position) {
790+
case 'bottom-right':
791+
positionStyles = 'bottom: 90px; right: 20px;';
792+
break;
793+
case 'bottom-left':
794+
positionStyles = 'bottom: 90px; left: 20px;';
795+
break;
796+
case 'top-right':
797+
positionStyles = 'top: 90px; right: 20px;';
798+
break;
799+
case 'top-left':
800+
positionStyles = 'top: 90px; left: 20px;';
801+
break;
802+
case 'center':
803+
positionStyles = 'top: calc(50% + 60px); left: 50%; transform: translateX(-50%);';
804+
break;
805+
default:
806+
positionStyles = 'bottom: 90px; right: 20px;';
807+
}
808+
718809
errorMsg.style.cssText = `
719810
position: fixed;
720-
bottom: 90px;
721-
right: 20px;
811+
${positionStyles}
722812
background: #ff4444;
723813
color: white;
724814
padding: 8px 12px;

test/test.html

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ <h2>Test Controls</h2>
9797
<button onclick="testUpgrade()">Simulate User Upgrade</button>
9898
<button onclick="testRefreshAfterUpgrade()">Test Refresh After Upgrade</button>
9999
<button onclick="manualRetryCheck()">Manual Retry Check (Debug)</button>
100+
<hr>
101+
<h3>New Features Testing</h3>
102+
<button onclick="testCenterPosition()">Test Center Position</button>
103+
<button onclick="testSmallSize()">Test Small Size</button>
104+
<button onclick="testLargeSize()">Test Large Size</button>
105+
<button onclick="testCustomSize()">Test Custom Size (100px)</button>
106+
<button onclick="testDefaultSize()">Reset to Default</button>
100107
</div>
101108

102109
<div class="content">
@@ -108,7 +115,7 @@ <h2>Test Content</h2>
108115
<h3>Expected Behavior:</h3>
109116
<ul>
110117
<li>Yellow circular button with black phone icon</li>
111-
<li>Fixed positioning in bottom-right corner</li>
118+
<li>Fixed positioning in bottom-right corner (or as configured)</li>
112119
<li>Hover effects (scale and shadow)</li>
113120
<li>Click to start call</li>
114121
<li>Visual feedback during call states</li>
@@ -122,6 +129,13 @@ <h3>Call States:</h3>
122129
<li><strong>Ended:</strong> Red background (briefly)</li>
123130
</ul>
124131

132+
<h3>New Features:</h3>
133+
<ul>
134+
<li><strong>Position Options:</strong> bottom-right, bottom-left, top-right, top-left, center</li>
135+
<li><strong>Size Options:</strong> small (50px), medium (60px), large (80px), custom (40-120px)</li>
136+
<li><strong>Custom Sizing:</strong> Specify exact pixel size with customSize property</li>
137+
</ul>
138+
125139
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
126140

127141
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
@@ -132,7 +146,7 @@ <h3>Call States:</h3>
132146
<script>
133147
// Configuration for the widget
134148
window.BuzzwaldConfig = {
135-
id: '1792d475-9f91-4886-a59b-801848adf489',
149+
id: '',
136150
position: 'bottom-right',
137151
backgroundColor: '#FFFF00',
138152
iconColor: '#000000',
@@ -268,7 +282,7 @@ <h3>Call States:</h3>
268282
setTimeout(() => {
269283
// Restore original config
270284
window.BuzzwaldConfig = {
271-
id: '1792d475-9f91-4886-a59b-801848adf489',
285+
id: '',
272286
position: 'bottom-right',
273287
backgroundColor: '#FFFF00',
274288
iconColor: '#000000',
@@ -311,6 +325,113 @@ <h3>Call States:</h3>
311325
}
312326
}
313327

328+
// New feature test functions
329+
function testCenterPosition() {
330+
if (window.buzzwaldDestroy) {
331+
window.buzzwaldDestroy();
332+
setTimeout(() => {
333+
window.BuzzwaldConfig = {
334+
id: '',
335+
position: 'center',
336+
backgroundColor: '#FFFF00',
337+
iconColor: '#000000',
338+
size: 'medium'
339+
};
340+
341+
import('/src/widget.js').then(module => {
342+
console.log('🎯 Creating centered widget...');
343+
new module.BuzzwaldWidget(window.BuzzwaldConfig);
344+
alert('Widget moved to center!');
345+
});
346+
}, 100);
347+
}
348+
}
349+
350+
function testSmallSize() {
351+
if (window.buzzwaldDestroy) {
352+
window.buzzwaldDestroy();
353+
setTimeout(() => {
354+
window.BuzzwaldConfig = {
355+
id: '',
356+
position: 'bottom-right',
357+
backgroundColor: '#FFFF00',
358+
iconColor: '#000000',
359+
size: 'small'
360+
};
361+
362+
import('/src/widget.js').then(module => {
363+
console.log('🔽 Creating small widget...');
364+
new module.BuzzwaldWidget(window.BuzzwaldConfig);
365+
alert('Widget size changed to small (50px)!');
366+
});
367+
}, 100);
368+
}
369+
}
370+
371+
function testLargeSize() {
372+
if (window.buzzwaldDestroy) {
373+
window.buzzwaldDestroy();
374+
setTimeout(() => {
375+
window.BuzzwaldConfig = {
376+
id: '',
377+
position: 'bottom-right',
378+
backgroundColor: '#FFFF00',
379+
iconColor: '#000000',
380+
size: 'large'
381+
};
382+
383+
import('/src/widget.js').then(module => {
384+
console.log('🔼 Creating large widget...');
385+
new module.BuzzwaldWidget(window.BuzzwaldConfig);
386+
alert('Widget size changed to large (80px)!');
387+
});
388+
}, 100);
389+
}
390+
}
391+
392+
function testCustomSize() {
393+
if (window.buzzwaldDestroy) {
394+
window.buzzwaldDestroy();
395+
setTimeout(() => {
396+
window.BuzzwaldConfig = {
397+
id: '',
398+
position: 'bottom-right',
399+
backgroundColor: '#FFFF00',
400+
iconColor: '#000000',
401+
size: 'custom',
402+
customSize: 100
403+
};
404+
405+
import('/src/widget.js').then(module => {
406+
console.log('📏 Creating custom size widget (100px)...');
407+
new module.BuzzwaldWidget(window.BuzzwaldConfig);
408+
alert('Widget size changed to custom (100px)!');
409+
});
410+
}, 100);
411+
}
412+
}
413+
414+
function testDefaultSize() {
415+
if (window.buzzwaldDestroy) {
416+
window.buzzwaldDestroy();
417+
setTimeout(() => {
418+
window.BuzzwaldConfig = {
419+
id: '',
420+
position: 'bottom-right',
421+
backgroundColor: '#FFFF00',
422+
iconColor: '#000000',
423+
size: 'medium'
424+
};
425+
426+
import('/src/widget.js').then(module => {
427+
console.log('🔄 Resetting to default size...');
428+
new module.BuzzwaldWidget(window.BuzzwaldConfig);
429+
alert('Widget reset to default size (60px)!');
430+
});
431+
}, 100);
432+
}
433+
}
434+
314435
// Load widget script in development mode
315436
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
316437
const script = document.createElement('script');

0 commit comments

Comments
 (0)