Skip to content

Commit 1ea4f61

Browse files
committed
feat: Extends NUsersList with customizable user actions
Allows customization of user display and actions within the NUsersList component. - Provides slots for customizing the user card content, including access to the default edit and delete functions (editUser, deleteUser). - Introduces editButton and deleteButton slots for customizing individual action buttons, enabling developers to override the default buttons with custom UI and logic while still leveraging the built-in permission checks and API calls. - Adds a new example demonstrating custom user list with default actions. This enhances the flexibility of the component, allowing developers to tailor the user interface to their specific needs while maintaining core functionality.
1 parent 07dd6a2 commit 1ea4f61

File tree

5 files changed

+505
-32
lines changed

5 files changed

+505
-32
lines changed

docs/examples/custom-components.md

Lines changed: 313 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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
371373
const handleEdit = (user) => {
372374
console.log('Edit user:', user)
373-
// Implement edit logic
375+
// Implement edit logic (e.g., show edit form)
374376
}
375377
376378
const 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
388384
const 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

Comments
 (0)