Provide a Bottom Sheet component
Prerequisites
- [x] I have searched for duplicate or closed feature requests
- [x] I have read the contributing guidelines
Proposal
I believe a Bottom Sheet component would be a valuable addition to Bootstrap’s component library.
For context, a Bottom Sheet is a mobile-only component that behaves like a sliding panel, which emerges from the bottom of the screen and can be dragged upwards. A well-known example of a Bottom Sheet is the one used in Google Maps. You can refer to the Material Design specification for more details.
Motivation and context
In today’s development environment, web applications need to be fully responsive, and sometimes they require mobile-specific features. The Bottom Sheet is a common and useful component, yet it can be challenging to implement from scratch.
Currently, there are very few vanilla JavaScript libraries that offer a reliable Bottom Sheet component. For those who use plain JavaScript or less popular frameworks, the available options are often poorly documented and/or not well maintained.
I used Modal to have a Bottom Sheet-kind-of-element with that:
.modal.fade.modal-fade--reverse .modal-dialog {
--modal-fade-transform: translate(0, 50px);
}
If that can help in any way!
Bottom-positioned offcanvas does work well for this task.
https://getbootstrap.com/docs/5.3/components/offcanvas/#placement
Yeah, offcanvas with bottom placement works ok, but it would be nicer if we can hold upper part and slide down and close the offcanvas (bottom sheet).
Yeah, offcanvas with bottom placement works ok, but it would be nicer if we can hold upper part and slide down and close the offcanvas (bottom sheet).
I don't think it's gonna happen anytime soon as it's kinda difficult to do that with non-native code.
@CyrilKrylatov No, ChatGPT help me to write some code and works ok, but an official component would be much better.
Close modal on drag
let startY = 0;
let currentY = 0;
let isDragging = false;
let modalElement = null;
$('body').on('touchstart', '.modal-header', function (e) {
startY = e.touches[0].clientY;
modalElement = $(this).closest('.modal')[0]; // Găsește modalul părinte
isDragging = true;
});
$('body').on('touchmove', function (e) {
if (!isDragging || !modalElement) return;
currentY = e.touches[0].clientY;
let translateY = Math.max(0, currentY - startY); // Evită mișcarea în sus
// Aplică efectul de tragere doar pe modal
modalElement.style.transform = `translateY(${translateY}px)`;
modalElement.style.transition = `none`; // Dezactivează animația în timpul dragului
});
$('body').on('touchend', function () {
if (!modalElement) return;
let threshold = 100; // Pragul de 100px pentru închidere
if (currentY - startY > threshold) {
let modalInstance = bootstrap.Modal.getInstance(modalElement);
if (modalInstance) {
modalInstance.hide();
}
} else {
// Resetează poziția modalului dacă nu a fost tras suficient
modalElement.style.transition = `transform 0.3s ease`;
modalElement.style.transform = `translateY(0)`;
}
isDragging = false;
modalElement = null;
});
Close modal on drag