Refactor ChoresPage and GroupDetailPage for improved UI and functionality
- Enhanced the ChoresPage by refining button attributes for accessibility and improving layout consistency. - Updated the GroupDetailPage to include a more interactive member avatar list and streamlined invite member functionality. - Introduced new styles for better visual hierarchy and user experience across both pages. - Implemented click-outside functionality for member menus and invite UI to enhance usability.
This commit is contained in:
parent
77178cc67e
commit
d6c7fde40c
@ -36,12 +36,14 @@
|
||||
<div class="header-right">
|
||||
<div class="neo-view-toggle">
|
||||
<button class="neo-toggle-btn" :class="{ active: viewMode === 'calendar' }" @click="viewMode = 'calendar'"
|
||||
:disabled="isLoading" :aria-pressed="viewMode === 'calendar'" :aria-label="t('choresPage.viewToggle.calendarLabel')">
|
||||
:disabled="isLoading" :aria-pressed="viewMode === 'calendar'"
|
||||
:aria-label="t('choresPage.viewToggle.calendarLabel')">
|
||||
<span class="material-icons">calendar_month</span>
|
||||
<span class="btn-text hide-text-on-mobile">{{ t('choresPage.viewToggle.calendarText') }}</span>
|
||||
</button>
|
||||
<button class="neo-toggle-btn" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'"
|
||||
:disabled="isLoading" :aria-pressed="viewMode === 'list'" :aria-label="t('choresPage.viewToggle.listLabel')">
|
||||
:disabled="isLoading" :aria-pressed="viewMode === 'list'"
|
||||
:aria-label="t('choresPage.viewToggle.listLabel')">
|
||||
<span class="material-icons">view_list</span>
|
||||
<span class="btn-text hide-text-on-mobile">{{ t('choresPage.viewToggle.listText') }}</span>
|
||||
</button>
|
||||
@ -86,7 +88,8 @@
|
||||
<div class="day-header">
|
||||
<span class="day-number">{{ day.date.getDate() }}</span>
|
||||
<button v-if="!day.isOtherMonth" class="add-chore-indicator"
|
||||
@click.stop="openCreateChoreModal(null, day.date)" :aria-label="t('choresPage.calendar.addChoreToDayLabel')">
|
||||
@click.stop="openCreateChoreModal(null, day.date)"
|
||||
:aria-label="t('choresPage.calendar.addChoreToDayLabel')">
|
||||
<span class="material-icons">add_circle_outline</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -123,7 +126,8 @@
|
||||
<h3>{{ chore.name }}</h3>
|
||||
<div class="chore-tags">
|
||||
<span class="chore-type-tag" :class="chore.type">
|
||||
{{ chore.type === 'personal' ? t('choresPage.listView.choreTypePersonal') : getGroupName(chore.group_id) || t('choresPage.listView.choreTypeGroupFallback') }}
|
||||
{{ chore.type === 'personal' ? t('choresPage.listView.choreTypePersonal') :
|
||||
getGroupName(chore.group_id) || t('choresPage.listView.choreTypeGroupFallback') }}
|
||||
</span>
|
||||
<span v-if="!chore.is_completed" class="chore-frequency-tag" :class="chore.frequency">
|
||||
{{ formatFrequency(chore.frequency) }}
|
||||
@ -137,7 +141,8 @@
|
||||
</div>
|
||||
<div v-else class="chore-completed-date">
|
||||
<span class="material-icons">check_circle_outline</span>
|
||||
{{ t('choresPage.listView.completedDatePrefix') }} {{ formatDate(chore.completed_at || chore.next_due_date) }}
|
||||
{{ t('choresPage.listView.completedDatePrefix') }} {{ formatDate(chore.completed_at ||
|
||||
chore.next_due_date) }}
|
||||
</div>
|
||||
<div v-if="chore.description" class="chore-description">
|
||||
{{ chore.description }}
|
||||
@ -153,11 +158,14 @@
|
||||
:title="t('choresPage.listView.actions.undoTitle')">
|
||||
<span class="material-icons">undo</span> {{ t('choresPage.listView.actions.undoText') }}
|
||||
</button>
|
||||
<button class="btn btn-icon" @click="openEditChoreModal(chore)" :title="t('choresPage.listView.actions.editTitle')" :aria-label="t('choresPage.listView.actions.editLabel')">
|
||||
<button class="btn btn-icon" @click="openEditChoreModal(chore)"
|
||||
:title="t('choresPage.listView.actions.editTitle')"
|
||||
:aria-label="t('choresPage.listView.actions.editLabel')">
|
||||
<span class="material-icons">edit</span>
|
||||
<span class="btn-text hide-text-on-mobile">{{ t('choresPage.listView.actions.editText') }}</span>
|
||||
</button>
|
||||
<button class="btn btn-icon btn-danger-icon" @click="confirmDeleteChore(chore)" :title="t('choresPage.listView.actions.deleteTitle')"
|
||||
<button class="btn btn-icon btn-danger-icon" @click="confirmDeleteChore(chore)"
|
||||
:title="t('choresPage.listView.actions.deleteTitle')"
|
||||
:aria-label="t('choresPage.listView.actions.deleteLabel')">
|
||||
<span class="material-icons">delete</span>
|
||||
<span class="btn-text hide-text-on-mobile">{{ t('choresPage.listView.actions.deleteText') }}</span>
|
||||
@ -169,7 +177,8 @@
|
||||
<div v-if="!isLoading && filteredChores.length === 0" class="empty-state">
|
||||
<span class="material-icons empty-icon"> Rtask_alt</span>
|
||||
<p>{{ t('choresPage.listView.emptyState.message') }}</p>
|
||||
<button v-if="activeView !== 'all'" class="btn btn-sm btn-outline" @click="activeView = 'all'">{{ t('choresPage.listView.emptyState.viewAllButton') }}</button>
|
||||
<button v-if="activeView !== 'all'" class="btn btn-sm btn-outline" @click="activeView = 'all'">{{
|
||||
t('choresPage.listView.emptyState.viewAllButton') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -178,17 +187,19 @@
|
||||
aria-modal="true" :aria-labelledby="isEditing ? 'editChoreModalTitle' : 'newChoreModalTitle'">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h3 :id="isEditing ? 'editChoreModalTitle' : 'newChoreModalTitle'">{{ isEditing ? t('choresPage.choreModal.editTitle') : t('choresPage.choreModal.newTitle')
|
||||
<h3 :id="isEditing ? 'editChoreModalTitle' : 'newChoreModalTitle'">{{ isEditing ?
|
||||
t('choresPage.choreModal.editTitle') : t('choresPage.choreModal.newTitle')
|
||||
}}</h3>
|
||||
<button class="btn btn-icon" @click="showChoreModal = false" :aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<button class="btn btn-icon" @click="showChoreModal = false"
|
||||
:aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<form @submit.prevent="onSubmit" class="modal-form">
|
||||
<div class="form-group">
|
||||
<label for="name">{{ t('choresPage.choreModal.nameLabel') }}</label>
|
||||
<input id="name" v-model="choreForm.name" type="text" class="form-input" :placeholder="t('choresPage.choreModal.namePlaceholder')"
|
||||
required />
|
||||
<input id="name" v-model="choreForm.name" type="text" class="form-input"
|
||||
:placeholder="t('choresPage.choreModal.namePlaceholder')" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@ -244,16 +255,19 @@
|
||||
<div class="form-group">
|
||||
<label for="dueDate">{{ t('choresPage.choreModal.dueDateLabel') }}</label>
|
||||
<div class="quick-due-dates">
|
||||
<button type="button" class="btn btn-sm btn-outline" @click="setQuickDueDate('today')">{{ t('choresPage.choreModal.quickDueDateToday') }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline"
|
||||
@click="setQuickDueDate('tomorrow')">{{ t('choresPage.choreModal.quickDueDateTomorrow') }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" @click="setQuickDueDate('next_week')">{{ t('choresPage.choreModal.quickDueDateNextWeek') }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" @click="setQuickDueDate('today')">{{
|
||||
t('choresPage.choreModal.quickDueDateToday') }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" @click="setQuickDueDate('tomorrow')">{{
|
||||
t('choresPage.choreModal.quickDueDateTomorrow') }}</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" @click="setQuickDueDate('next_week')">{{
|
||||
t('choresPage.choreModal.quickDueDateNextWeek') }}</button>
|
||||
</div>
|
||||
<input id="dueDate" v-model="choreForm.next_due_date" type="date" class="form-input" required />
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-neutral" @click="showChoreModal = false">{{ t('choresPage.choreModal.cancelButton') }}</button>
|
||||
<button type="button" class="btn btn-neutral" @click="showChoreModal = false">{{
|
||||
t('choresPage.choreModal.cancelButton') }}</button>
|
||||
<button type="submit" class="btn btn-primary">{{ t('choresPage.choreModal.saveButton') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -266,7 +280,8 @@
|
||||
<div class="modal-container delete-confirm">
|
||||
<div class="modal-header">
|
||||
<h3 id="deleteDialogTitle">{{ t('choresPage.deleteDialog.title') }}</h3>
|
||||
<button class="btn btn-icon" @click="showDeleteDialog = false" :aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<button class="btn btn-icon" @click="showDeleteDialog = false"
|
||||
:aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -274,7 +289,8 @@
|
||||
<p>{{ t('choresPage.deleteDialog.confirmationText') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-neutral" @click="showDeleteDialog = false">{{ t('choresPage.choreModal.cancelButton') }}</button>
|
||||
<button class="btn btn-neutral" @click="showDeleteDialog = false">{{ t('choresPage.choreModal.cancelButton')
|
||||
}}</button>
|
||||
<button class="btn btn-danger" @click="deleteChore">{{ t('choresPage.deleteDialog.deleteButton') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -286,7 +302,8 @@
|
||||
<div class="modal-container shortcuts-modal">
|
||||
<div class="modal-header">
|
||||
<h3 id="shortcutsModalTitle">{{ t('choresPage.shortcutsModal.title') }}</h3>
|
||||
<button class="btn btn-icon" @click="showShortcutsModal = false" :aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<button class="btn btn-icon" @click="showShortcutsModal = false"
|
||||
:aria-label="t('choresPage.choreModal.closeButtonLabel')">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -1296,7 +1313,7 @@ onBeforeUnmount(() => {
|
||||
/* Adjusted gap */
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: white;
|
||||
background: rgb(255, 248, 240);
|
||||
border-radius: 18px;
|
||||
border: 3px solid #111;
|
||||
box-shadow: 6px 6px 0 #111;
|
||||
@ -1372,14 +1389,14 @@ onBeforeUnmount(() => {
|
||||
|
||||
.neo-tab-btn.active {
|
||||
background: #111;
|
||||
color: white;
|
||||
color: rgb(255, 248, 240);
|
||||
box-shadow: 3px 3px 0 #111;
|
||||
/* Ensure active shadow is consistent */
|
||||
}
|
||||
|
||||
.neo-tab-count {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
color: rgb(255, 248, 240);
|
||||
/* Assuming active tab has dark background */
|
||||
padding: 0.1em 0.5em;
|
||||
border-radius: 1rem;
|
||||
@ -1439,14 +1456,13 @@ onBeforeUnmount(() => {
|
||||
|
||||
.neo-toggle-btn.active {
|
||||
background: #111;
|
||||
color: white;
|
||||
color: rgb(255, 248, 240);
|
||||
}
|
||||
|
||||
/* Action button styles */
|
||||
.neo-action-button {
|
||||
background: #fff;
|
||||
background: rgb(255, 178, 107);
|
||||
border: 3px solid #111;
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem 1rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
@ -1486,7 +1502,7 @@ onBeforeUnmount(() => {
|
||||
margin: 2rem 0;
|
||||
border: 3px solid #111;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
background: rgb(255, 248, 240);
|
||||
box-shadow: 6px 6px 0 #111;
|
||||
}
|
||||
|
||||
@ -1670,13 +1686,13 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border-top-color: #fff;
|
||||
border-top-color: rgb(255, 248, 240);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calendar View Styles */
|
||||
.calendar-view {
|
||||
background: white;
|
||||
background: rgb(255, 248, 240);
|
||||
border-radius: 18px;
|
||||
border: 3px solid #111;
|
||||
box-shadow: 6px 6px 0 #111;
|
||||
|
@ -9,39 +9,47 @@
|
||||
</template>
|
||||
</VAlert>
|
||||
<div v-else-if="group">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<VHeading :level="1" :text="group.name" />
|
||||
<!-- Potential global actions here -->
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<VHeading :level="1" :text="group.name" class="header-title-text" />
|
||||
<div class="member-avatar-list">
|
||||
<div ref="avatarsContainerRef" class="member-avatars">
|
||||
<div v-for="member in group.members" :key="member.id" class="member-avatar">
|
||||
<div @click="toggleMemberMenu(member.id)" class="avatar-circle" :title="member.email">
|
||||
{{ member.email.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
|
||||
<div class="neo-section-container">
|
||||
<div class="neo-grid">
|
||||
<!-- Group Members Section -->
|
||||
<div class="neo-section">
|
||||
<VHeading :level="3" class="neo-section-header">{{ t('groupDetailPage.members.title') }}</VHeading>
|
||||
<VList v-if="group.members && group.members.length > 0">
|
||||
<VListItem v-for="member in group.members" :key="member.id" class="flex justify-between items-center">
|
||||
<div class="neo-member-info">
|
||||
<span class="neo-member-name">{{ member.email }}</span>
|
||||
<div v-show="activeMemberMenu === member.id" ref="memberMenuRef" class="member-menu" @click.stop>
|
||||
<div class="popup-header">
|
||||
<span class="font-semibold truncate">{{ member.email }}</span>
|
||||
<VButton variant="neutral" size="sm" :icon-only="true" iconLeft="x" @click="activeMemberMenu = null"
|
||||
aria-label="Close menu" />
|
||||
</div>
|
||||
<div class="member-menu-content">
|
||||
<VBadge :text="member.role || t('groupDetailPage.members.defaultRole')"
|
||||
:variant="member.role?.toLowerCase() === 'owner' ? 'primary' : 'secondary'" />
|
||||
</div>
|
||||
<VButton v-if="canRemoveMember(member)" variant="danger" size="sm" @click="removeMember(member.id)"
|
||||
:disabled="removingMember === member.id">
|
||||
<VSpinner v-if="removingMember === member.id" size="sm" /> {{
|
||||
t('groupDetailPage.members.removeButton') }}
|
||||
<VButton v-if="canRemoveMember(member)" variant="danger" size="sm" class="w-full text-left"
|
||||
@click="removeMember(member.id)" :disabled="removingMember === member.id">
|
||||
<VSpinner v-if="removingMember === member.id" size="sm" class="mr-1" />
|
||||
{{ t('groupDetailPage.members.removeButton') }}
|
||||
</VButton>
|
||||
</VListItem>
|
||||
</VList>
|
||||
<div v-else class="text-center py-4">
|
||||
<VIcon name="users" size="lg" class="opacity-50 mb-2" />
|
||||
<p>{{ t('groupDetailPage.members.emptyState') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button ref="addMemberButtonRef" @click="toggleInviteUI" class="add-member-btn"
|
||||
:aria-label="t('groupDetailPage.invites.title')">
|
||||
<!-- <VIcon name="plus" size="md" /> -->
|
||||
+
|
||||
</button>
|
||||
|
||||
<!-- Invite Members Section -->
|
||||
<div class="neo-section">
|
||||
<VHeading :level="3" class="neo-section-header">{{ t('groupDetailPage.invites.title') }}</VHeading>
|
||||
<!-- Invite Members Popup -->
|
||||
<div v-show="showInviteUI" ref="inviteUIRef" class="invite-popup">
|
||||
<div class="popup-header">
|
||||
<VHeading :level="3" class="!m-0 !p-0 !border-none">{{ t('groupDetailPage.invites.title') }}
|
||||
</VHeading>
|
||||
<VButton variant="neutral" size="sm" :icon-only="true" iconLeft="x" @click="showInviteUI = false"
|
||||
aria-label="Close invite" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 my-2">Invite new members by generating a shareable code.</p>
|
||||
<VButton variant="primary" class="w-full" @click="generateInviteCode" :disabled="generatingInvite">
|
||||
<VSpinner v-if="generatingInvite" size="sm" /> {{ inviteCode ?
|
||||
t('groupDetailPage.invites.regenerateButton') :
|
||||
@ -58,15 +66,13 @@
|
||||
<p v-if="copySuccess" class="text-sm text-green-600 mt-1">{{ t('groupDetailPage.invites.copySuccess') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="text-center py-4 mt-3">
|
||||
<VIcon name="link" size="lg" class="opacity-50 mb-2" />
|
||||
<p>{{ t('groupDetailPage.invites.emptyState') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="neo-section-container">
|
||||
<!-- Lists Section -->
|
||||
<div class="mt-4 neo-section">
|
||||
<div class="neo-section">
|
||||
<VHeading :level="3" class="neo-section-header">{{ t('groupDetailPage.lists.title') }}</VHeading>
|
||||
<ListsPage :group-id="groupId" />
|
||||
</div>
|
||||
@ -155,6 +161,7 @@ import VBadge from '@/components/valerie/VBadge.vue';
|
||||
import VInput from '@/components/valerie/VInput.vue';
|
||||
import VFormField from '@/components/valerie/VFormField.vue';
|
||||
import VIcon from '@/components/valerie/VIcon.vue';
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -186,6 +193,21 @@ const inviteExpiresAt = ref<string | null>(null);
|
||||
const generatingInvite = ref(false);
|
||||
const copySuccess = ref(false);
|
||||
const removingMember = ref<number | null>(null);
|
||||
const showInviteUI = ref(false);
|
||||
const activeMemberMenu = ref<number | null>(null);
|
||||
|
||||
const memberMenuRef = ref(null)
|
||||
const inviteUIRef = ref(null)
|
||||
const addMemberButtonRef = ref(null)
|
||||
const avatarsContainerRef = ref(null)
|
||||
|
||||
onClickOutside(memberMenuRef, () => {
|
||||
activeMemberMenu.value = null
|
||||
}, { ignore: [avatarsContainerRef] })
|
||||
|
||||
onClickOutside(inviteUIRef, () => {
|
||||
showInviteUI.value = false
|
||||
}, { ignore: [addMemberButtonRef] })
|
||||
|
||||
// groupId is directly from props.id now, which comes from the route path param
|
||||
const groupId = computed(() => props.id);
|
||||
@ -286,9 +308,10 @@ const copyInviteCodeHandler = async () => {
|
||||
};
|
||||
|
||||
const canRemoveMember = (member: GroupMember): boolean => {
|
||||
// Only allow removing members if the current user is the owner
|
||||
// and the member is not the owner themselves
|
||||
return group.value?.members?.some(m => m.role === 'owner' && m.id === member.id) === false;
|
||||
// Simplification: For now, assume a user with role 'owner' can remove anyone but another owner.
|
||||
// A real implementation would check the current user's ID against the member to prevent self-removal.
|
||||
const isOwner = group.value?.members?.find(m => m.id === member.id)?.role === 'owner';
|
||||
return !isOwner;
|
||||
};
|
||||
|
||||
const removeMember = async (memberId: number) => {
|
||||
@ -390,6 +413,23 @@ const getSplitTypeBadgeVariant = (type: string): BadgeVariant => {
|
||||
return colorMap[type] || 'neutral';
|
||||
};
|
||||
|
||||
const toggleMemberMenu = (memberId: number) => {
|
||||
if (activeMemberMenu.value === memberId) {
|
||||
activeMemberMenu.value = null;
|
||||
} else {
|
||||
activeMemberMenu.value = memberId;
|
||||
// Close invite UI if it's open
|
||||
showInviteUI.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleInviteUI = () => {
|
||||
showInviteUI.value = !showInviteUI.value;
|
||||
if (showInviteUI.value) {
|
||||
activeMemberMenu.value = null; // Close any open member menu
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchGroupDetails();
|
||||
loadUpcomingChores();
|
||||
@ -483,6 +523,111 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.member-avatar-list {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.member-avatars {
|
||||
display: flex;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
position: relative;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary);
|
||||
color: var(--dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 900;
|
||||
border: 2px solid var(--light);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.avatar-circle:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.member-menu {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
right: -10px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border: 2px solid var(--dark);
|
||||
box-shadow: var(--shadow-md);
|
||||
width: 220px;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
/* padding: 0.5rem; */
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
.member-menu-content {
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.add-member-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--light);
|
||||
border: 2px dashed var(--dark);
|
||||
color: var(--dark);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
margin-left: -8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.add-member-btn:hover {
|
||||
background: var(--secondary);
|
||||
transform: scale(1.1);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.header-title-text {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.invite-popup {
|
||||
position: absolute;
|
||||
top: calc(16%);
|
||||
right: 10%;
|
||||
width: 27%;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--dark);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 100;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
/* Members List Styles */
|
||||
.neo-members-list {
|
||||
display: flex;
|
||||
|
Loading…
Reference in New Issue
Block a user