1
0
mirror of https://github.com/tabler/tabler.git synced 2025-12-22 01:44:25 +04:00

feat: Add four new modals (new task, edit profile, confirm delete, change password) (#2529)

This commit is contained in:
Paweł Kuna
2025-11-15 13:42:27 +01:00
committed by GitHub
parent c7070180dc
commit b0fa6559da
15 changed files with 439 additions and 20 deletions

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": minor
---
Added Change Password modal with current password, new password with strength indicator, confirm password validation, and show/hide password toggles.

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": minor
---
Added Confirm Delete modal with warning icon, confirmation checkbox, and JavaScript validation to enable delete button only when confirmed.

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": minor
---
Added Edit Profile modal with avatar upload, personal information fields, social links, and date of birth.

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": minor
---
Added New Task modal with fields for task name, description, assigned user, priority, due date, and category tags.

View File

@@ -69,7 +69,7 @@ const submitForm = (form) => {
parseUrl()
// Elements
const form = document.querySelector("#offcanvasSettings")
const form = document.querySelector("#offcanvas-settings")
// Toggle form controls
if (form) {

View File

@@ -4,7 +4,7 @@ page-header: Modals
page-menu: base.modals
layout: default
permalink: modals.html
page-libs: [signature_pad, hugerte]
page-libs: [signature_pad, hugerte, litepicker]
---
<div class="card">
@@ -23,53 +23,78 @@ page-libs: [signature_pad, hugerte]
<a href="#modal-team" class="nav-link">Modal with simple form</a>
<a href="#modal-signature" class="nav-link">Modal with signature form</a>
<a href="#modal-new-email" class="nav-link">New email modal</a>
<a href="#modal-new-event" class="nav-link">New event modal</a>
<a href="#modal-new-task" class="nav-link">New task modal</a>
<a href="#modal-edit-profile" class="nav-link">Edit profile modal</a>
<a href="#modal-confirm-delete" class="nav-link">Confirm delete modal</a>
<a href="#modal-change-password" class="nav-link">Change password modal</a>
</div>
</div>
<div class="col">
<div class="space-y-6">
<div>
<h3>Simple modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="simple" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="simple" inline show %}
</div>
<div>
<h3>Large modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="large" size="lg" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="large" size="lg" inline show %}
</div>
<div>
<h3>Small modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="small" size="sm" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="small" size="sm" inline show %}
</div>
<div>
<h3>Full width modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="full-width" size="full-width" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="full-width" size="full-width" inline show %}
</div>
<div>
<h3>Scrollable modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="scrollable" scrollable=true style="max-height: 30rem" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="scrollable" scrollable=true style="max-height: 30rem" inline show %}
</div>
<div>
<h3>Modal with form</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="report" size="lg" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="report" size="lg" inline show %}
</div>
<div>
<h3>Success modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="success" size="sm" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="success" size="sm" inline show %}
</div>
<div>
<h3>Danger modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="danger" size="sm" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="danger" size="sm" inline show %}
</div>
<div>
<h3>Modal with simple form</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="team" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="team" inline show %}
</div>
<div>
<h3>Modal with signature form</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="signature" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="signature" inline show %}
</div>
<div>
<h3>New email modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="new-email" inline %}
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="new-email" inline show %}
</div>
<div>
<h3>New event modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="new-event" inline show %}
</div>
<div>
<h3>New task modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="new-task" inline show %}
</div>
<div>
<h3>Edit profile modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="edit-profile" size="lg" inline show %}
</div>
<div>
<h3>Confirm delete modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="confirm-delete" size="sm" inline show %}
</div>
<div>
<h3>Change password modal</h3>
{% include "ui/modal.html" class="position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0" modal-id="change-password" inline show %}
</div>
</div>
</div>

View File

@@ -80,7 +80,7 @@
<!-- BEGIN NAVBAR NAV -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="#" data-bs-toggle="offcanvas" data-bs-target="#offcanvasSettings">
<a class="nav-link" href="#" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-settings">
<span class="badge badge-sm bg-red text-red-fg">New</span>
<span class="nav-link-icon d-md-none d-lg-inline-block">
{% include "ui/icon.html" icon="settings" %}

View File

