feat: add setting to toggle alphabetical grouping in contacts list
This commit is contained in:
@@ -137,6 +137,7 @@ export function ContactList({
|
|||||||
const t = useTranslations("contacts");
|
const t = useTranslations("contacts");
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const density = useSettingsStore((state) => state.density);
|
const density = useSettingsStore((state) => state.density);
|
||||||
|
const groupByLetter = useSettingsStore((state) => state.groupContactsByLetter);
|
||||||
const { contextMenu, openContextMenu, closeContextMenu, menuRef } = useContextMenu<ContactCard>();
|
const { contextMenu, openContextMenu, closeContextMenu, menuRef } = useContextMenu<ContactCard>();
|
||||||
const [filtersOpen, setFiltersOpen] = useState(false);
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
||||||
const [filters, setFilters] = useState<ListFilters>(EMPTY_FILTERS);
|
const [filters, setFilters] = useState<ListFilters>(EMPTY_FILTERS);
|
||||||
@@ -528,43 +529,50 @@ export function ContactList({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
(() => {
|
||||||
{groupedSections.map(({ letter, items }) => (
|
const renderItem = (contact: ContactCard) => (
|
||||||
<section key={letter}>
|
<ContactListItem
|
||||||
<div className="sticky top-0 z-10 px-4 py-0.5 bg-background/90 backdrop-blur-sm text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wide">
|
key={contact.id}
|
||||||
{letter}
|
contact={contact}
|
||||||
</div>
|
isSelected={contact.id === selectedContactId}
|
||||||
{items.map((contact) => (
|
isChecked={selectedContactIds.has(contact.id)}
|
||||||
<ContactListItem
|
hasSelection={hasSelection}
|
||||||
key={contact.id}
|
density={density}
|
||||||
contact={contact}
|
selectedContactIds={selectedContactIds}
|
||||||
isSelected={contact.id === selectedContactId}
|
onClick={(e) => {
|
||||||
isChecked={selectedContactIds.has(contact.id)}
|
if (e.ctrlKey || e.metaKey) {
|
||||||
hasSelection={hasSelection}
|
e.preventDefault();
|
||||||
density={density}
|
onToggleSelection(contact.id);
|
||||||
selectedContactIds={selectedContactIds}
|
} else if (e.shiftKey) {
|
||||||
onClick={(e) => {
|
e.preventDefault();
|
||||||
if (e.ctrlKey || e.metaKey) {
|
onSelectRangeContacts(contact.id, sortedIds);
|
||||||
e.preventDefault();
|
} else {
|
||||||
onToggleSelection(contact.id);
|
if (hasSelection) onClearSelection();
|
||||||
} else if (e.shiftKey) {
|
onSelectContact(contact.id);
|
||||||
e.preventDefault();
|
}
|
||||||
onSelectRangeContacts(contact.id, sortedIds);
|
}}
|
||||||
} else {
|
onCheckboxClick={(e) => {
|
||||||
if (hasSelection) onClearSelection();
|
e.stopPropagation();
|
||||||
onSelectContact(contact.id);
|
onToggleSelection(contact.id);
|
||||||
}
|
}}
|
||||||
}}
|
onContextMenu={(e, c) => openContextMenu(e, c)}
|
||||||
onCheckboxClick={(e) => {
|
/>
|
||||||
e.stopPropagation();
|
);
|
||||||
onToggleSelection(contact.id);
|
return groupByLetter ? (
|
||||||
}}
|
<div>
|
||||||
onContextMenu={(e, c) => openContextMenu(e, c)}
|
{groupedSections.map(({ letter, items }) => (
|
||||||
/>
|
<section key={letter}>
|
||||||
|
<div className="sticky top-0 z-10 px-4 py-0.5 bg-background/90 backdrop-blur-sm text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wide">
|
||||||
|
{letter}
|
||||||
|
</div>
|
||||||
|
{items.map(renderItem)}
|
||||||
|
</section>
|
||||||
))}
|
))}
|
||||||
</section>
|
</div>
|
||||||
))}
|
) : (
|
||||||
</div>
|
<div>{sorted.map(renderItem)}</div>
|
||||||
|
);
|
||||||
|
})()
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { useState, useCallback } from "react";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Upload, Download } from "lucide-react";
|
import { Upload, Download } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { SettingsSection, SettingItem } from "./settings-section";
|
import { SettingsSection, SettingItem, ToggleSwitch } from "./settings-section";
|
||||||
import { ContactImportDialog } from "@/components/contacts/contact-import-dialog";
|
import { ContactImportDialog } from "@/components/contacts/contact-import-dialog";
|
||||||
import { exportContacts } from "@/components/contacts/contact-export";
|
import { exportContacts } from "@/components/contacts/contact-export";
|
||||||
import { useContactStore } from "@/stores/contact-store";
|
import { useContactStore } from "@/stores/contact-store";
|
||||||
import { useAuthStore } from "@/stores/auth-store";
|
import { useAuthStore } from "@/stores/auth-store";
|
||||||
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
import { toast } from "@/stores/toast-store";
|
import { toast } from "@/stores/toast-store";
|
||||||
|
|
||||||
export function ContactsSettings() {
|
export function ContactsSettings() {
|
||||||
@@ -20,6 +21,8 @@ export function ContactsSettings() {
|
|||||||
supportsSync,
|
supportsSync,
|
||||||
importContacts,
|
importContacts,
|
||||||
} = useContactStore();
|
} = useContactStore();
|
||||||
|
const groupContactsByLetter = useSettingsStore((s) => s.groupContactsByLetter);
|
||||||
|
const updateSetting = useSettingsStore((s) => s.updateSetting);
|
||||||
const [showImport, setShowImport] = useState(false);
|
const [showImport, setShowImport] = useState(false);
|
||||||
|
|
||||||
const individuals = contacts.filter(c => c.kind !== "group");
|
const individuals = contacts.filter(c => c.kind !== "group");
|
||||||
@@ -55,6 +58,16 @@ export function ContactsSettings() {
|
|||||||
title={tSettings("title")}
|
title={tSettings("title")}
|
||||||
description={tSettings("description")}
|
description={tSettings("description")}
|
||||||
>
|
>
|
||||||
|
<SettingItem
|
||||||
|
label={tSettings("group_by_letter_label")}
|
||||||
|
description={tSettings("group_by_letter_description")}
|
||||||
|
>
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={groupContactsByLetter}
|
||||||
|
onChange={(checked) => updateSetting("groupContactsByLetter", checked)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={tSettings("import_label")}
|
label={tSettings("import_label")}
|
||||||
description={tSettings("import_description")}
|
description={tSettings("import_description")}
|
||||||
|
|||||||
@@ -1322,6 +1322,8 @@
|
|||||||
"contacts": {
|
"contacts": {
|
||||||
"title": "Contacts",
|
"title": "Contacts",
|
||||||
"description": "Import and export your contacts",
|
"description": "Import and export your contacts",
|
||||||
|
"group_by_letter_label": "Group by first letter",
|
||||||
|
"group_by_letter_description": "Show alphabetical section headers in the contact list",
|
||||||
"import_label": "Import Contacts",
|
"import_label": "Import Contacts",
|
||||||
"import_description": "Import contacts from a vCard (.vcf) file",
|
"import_description": "Import contacts from a vCard (.vcf) file",
|
||||||
"export_label": "Export Contacts",
|
"export_label": "Export Contacts",
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ interface SettingsState {
|
|||||||
showBirthdayCalendar: boolean;
|
showBirthdayCalendar: boolean;
|
||||||
birthdayCalendarColor: string;
|
birthdayCalendarColor: string;
|
||||||
|
|
||||||
|
// Contacts Display
|
||||||
|
groupContactsByLetter: boolean;
|
||||||
|
|
||||||
// Email Notifications
|
// Email Notifications
|
||||||
emailNotificationsEnabled: boolean;
|
emailNotificationsEnabled: boolean;
|
||||||
emailNotificationSound: boolean;
|
emailNotificationSound: boolean;
|
||||||
@@ -304,6 +307,9 @@ const DEFAULT_SETTINGS = {
|
|||||||
showBirthdayCalendar: false,
|
showBirthdayCalendar: false,
|
||||||
birthdayCalendarColor: '#eab308',
|
birthdayCalendarColor: '#eab308',
|
||||||
|
|
||||||
|
// Contacts Display
|
||||||
|
groupContactsByLetter: true,
|
||||||
|
|
||||||
// Email Notifications
|
// Email Notifications
|
||||||
emailNotificationsEnabled: true,
|
emailNotificationsEnabled: true,
|
||||||
emailNotificationSound: true,
|
emailNotificationSound: true,
|
||||||
@@ -459,6 +465,7 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
showTasksOnCalendar: state.showTasksOnCalendar,
|
showTasksOnCalendar: state.showTasksOnCalendar,
|
||||||
showBirthdayCalendar: state.showBirthdayCalendar,
|
showBirthdayCalendar: state.showBirthdayCalendar,
|
||||||
birthdayCalendarColor: state.birthdayCalendarColor,
|
birthdayCalendarColor: state.birthdayCalendarColor,
|
||||||
|
groupContactsByLetter: state.groupContactsByLetter,
|
||||||
expandedFilterView: state.expandedFilterView,
|
expandedFilterView: state.expandedFilterView,
|
||||||
showTimeInMonthView: state.showTimeInMonthView,
|
showTimeInMonthView: state.showTimeInMonthView,
|
||||||
showWeekNumbers: state.showWeekNumbers,
|
showWeekNumbers: state.showWeekNumbers,
|
||||||
|
|||||||
Reference in New Issue
Block a user