export function tipCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Tip Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Bill Details Section
const billSection = form.addSubform('bill', { title: '๐งพ Bill Details' });
billSection.addRow(row => {
row.addDecimal('billAmount', {
label: 'Bill Amount',
min: 0,
max: 100000,
defaultValue: 85.50,
placeholder: 'e.g. 85.50',
prefix: '$',
isRequired: true
}, '1fr');
row.addDropdown('taxOption', {
label: 'Calculate Tip On',
options: [
{ id: 'pre-tax', name: 'Pre-tax Amount' },
{ id: 'post-tax', name: 'Total with Tax' }
],
defaultValue: 'post-tax',
tooltip: 'Choose whether to tip on pre-tax or post-tax amount'
}, '1fr');
});
billSection.addRow(row => {
row.addDecimal('taxAmount', {
label: 'Tax Amount (if pre-tax)',
min: 0,
max: 10000,
defaultValue: 0,
placeholder: 'e.g. 7.50',
prefix: '$',
isVisible: () => billSection.dropdown('taxOption')?.value() === 'pre-tax',
tooltip: 'Enter tax amount to calculate tip on pre-tax total'
});
});
// Tip Selection Section
const tipSection = form.addSubform('tip', { title: '๐ต Tip Amount' });
tipSection.addRow(row => {
row.addRadioButton('tipPreset', {
label: 'Tip Percentage',
options: [
{ id: '15', name: '15% (Good)' },
{ id: '18', name: '18% (Great)' },
{ id: '20', name: '20% (Excellent)' },
{ id: '25', name: '25% (Outstanding)' },
{ id: 'custom', name: 'Custom' }
],
defaultValue: '20',
isRequired: true
});
});
tipSection.addRow(row => {
row.addDecimal('customTip', {
label: 'Custom Tip Percentage',
min: 0,
max: 100,
defaultValue: 22,
suffix: '%',
isVisible: () => tipSection.radioButton('tipPreset')?.value() === 'custom'
});
});
// Split Bill Section
const splitSection = form.addSubform('split', { title: '๐ฅ Split the Bill' });
splitSection.addRow(row => {
row.addInteger('numberOfPeople', {
label: 'Number of People',
min: 1,
max: 50,
defaultValue: 1,
isRequired: true
}, '1fr');
row.addDropdown('roundOption', {
label: 'Round Total',
options: [
{ id: 'none', name: 'No Rounding' },
{ id: 'nearest', name: 'Nearest Dollar' },
{ id: 'up', name: 'Round Up' }
],
defaultValue: 'none',
tooltip: 'Round the total for easier splitting'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Quick Reference
const quickTipSection = form.addSubform('quickTip', { title: 'โก Quick Tip Reference', isCollapsible: true });
quickTipSection.addRow(row => {
row.addTextPanel('quickTips', {
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const taxOption = billSection.dropdown('taxOption')?.value() || 'post-tax';
const taxAmount = billSection.decimal('taxAmount')?.value() || 0;
const tipBase = taxOption === 'pre-tax' ? billAmount - taxAmount : billAmount;
const tip15 = ((Number(tipBase) || 0) * 0.15).toFixed(2);
const tip18 = ((Number(tipBase) || 0) * 0.18).toFixed(2);
const tip20 = ((Number(tipBase) || 0) * 0.20).toFixed(2);
const tip25 = ((Number(tipBase) || 0) * 0.25).toFixed(2);
return `15%: $${tip15} | 18%: $${tip18} | 20%: $${tip20} | 25%: $${tip25}`;
},
customStyles: { 'font-size': '0.9rem', 'color': '#475569', 'text-align': 'center' }
});
});
// Results Section
const resultsSection = form.addSubform('results', { title: '๐ Tip Breakdown', isCollapsible: false });
resultsSection.addRow(row => {
row.addPriceDisplay('tipAmount', {
label: 'Tip Amount',
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const taxOption = billSection.dropdown('taxOption')?.value() || 'post-tax';
const taxAmount = billSection.decimal('taxAmount')?.value() || 0;
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const tipBase = taxOption === 'pre-tax' ? billAmount - taxAmount : billAmount;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
return Math.round(tipBase * (tipPercent / 100) * 100) / 100;
},
variant: 'success'
}, '1fr');
row.addTextPanel('tipPercentDisplay', {
label: 'Tip Percentage',
computedValue: () => {
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
return `${tipPercent}%`;
},
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'text-align': 'center', 'color': '#059669' }
}, '1fr');
});
resultsSection.addRow(row => {
row.addPriceDisplay('totalAmount', {
label: 'Total (Bill + Tip)',
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const taxOption = billSection.dropdown('taxOption')?.value() || 'post-tax';
const taxAmount = billSection.decimal('taxAmount')?.value() || 0;
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const roundOption = splitSection.dropdown('roundOption')?.value() || 'none';
const tipBase = taxOption === 'pre-tax' ? billAmount - taxAmount : billAmount;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
const tipAmount = tipBase * (tipPercent / 100);
let total = billAmount + tipAmount;
if (roundOption === 'nearest') {
total = Math.round(total);
} else if (roundOption === 'up') {
total = Math.ceil(total);
}
return Math.round(total * 100) / 100;
},
variant: 'large'
});
});
// Per Person Section
const perPersonSection = form.addSubform('perPerson', { title: '๐ค Per Person', isCollapsible: false });
perPersonSection.addRow(row => {
row.addPriceDisplay('tipPerPerson', {
label: 'Tip per Person',
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const taxOption = billSection.dropdown('taxOption')?.value() || 'post-tax';
const taxAmount = billSection.decimal('taxAmount')?.value() || 0;
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const numberOfPeople = splitSection.integer('numberOfPeople')?.value() || 1;
const tipBase = taxOption === 'pre-tax' ? billAmount - taxAmount : billAmount;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
const tipAmount = tipBase * (tipPercent / 100);
return Math.round((tipAmount / numberOfPeople) * 100) / 100;
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('totalPerPerson', {
label: 'Total per Person',
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const taxOption = billSection.dropdown('taxOption')?.value() || 'post-tax';
const taxAmount = billSection.decimal('taxAmount')?.value() || 0;
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const numberOfPeople = splitSection.integer('numberOfPeople')?.value() || 1;
const roundOption = splitSection.dropdown('roundOption')?.value() || 'none';
const tipBase = taxOption === 'pre-tax' ? billAmount - taxAmount : billAmount;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
const tipAmount = tipBase * (tipPercent / 100);
let total = billAmount + tipAmount;
if (roundOption === 'nearest') {
total = Math.round(total);
} else if (roundOption === 'up') {
total = Math.ceil(total);
}
return Math.round((total / numberOfPeople) * 100) / 100;
},
variant: 'success'
}, '1fr');
});
perPersonSection.addRow(row => {
row.addTextPanel('splitNote', {
computedValue: () => {
const numberOfPeople = splitSection.integer('numberOfPeople')?.value() || 1;
if (numberOfPeople === 1) {
return 'Not splitting the bill';
}
return `Bill split ${numberOfPeople} ways`;
},
customStyles: { 'font-size': '0.9rem', 'color': '#64748b', 'text-align': 'center' }
});
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addTextPanel('summaryText', {
computedValue: () => {
const billAmount = billSection.decimal('billAmount')?.value() || 85.50;
const tipPreset = tipSection.radioButton('tipPreset')?.value() || '20';
const customTip = tipSection.decimal('customTip')?.value() || 22;
const numberOfPeople = splitSection.integer('numberOfPeople')?.value() || 1;
const tipPercent = tipPreset === 'custom' ? customTip : parseFloat(tipPreset);
if (numberOfPeople === 1) {
return `$${(Number(billAmount) || 0).toFixed(2)} bill with ${tipPercent}% tip`;
}
return `$${(Number(billAmount) || 0).toFixed(2)} bill with ${tipPercent}% tip, split ${numberOfPeople} ways`;
},
customStyles: { 'font-size': '0.95rem', 'font-weight': '500', 'text-align': 'center', 'color': '#1e293b' }
});
});
summarySection.addRow(row => {
row.addTextPanel('tippingNote', {
computedValue: () => 'Tip amounts are suggestions. Always tip based on service quality and local customs.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Share Bill'
});
}