@@ -0,0 +1,138 @@
<div class="modal-header">
<h4 class="modal-title" id="password-modal-label">Change password</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-4">
<label class="form-label" for="password-current">Current password</label>
{% include "ui/form/input-group.html" type="password" id="password-current" placeholder="Enter your current password" append-button="eye:Show password" flat=true %}
</div>
<div class="mb-4">
<label class="form-label" for="password-new">New password</label>
{% include "ui/form/input-group.html" type="password" id="password-new" placeholder="Enter new password" append-button="eye:Show password" flat=true %}
<small class="form-hint">
Your password must be 8-20 characters long, contain letters and numbers, and must not contain
spaces, special characters, or emoji.
</small>
<div class="mt-2">
<div class="progress" style="height: 4px;">
<div class="progress-bar" id="password-strength" role="progressbar" style="width: 0%"></div>
</div>
<div class="text-secondary text-xs mt-1" id="password-strength-text"></div>
</div>
</div>
<div class="mb-4">
<label class="form-label" for="password-confirm">Confirm new password</label>
{% include "ui/form/input-group.html" type="password" id="password-confirm" placeholder="Confirm your new password" append-button="eye:Show password" flat=true %}
<div class="invalid-feedback d-none" id="password-match-error">
Passwords do not match.
</div>
</div>
{% include "ui/button.html" type="submit" text="Update password" color="primary" block=true class="mt-4" %}
</form>
</div>
{% capture_script %}
<script>
document.addEventListener("DOMContentLoaded", function () {
function setupPasswordToggle(inputId) {
const input = document.getElementById(inputId);
if (!input) return;
const inputGroup = input.closest('.input-group');
if (!inputGroup) return;
const toggleLink = inputGroup.querySelector('a.link-secondary');
if (!toggleLink) return;
toggleLink.addEventListener('click', function(e) {
e.preventDefault();
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
// Update tooltip text
const tooltipText = isPassword ? 'Hide password' : 'Show password';
this.setAttribute('title', tooltipText);
this.setAttribute('data-bs-original-title', tooltipText);
// Update icon (simple approach - toggle classes if needed)
const svg = this.querySelector('svg');
if (svg) {
const use = svg.querySelector('use');
if (use) {
use.setAttribute('href', isPassword ? '#icon-eye-off' : '#icon-eye');
}
}
});
}
setupPasswordToggle('password-current');
setupPasswordToggle('password-new');
setupPasswordToggle('password-confirm');
const newPasswordInput = document.getElementById('password-new');
const strengthBar = document.getElementById('password-strength');
const strengthText = document.getElementById('password-strength-text');
if (newPasswordInput && strengthBar && strengthText) {
newPasswordInput.addEventListener('input', function() {
const password = this.value;
let strength = 0;
let strengthLabel = '';
if (password.length >= 8) strength++;
if (password.length >= 12) strength++;
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
if (/\d/.test(password)) strength++;
if (/[^a-zA-Z0-9]/.test(password)) strength++;
const percentage = (strength / 5) * 100;
strengthBar.style.width = percentage + '%';
if (strength <= 2) {
strengthBar.className = 'progress-bar bg-danger';
strengthLabel = 'Weak';
} else if (strength <= 3) {
strengthBar.className = 'progress-bar bg-warning';
strengthLabel = 'Fair';
} else if (strength <= 4) {
strengthBar.className = 'progress-bar bg-info';
strengthLabel = 'Good';
} else {
strengthBar.className = 'progress-bar bg-success';
strengthLabel = 'Strong';
}
strengthText.textContent = password ? strengthLabel : '';
});
}
const confirmPasswordInput = document.getElementById('password-confirm');
const matchError = document.getElementById('password-match-error');
if (newPasswordInput && confirmPasswordInput && matchError) {
function validateMatch() {
const newPassword = newPasswordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (confirmPassword && newPassword !== confirmPassword) {
confirmPasswordInput.classList.add('is-invalid');
matchError.classList.remove('d-none');
} else {
confirmPasswordInput.classList.remove('is-invalid');
matchError.classList.add('d-none');
}
}
newPasswordInput.addEventListener('input', validateMatch);
confirmPasswordInput.addEventListener('input', validateMatch);
}
});
</script>
{% endcapture_script %}

View File

