diff --git a/package.json b/package.json index b42dad1..b4d5166 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "README.md" ], "peerDependencies": { - "@govoplan/core-webui": "^0.1.8", + "@govoplan/core-webui": "^0.1.9", "lucide-react": "^1.23.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/webui/package.json b/webui/package.json index 6652fac..db330c2 100644 --- a/webui/package.json +++ b/webui/package.json @@ -13,8 +13,11 @@ }, "./styles/addresses.css": "./src/styles/addresses.css" }, + "scripts": { + "test:ui-structure": "node scripts/test-selection-list-structure.mjs" + }, "peerDependencies": { - "@govoplan/core-webui": "^0.1.8", + "@govoplan/core-webui": "^0.1.9", "lucide-react": "^1.23.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/webui/scripts/test-selection-list-structure.mjs b/webui/scripts/test-selection-list-structure.mjs new file mode 100644 index 0000000..f63b3f1 --- /dev/null +++ b/webui/scripts/test-selection-list-structure.mjs @@ -0,0 +1,21 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +const pagePath = fileURLToPath(new URL("../src/features/addressbook/AddressBookPage.tsx", import.meta.url)); +const stylesPath = fileURLToPath(new URL("../src/styles/addresses.css", import.meta.url)); +const page = readFileSync(pagePath, "utf8"); +const styles = readFileSync(stylesPath, "utf8"); + +assert.match(page, /SegmentedControl,[\s\S]*SelectionList,[\s\S]*SelectionListItem,[\s\S]*from "@govoplan\/core-webui"/); +assert.match(page, //); +assert.match(page, //); +assert.match(page, /selected=\{cardDavForm\.collection_url === item\.collection_url\}/); +assert.match(page, /[\s\S]*role="group"[\s\S]*value=\{conflictMergeChoices\[row\.field\] \?\? "local"\}/); +assert.doesNotMatch(page, / setSelectedContactId(contact.id)} onDragStart={(event) => handleContactDragStart(event, contact)}> @@ -1805,7 +1808,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props) {contact.deleted_at ? : contact.tags.slice(0, 2).map((tag) => {tag})} - + ); } @@ -1981,10 +1984,12 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props) {selectedBook?.read_only && } {selectedBook?.deleted_at && } -
+
{visibleContacts.length === 0 ?
{selectedBook ? "No contacts found." : "Select an address book."}
: - visibleContacts.map(renderContactRow) + + {visibleContacts.map(renderContactRow)} + }
@@ -2267,14 +2272,18 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props) } {cardDavDiscovery.length > 0 && -
+ {cardDavDiscovery.map((item) => ( - + ))} -
+ } @@ -2450,15 +2459,20 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props) {row.field} {row.local} {row.remote} - +
{canMergeConflict(conflictDialog) ? - <> - - - : + + className="address-conflict-choice" + role="group" + size="equal" + ariaLabel={`Source for ${row.field}`} + options={[{ id: "local", label: "Local" }, { id: "remote", label: "Remote" }]} + value={conflictMergeChoices[row.field] ?? "local"} + onChange={(choice) => setConflictMergeChoice(row.field, choice)} + /> : "—" } - +
))} diff --git a/webui/src/styles/addresses.css b/webui/src/styles/addresses.css index 2f7252f..4755925 100644 --- a/webui/src/styles/addresses.css +++ b/webui/src/styles/addresses.css @@ -150,16 +150,19 @@ width: min(960px, calc(100vw - 36px)); } +.address-sync-record-list, +.address-sync-plan-grid { + display: grid; +} + .address-sync-result-list, .address-sync-record-list, .address-sync-plan-grid { border: var(--border-line); - display: grid; max-height: 260px; overflow: auto; } -.address-sync-result-row, .address-sync-record-row, .address-sync-plan-row { align-items: center; @@ -175,13 +178,11 @@ } .address-sync-result-row { - cursor: pointer; -} - -.address-sync-result-row:hover, -.address-sync-result-row:focus-visible { - background: var(--line); - outline: none; + align-items: center; + border-bottom: var(--border-line); + display: grid; + gap: 10px; + grid-template-columns: minmax(0, 1fr) auto; } .address-sync-result-row strong, @@ -255,37 +256,6 @@ overflow-wrap: anywhere; } -.address-conflict-choice { - align-items: center; - display: inline-flex; - gap: 0; - white-space: nowrap; -} - -.address-conflict-choice button { - background: var(--panel); - border: var(--border-line); - color: var(--text); - cursor: pointer; - font: inherit; - min-height: 28px; - padding: 3px 8px; -} - -.address-conflict-choice button:first-child { - border-radius: var(--radius-sm) 0 0 var(--radius-sm); -} - -.address-conflict-choice button:last-child { - border-left: 0; - border-radius: 0 var(--radius-sm) var(--radius-sm) 0; -} - -.address-conflict-choice button.is-selected { - background: var(--line); - box-shadow: inset 0 1px 2px rgba(var(--shadow-color-rgb), .12); -} - .address-list-panel, .address-detail-panel { display: flex; @@ -338,34 +308,15 @@ align-content: start; } +.address-contact-selection-list { + gap: 2px; +} + .address-contact-row { align-items: center; - background: transparent; - border: 0; - border-radius: 6px; - color: var(--text); - cursor: pointer; display: grid; gap: 10px; grid-template-columns: minmax(0, 1fr) auto; - min-width: 0; - padding: 10px; - text-align: left; -} - -.address-contact-row[draggable="true"] { - cursor: grab; -} - -.address-contact-row[draggable="true"]:active { - cursor: grabbing; -} - -.address-contact-row:hover, -.address-contact-row:focus-visible, -.address-contact-row.is-selected { - background: var(--line); - outline: none; } .address-contact-row-main {