I will post here a loom about a behaviour I get on a custom component. I am trying to solve this for some time now and I did try everything I know. Can anyone please help me?
Here is a loom explaining the problem: Do tables have any CSS that will make this behaviour happen? | Loom
Here is the HTML of the custom component:
<!-- 3rd party scripts and styles -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- Add Flatpickr CSS and JS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- root element where the component will be rendered -->
<div class="root"></div>
<!-- custom styles -->
<style>
.filter-container {
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
width: 700px;
}
.filter-group {
margin-bottom: 20px;
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #fafafa;
position: relative;
}
.filter-row {
display: flex;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
}
.filter-row select, .filter-row input {
margin-right: 10px;
padding: 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: white;
flex: 1;
}
.filter-row button, .group-logic-button, .row-logic-button, .remove-group-button {
margin-left: 10px;
padding: 8px 12px;
border: 1px solid #feb400;
border-radius: 4px;
background-color: #feb400;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
text-align: center;
vertical-align: middle;
line-height: normal;
}
.filter-row button:hover, .group-logic-button:hover, .row-logic-button:hover, .remove-group-button:hover {
background-color: #feb400;
color: white;
}
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.filter-header button {
padding: 10px 20px;
border: 1px solid #feb400;
border-radius: 8px;
background-color: #feb400;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.filter-header button:hover {
background-color: #feb400;
color: white;
}
.add-button-container {
display: flex;
align-items: center;
margin-top: 10px;
margin-bottom: 10px;
}
.add-button-container button {
margin-right: 10px;
border: 1px solid #feb400;
border-radius: 4px;
background-color: #feb400;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.add-button-container button:hover {
background-color: #feb400;
color: white;
}
.logic-container {
display: flex;
align-items: center;
margin-bottom: 10px;
justify-content: center;
}
.dropdown {
position: relative;
width: 100%;
}
.dropdown-button {
border: 1px solid #feb400;
border-radius: 8px;
background-color: #feb400;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
width: 100%;
height: 27px;
font-size: 14px;
font-weight: bold;
}
.dropdown-button:hover {
background-color: #feb400;
color: white;
}
.dropdown-content {
display: none;
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #fafafa;
position: absolute;
z-index: 1050 !important;
width: 720px;
}
.show {
display: block;
}
.remove-row-button {
background-color: white !important;
color: black !important;
border-color: transparent !important;
}
.remove-row-button:hover {
background-color: #f0f0f0 !important;
}
.remove-group-button {
background-color: white !important;
color: black !important;
border-color: transparent !important;
}
.remove-group-button:hover {
background-color: #f0f0f0 !important;
}
.add-button-container button {
background-color: white !important;
color: black !important;
border-color: transparent !important;
}
.add-button-container button:hover {
background-color: #f0f0f0 !important;
}
.add-filter-group-button {
background-color: white !important;
color: black !important;
border-color: transparent !important;
}
.add-filter-group-button:hover {
background-color: #f0f0f0 !important;
}
#result {
display: none;
}
</style>
<!-- custom logic -->
<script type="text/babel">
function CustomComponent() {
const data = UB.useData();
React.useEffect(() => {
if (data && data.groups) {
loadFilters(data);
}
}, [data]);
const toggleDropdown = () => {
document.getElementById('dropdown-content').classList.toggle('show');
};
const closeDropdown = () => {
document.getElementById('dropdown-content').classList.remove('show');
};
const fields = [
{ name: 'Contact Status', value: 'p.contact_status' },
{ name: 'First Name', value: 'pe.first_name' },
{ name: 'Last Name', value: 'pe.last_name' },
{ name: 'Full Name', value: 'pe.full_name' },
{ name: 'Language', value: 'c.language' },
{ name: 'Title', value: 'pe.title' },
{ name: 'Email', value: 'pe.email' },
{ name: 'Email Status', value: 'pe.email_status' },
{ name: 'Telephone', value: 'pe.telephone' },
{ name: 'Mobile', value: 'pe.mobile' },
{ name: 'Telephone Enriched', value: 'pe.gc_telephone' },
{ name: 'Mobile Enriched', value: 'pe.gc_mobile' },
{ name: 'Last Contacted By', value: 'p.last_contacted_by' },
{ name: 'Last Touchpoint', value: 'p.last_touchpoint', type: 'date' },
{ name: 'Company Name', value: 'o.name' },
{ name: 'Company Website', value: 'o.website' },
{ name: 'Company Phone', value: 'o.phone' },
{ name: 'Company Country', value: 'o.country' },
{ name: 'Company Industry', value: 'o.industry' },
{ name: 'Company Size', value: 'o.company_size' },
{ name: 'Campaign Title', value: 'c.title' },
{ name: 'Created At', value: 'p.created_at', type: 'date' },
{ name: 'Gender', value: 'pe.gender' },
];
const operators = ['==', '!=', '>', '<', '>=', '<=', 'includes', 'does not include'];
function addFilterGroup(groupData = null) {
const container = document.getElementById('filter-container');
const groupDiv = document.createElement('div');
groupDiv.className = 'filter-group';
const addButtonContainer = document.createElement('div');
addButtonContainer.className = 'add-button-container';
const addFilterButton = document.createElement('button');
addFilterButton.innerText = '+ Add filter';
addFilterButton.onclick = () => addFilterRow(groupDiv);
addButtonContainer.appendChild(addFilterButton);
groupDiv.appendChild(addButtonContainer);
const groupLogicButton = document.createElement('button');
groupLogicButton.className = 'group-logic-button';
groupLogicButton.innerText = groupData && groupData.groupLogic ? groupData.groupLogic : 'AND';
groupLogicButton.onclick = () => {
groupLogicButton.innerText = groupLogicButton.innerText === 'AND' ? 'OR' : 'AND';
};
groupDiv.appendChild(groupLogicButton);
const removeGroupButton = document.createElement('button');
removeGroupButton.className = 'remove-group-button';
removeGroupButton.innerText = 'Remove Group';
removeGroupButton.onclick = () => groupDiv.remove();
groupDiv.appendChild(removeGroupButton);
container.appendChild(groupDiv);
if (groupData && groupData.filters) {
groupData.filters.forEach(filter => addFilterRow(groupDiv, filter));
} else {
addFilterRow(groupDiv);
}
}
function addFilterRow(groupDiv, filterData = null) {
const rowDiv = document.createElement('div');
rowDiv.className = 'filter-row';
const fieldSelect = document.createElement('select');
fields.forEach(field => {
const option = document.createElement('option');
option.value = field.value;
option.innerText = field.name;
if (filterData && filterData.field === field.value) {
option.selected = true;
}
fieldSelect.appendChild(option);
});
const operatorSelect = document.createElement('select');
operators.forEach(op => {
const option = document.createElement('option');
option.value = op;
option.innerText = op;
if (filterData && filterData.operator === op) {
option.selected = true;
}
operatorSelect.appendChild(option);
});
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.placeholder = 'Value';
if (filterData && filterData.value) {
valueInput.value = filterData.value;
}
const initializeDatePicker = () => {
const selectedField = fields.find(f => f.value === fieldSelect.value);
if (selectedField && selectedField.type === 'date') {
flatpickr(valueInput, {
enableTime: true,
dateFormat: "Y-m-d H:i",
onChange: (selectedDates) => {
if (selectedDates.length > 0) {
valueInput.dataset.timestamp = selectedDates[0].getTime();
}
}
});
} else {
if (valueInput._flatpickr) {
valueInput._flatpickr.destroy();
}
}
};
fieldSelect.addEventListener('change', initializeDatePicker);
initializeDatePicker();
const logicButton = document.createElement('button');
logicButton.className = 'row-logic-button';
logicButton.innerText = 'AND';
logicButton.onclick = () => {
logicButton.innerText = logicButton.innerText === 'AND' ? 'OR' : 'AND';
};
const removeButton = document.createElement('button');
removeButton.className = 'remove-row-button';
removeButton.innerText = 'X';
removeButton.onclick = () => {
rowDiv.remove();
if (groupDiv.getElementsByClassName('filter-row').length === 0) {
groupDiv.remove();
}
};
rowDiv.appendChild(fieldSelect);
rowDiv.appendChild(operatorSelect);
rowDiv.appendChild(valueInput);
rowDiv.appendChild(logicButton);
rowDiv.appendChild(removeButton);
const buttons = groupDiv.querySelectorAll('.add-button-container');
const addButtonContainer = buttons[0];
groupDiv.insertBefore(rowDiv, addButtonContainer);
}
function generateFilter() {
const filterContainer = document.getElementById('filter-container');
const filterGroups = filterContainer.getElementsByClassName('filter-group');
const groups = Array.from(filterGroups).map((group, groupIndex) => {
const rows = group.getElementsByClassName('filter-row');
const groupLogic = group.querySelector('.group-logic-button').innerText;
const filters = Array.from(rows).map((row, index, array) => {
const field = row.querySelector('select:nth-of-type(1)').value;
const operator = row.querySelector('select:nth-of-type(2)').value;
const valueInput = row.querySelector('input[type="text"]');
let value = valueInput.value;
const selectedField = fields.find(f => f.value === field);
if (selectedField && selectedField.type === 'date' && valueInput.dataset.timestamp) {
value = valueInput.dataset.timestamp;
}
const rowLogic = row.querySelector('.row-logic-button').innerText;
return {
field: field,
operator: operator,
value: value,
logic: index < array.length - 1 ? rowLogic : null
};
});
return {
filters: filters,
groupLogic: groupIndex < filterGroups.length - 1 ? groupLogic : null
};
});
const result = {
groups: groups
};
UB.updateValue(result);
UB.triggerEvent(result);
}
function loadFilters(data) {
document.getElementById('filter-container').innerHTML = '';
data.groups.forEach(group => addFilterGroup(group));
}
function resetFilters() {
const filterContainer = document.getElementById('filter-container');
filterContainer.innerHTML = '';
}
return (
<div className="dropdown">
<button className="dropdown-button" onClick={toggleDropdown}>
+Filter
</button>
<div id="dropdown-content" className="dropdown-content">
<div className="filter-container">
<div className="filter-header">
<div>
<button className="add-filter-group-button" onClick={() => addFilterGroup()}>Add Filter Group</button>
</div>
<div>
<button className="add-filter-group-button" onClick={() => {resetFilters(); generateFilter();}}>Reset</button>
<button onClick={generateFilter}>Filter</button>
</div>
</div>
<div id="filter-container"></div>
<pre id="result" style={{ display: 'none' }}></pre>
</div>
</div>
</div>
);
}
const Component = UB.connectReactComponent(CustomComponent);
ReactDOM.render(<Component />, UB.container.querySelector('.root'));
// it's a good practice to destroy all resources you consumed in your custom component.
UB.onDestroy(() => ReactDOM.unmountComponentAtNode(UB.container.querySelector('.root')));
</script>