@@ -0,0 +1,56 @@
{% include "ui/modal/close.html" %}
<div class="modal-status bg-danger"></div>
<div class="modal-body text-center py-4">
{% include "ui/icon.html" icon="alert-triangle" color="danger" size="lg" class="mb-2" %}
<h3 id="confirm-delete-title">Are you sure?</h3>
<div class="text-secondary mb-4">
<p id="confirm-delete-message">Do you really want to delete this item? This action cannot be undone.</p>
<div id="confirm-delete-items" class="text-start d-none">
<div class="card card-sm mt-3">
<div class="card-body">
<div class="fw-bold mb-2">Items to be deleted:</div>
<ul class="list-unstyled mb-0" id="confirm-delete-list">
<li>• Item 1</li>
<li>• Item 2</li>
<li>• Item 3</li>
</ul>
</div>
</div>
</div>
</div>
<label class="form-check text-start">
<input class="form-check-input" type="checkbox" id="confirm-delete-checkbox">
<span class="form-check-label">I understand this action cannot be undone</span>
</label>
</div>
<div class="modal-footer">
<div class="w-100">
<div class="row">
<div class="col">{% include "ui/button.html" dismiss=true text="Cancel" block=true %}</div>
<div class="col">
<button type="button" class="btn btn-danger w-100" data-bs-dismiss="modal" id="confirm-delete-button" disabled>
Delete
</button>
</div>
</div>
</div>
</div>
{% capture_script %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const checkbox = document.getElementById("confirm-delete-checkbox");
const deleteButton = document.getElementById("confirm-delete-button");
if (checkbox && deleteButton) {
checkbox.addEventListener("change", function() {
deleteButton.disabled = !this.checked;
});
}
});
</script>
{% endcapture_script %}

View File

@@ -0,0 +1,92 @@
<div class="modal-header">
<h4 class="modal-title" id="profile-modal-label">Edit profile</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-4">
<div class="row align-items-end">
<div class="col-auto">
{% include "ui/avatar-upload.html" class="rounded" size="lg" %}
</div>
<div class="col">
<label class="form-label">Avatar</label>
<div class="text-secondary">Click to upload a new avatar</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="profile-first-name">First name</label>
<input class="form-control" id="profile-first-name" type="text" placeholder="John">
</div>
</div>
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="profile-last-name">Last name</label>
<input class="form-control" id="profile-last-name" type="text" placeholder="Doe">
</div>
</div>
</div>
<div class="mb-4">
<label class="form-label" for="profile-email">Email</label>
<input class="form-control" id="profile-email" type="email" placeholder="john.doe@example.com">
</div>
<div class="mb-4">
<label class="form-label" for="profile-phone">Phone</label>
<input class="form-control" id="profile-phone" type="tel" placeholder="+1 (555) 123-4567">
</div>
<div class="mb-4">
<label class="form-label" for="profile-bio">Bio</label>
<textarea class="form-control" id="profile-bio" rows="4" placeholder="Tell us about yourself..."></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="profile-location">Location</label>
<input class="form-control" id="profile-location" type="text" placeholder="City, Country">
</div>
</div>
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="profile-birthdate">Date of birth</label>
{% include "ui/datepicker.html" layout="icon" id="profile-birthdate" %}
</div>
</div>
</div>
<div class="mb-4">
<label class="form-label">Social links</label>
<div class="row g-2">
<div class="col-12">
<div class="input-group">
<span class="input-group-text">{% include "ui/icon.html" icon="brand-twitter" %}</span>
<input type="text" class="form-control" placeholder="twitter.com/username">
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">{% include "ui/icon.html" icon="brand-github" %}</span>
<input type="text" class="form-control" placeholder="github.com/username">
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">{% include "ui/icon.html" icon="brand-linkedin" %}</span>
<input type="text" class="form-control" placeholder="linkedin.com/in/username">
</div>
</div>
</div>
</div>
{% include "ui/button.html" type="submit" text="Save changes" color="primary" block=true class="mt-4" %}
</form>
</div>

View File

@@ -0,0 +1,33 @@
<div class="modal-header">
<h4 class="modal-title" id="event-modal-label">New event</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-4">
<label class="form-label" for="event-title">Title</label>
<input class="form-control" id="event-title" type="text" placeholder="Event title">
</div>
<div class="mb-4">
<label class="form-label" for="event-description">Description</label>
<textarea class="form-control" id="event-description" rows="3" placeholder="Event description"></textarea>
</div>
<div class="row">
<div class="col">
<div class="mb-4">
<label class="form-label" for="event-start">Start</label>
{% include "ui/datepicker.html" layout="icon" id="event-start" %}
</div>
</div>
<div class="col">
<div class="mb-4">
<label class="form-label" for="event-end">End</label>
{% include "ui/datepicker.html" layout="icon" id="event-end" %}
</div>
</div>
</div>
{% include "ui/button.html" type="submit" text="Create event" color="primary" block=true class="mt-4" %}
</form>
</div>

