@@ -290,8 +290,8 @@ const handleError = (error) => {
290290 </div>
291291 </template>
292292
293- <!-- Custom user display -->
294- <template #user="{ user, index }">
293+ <!-- Custom user display with access to default Edit/Delete functions -->
294+ <template #user="{ user, index, editUser, deleteUser }">
295295 <div class="user-card">
296296 <div class="user-avatar">
297297 <img
@@ -309,14 +309,16 @@ const handleError = (error) => {
309309 </div>
310310
311311 <div class="user-actions">
312- <button @click="handleEdit(user)" class="edit-btn">
312+ <!-- Use the provided editUser and deleteUser functions -->
313+ <!-- These include permission checks and API calls automatically -->
314+ <button @click="editUser" class="edit-btn">
313315 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
314316 <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
315317 </svg>
316318 Edit
317319 </button>
318320
319- <button @click="confirmDelete(user) " class="delete-btn">
321+ <button @click="deleteUser " class="delete-btn">
320322 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
321323 <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
322324 </svg>
@@ -370,19 +372,13 @@ const showAddUser = ref(false)
370372
371373const handleEdit = (user) => {
372374 console.log('Edit user:', user)
373- // Implement edit logic
375+ // Implement edit logic (e.g., show edit form)
374376}
375377
376378const handleDelete = (user) => {
377379 console.log('User deleted:', user)
378- // Refresh list or show success message
379- }
380-
381- const confirmDelete = (user) => {
382- if (confirm(`Are you sure you want to delete ${user.name}?`)) {
383- // Call delete API
384- handleDelete(user)
385- }
380+ // Handle post-deletion logic (e.g., refresh list, show success message)
381+ // Note: The actual API call and confirmation dialog are handled by the deleteUser function
386382}
387383
388384const handleUserAdded = (userData) => {
@@ -591,6 +587,310 @@ const handleUserAdded = (userData) => {
591587</style>
592588```
593589
590+ ## Custom User List with Default Actions
591+
592+ This example shows how to create a completely custom user card layout while still using the default Edit and Delete functionality:
593+
594+ ``` vue
595+ <!-- components/CustomUserList.vue -->
596+ <template>
597+ <div class="custom-user-list">
598+ <NUsersList @edit-click="handleEdit" @delete="handleDelete">
599+ <!-- Custom title -->
600+ <template #title>
601+ <div class="list-header">
602+ <h1>Team Members</h1>
603+ <p>Manage your team members with custom styling</p>
604+ </div>
605+ </template>
606+
607+ <!-- Completely custom user card with default actions -->
608+ <template #user="{ user, index, editUser, deleteUser }">
609+ <div class="custom-user-card">
610+ <div class="user-header">
611+ <div class="user-avatar">
612+ <img
613+ :src="user.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=7c3aed&color=fff&size=64`"
614+ :alt="user.name"
615+ />
616+ </div>
617+ <div class="user-basic-info">
618+ <h3>{{ user.name }}</h3>
619+ <p class="user-email">{{ user.email }}</p>
620+ </div>
621+ <div class="user-status">
622+ <span class="status-indicator" :class="user.active ? 'active' : 'inactive'"></span>
623+ <span class="status-text">{{ user.active ? 'Active' : 'Inactive' }}</span>
624+ </div>
625+ </div>
626+
627+ <div class="user-details">
628+ <div class="detail-item">
629+ <span class="label">Role:</span>
630+ <span class="value role-badge" :class="`role-${user.role}`">
631+ {{ user.role }}
632+ </span>
633+ </div>
634+ <div class="detail-item">
635+ <span class="label">Joined:</span>
636+ <span class="value">{{ formatDate(user.created_at) }}</span>
637+ </div>
638+ <div class="detail-item">
639+ <span class="label">Last Login:</span>
640+ <span class="value">{{ formatDate(user.last_login) }}</span>
641+ </div>
642+ </div>
643+
644+ <div class="user-actions">
645+ <!-- These functions include permission checks and API calls -->
646+ <button @click="editUser" class="action-btn edit-btn">
647+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
648+ <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
649+ </svg>
650+ Edit Profile
651+ </button>
652+
653+ <button @click="deleteUser" class="action-btn delete-btn">
654+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
655+ <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
656+ </svg>
657+ Remove User
658+ </button>
659+ </div>
660+ </div>
661+ </template>
662+ </NUsersList>
663+ </div>
664+ </template>
665+
666+ <script setup>
667+ const handleEdit = (user) => {
668+ console.log('Edit user:', user)
669+ // Show edit form or navigate to edit page
670+ }
671+
672+ const handleDelete = (user) => {
673+ console.log('User deleted:', user)
674+ // Handle post-deletion logic (refresh list, show notification, etc.)
675+ }
676+
677+ const formatDate = (dateString) => {
678+ if (!dateString) return 'Never'
679+ return new Date(dateString).toLocaleDateString()
680+ }
681+ </script>
682+
683+ <style scoped>
684+ .custom-user-list {
685+ padding: 2rem;
686+ max-width: 1200px;
687+ margin: 0 auto;
688+ }
689+
690+ .list-header {
691+ text-align: center;
692+ margin-bottom: 3rem;
693+ }
694+
695+ .list-header h1 {
696+ color: #111827;
697+ font-size: 2.5rem;
698+ font-weight: 700;
699+ margin-bottom: 0.5rem;
700+ }
701+
702+ .list-header p {
703+ color: #6b7280;
704+ font-size: 1.125rem;
705+ }
706+
707+ .custom-user-card {
708+ background: white;
709+ border: 1px solid #e5e7eb;
710+ border-radius: 16px;
711+ padding: 2rem;
712+ margin-bottom: 1.5rem;
713+ transition: all 0.3s ease;
714+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
715+ }
716+
717+ .custom-user-card:hover {
718+ border-color: #7c3aed;
719+ box-shadow: 0 10px 15px -3px rgba(124, 58, 237, 0.1);
720+ transform: translateY(-2px);
721+ }
722+
723+ .user-header {
724+ display: flex;
725+ align-items: center;
726+ gap: 1.5rem;
727+ margin-bottom: 1.5rem;
728+ }
729+
730+ .user-avatar img {
731+ width: 64px;
732+ height: 64px;
733+ border-radius: 50%;
734+ object-fit: cover;
735+ border: 3px solid #f3f4f6;
736+ }
737+
738+ .user-basic-info {
739+ flex: 1;
740+ }
741+
742+ .user-basic-info h3 {
743+ font-size: 1.25rem;
744+ font-weight: 600;
745+ color: #111827;
746+ margin-bottom: 0.25rem;
747+ }
748+
749+ .user-email {
750+ color: #6b7280;
751+ font-size: 0.875rem;
752+ }
753+
754+ .user-status {
755+ display: flex;
756+ align-items: center;
757+ gap: 0.5rem;
758+ }
759+
760+ .status-indicator {
761+ width: 8px;
762+ height: 8px;
763+ border-radius: 50%;
764+ }
765+
766+ .status-indicator.active {
767+ background: #10b981;
768+ }
769+
770+ .status-indicator.inactive {
771+ background: #ef4444;
772+ }
773+
774+ .status-text {
775+ font-size: 0.875rem;
776+ color: #6b7280;
777+ font-weight: 500;
778+ }
779+
780+ .user-details {
781+ display: grid;
782+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
783+ gap: 1rem;
784+ margin-bottom: 1.5rem;
785+ padding: 1rem;
786+ background: #f9fafb;
787+ border-radius: 8px;
788+ }
789+
790+ .detail-item {
791+ display: flex;
792+ flex-direction: column;
793+ gap: 0.25rem;
794+ }
795+
796+ .label {
797+ font-size: 0.75rem;
798+ color: #6b7280;
799+ font-weight: 500;
800+ text-transform: uppercase;
801+ letter-spacing: 0.05em;
802+ }
803+
804+ .value {
805+ font-size: 0.875rem;
806+ color: #111827;
807+ font-weight: 500;
808+ }
809+
810+ .role-badge {
811+ display: inline-block;
812+ padding: 0.25rem 0.75rem;
813+ border-radius: 9999px;
814+ font-size: 0.75rem;
815+ font-weight: 600;
816+ text-transform: uppercase;
817+ width: fit-content;
818+ }
819+
820+ .role-admin {
821+ background: #fef3c7;
822+ color: #92400e;
823+ }
824+
825+ .role-user {
826+ background: #dbeafe;
827+ color: #1e40af;
828+ }
829+
830+ .role-manager {
831+ background: #e0e7ff;
832+ color: #5b21b6;
833+ }
834+
835+ .user-actions {
836+ display: flex;
837+ gap: 1rem;
838+ justify-content: flex-end;
839+ }
840+
841+ .action-btn {
842+ display: flex;
843+ align-items: center;
844+ gap: 0.5rem;
845+ padding: 0.75rem 1.5rem;
846+ border: 1px solid #d1d5db;
847+ border-radius: 8px;
848+ background: white;
849+ cursor: pointer;
850+ font-size: 0.875rem;
851+ font-weight: 500;
852+ transition: all 0.2s;
853+ }
854+
855+ .edit-btn:hover {
856+ border-color: #7c3aed;
857+ color: #7c3aed;
858+ background: #faf5ff;
859+ }
860+
861+ .delete-btn:hover {
862+ border-color: #ef4444;
863+ color: #ef4444;
864+ background: #fef2f2;
865+ }
866+
867+ /* Responsive design */
868+ @media (max-width: 768px) {
869+ .user-header {
870+ flex-direction: column;
871+ text-align: center;
872+ gap: 1rem;
873+ }
874+
875+ .user-details {
876+ grid-template-columns: 1fr;
877+ }
878+
879+ .user-actions {
880+ justify-content: center;
881+ }
882+ }
883+ </style>
884+ ```
885+
886+ ** Key Benefits of this approach:**
887+
888+ 1 . ** Complete UI Control** - You can design the user card exactly how you want
889+ 2 . ** Default Functionality** - Edit and Delete buttons work exactly like the default ones
890+ 3 . ** Permission Handling** - The ` editUser ` and ` deleteUser ` functions automatically check permissions
891+ 4 . ** API Integration** - Delete operations include confirmation dialogs and API calls
892+ 5 . ** Event Consistency** - All events are properly emitted to parent components
893+
594894## Custom Logout Component
595895
596896``` vue
0 commit comments