View File

@@ -0,0 +1,51 @@
<div class="modal-header">
<h4 class="modal-title" id="task-modal-label">New task</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-4">
<label class="form-label" for="task-name">Task name</label>
<input class="form-control" id="task-name" type="text" placeholder="Enter task name">
</div>
<div class="mb-4">
<label class="form-label" for="task-description">Description</label>
<textarea class="form-control" id="task-description" rows="3" placeholder="Enter task description"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="task-assigned">Assigned to</label>
{% include "ui/select.html" key="people" id="task-assigned" indicator="avatar" %}
</div>
</div>
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="task-priority">Priority</label>
<select class="form-select" id="task-priority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="task-due-date">Due date</label>
{% include "ui/datepicker.html" layout="icon" id="task-due-date" %}
</div>
</div>
<div class="col-md-6">
<div class="mb-4">
<label class="form-label" for="task-category">Category / Tags</label>
{% include "ui/select.html" key="tags" id="task-category" %}
</div>
</div>
</div>
{% include "ui/button.html" type="submit" text="Create task" color="primary" block=true class="mt-4" %}
</form>
</div>

View File

@@ -2,7 +2,7 @@
<div class="modal-body">
<div class="row mb-3 align-items-end">
<div class="col-auto">
{% include "ui/avatar-upload.html" class="rounded" %}
{% include "ui/avatar-upload.html" class="rounded" size="xl" %}
</div>
<div class="col">
<label class="form-label">Name</label>

View File

@@ -1,11 +1,11 @@
<div class="settings">
<a href="#" class="btn btn-floating btn-icon btn-primary" data-bs-toggle="offcanvas" data-bs-target="#offcanvasSettings" aria-controls="offcanvasSettings" aria-label="Theme Settings">
<a href="#" class="btn btn-floating btn-icon btn-primary" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-settings" aria-controls="offcanvas-settings" aria-label="Theme Settings">
{% include "ui/icon.html" icon="brush" %}
</a>
<form class="offcanvas offcanvas-start offcanvas-narrow" tabindex="-1" id="offcanvasSettings" role="dialog" aria-modal="true" aria-labelledby="offcanvasSettingsTitle">
<form class="offcanvas offcanvas-start offcanvas-narrow" tabindex="-1" id="offcanvas-settings" role="dialog" aria-modal="true" aria-labelledby="offcanvas-settings-title">
<div class="offcanvas-header">
<h2 class="offcanvas-title" id="offcanvasSettingsTitle">Theme Settings</h2>
<h2 class="offcanvas-title" id="offcanvas-settings-title">Theme Settings</h2>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body d-flex flex-column">
@@ -124,7 +124,7 @@
}
var url = new URL(window.location)
var form = document.getElementById("offcanvasSettings")
var form = document.getElementById("offcanvas-settings")
var resetButton = document.getElementById("reset-changes")
var checkItems = function () {

View File

@@ -4,7 +4,7 @@
{% assign inline = include.inline | default: false %}
{% capture_modal inline %}
<div class="modal modal-blur fade{% if include.class %} {{ include.class }}{% endif %}" {% if include.style %} style="{{ include.style }}"{% endif %} id="modal-{{ modal-id }}" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal modal-blur fade{% if include.class %} {{ include.class }}{% endif %}{% if include.show %} show{% endif %}" {% if include.style %} style="{{ include.style }}"{% endif %} id="modal-{{ modal-id }}" tabindex="-1" role="dialog"{% if include.show %} aria-hidden="false"{% else %} aria-hidden="true"{% endif %}>
<div class="modal-dialog{% if size%} modal-{{ size }}{% endif %}{% unless include.top %} modal-dialog-centered{% endunless %}{% if include.scrollable %} modal-dialog-scrollable{% endif %}" role="document">
<div class="modal-content">
{% include "parts/modals/{{ modal-id }}.html" %}