From 66527842a888545ed263421b5a7cf19eda87e1dc Mon Sep 17 00:00:00 2001 From: Rodrigo Gomes da Silva Date: Fri, 2 Jan 2026 15:32:39 -0300 Subject: [PATCH 1/3] Release: UI Componentization (v1.23.0) (#44) * refactor: componentize buttons and icons (#33) * fix: add cursor pointer to close button in ChatStats component * feat: add reusable button components * feat: add Icon component with 47 icons and filled state support * refactor: replace inline SVGs with Icon component * refactor: replace inline buttons with IconButton and ListItemButton * refactor: replace inline SVGs and buttons in main page - Replace all inline SVG icons with Icon component - Replace inline button elements with IconButton component - Fix layout structure issues for proper flex container nesting - Fix indentation in no-chat-selected section * style: fix linting issues * fix: replace remaining inline SVG and hardcoded strings - Replace inline WhatsApp SVG with Icon component in +page.svelte - Replace hardcoded 'Delete bookmark', 'Cancel', 'Save' with i18n messages in BookmarkModal - Replace hardcoded 'Remove Chat' with i18n message in ChatList * fix: export new button and icon components from index.ts * fix: address Copilot review comments - Fix wifi-off icon SVG path (was showing lightbulb icon) - Fix circle icon SVG path (malformed relative move command) - Fix Icon indentation inside IconButton/Button components (7 locations) * Initial plan * fix: replace array class bindings with template literals and remove unused prop Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com> * style: use consistent template literal pattern for class bindings Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com> * Initial plan * fix: resolve type check errors and a11y warning - Use local inlang plugins instead of CDN to fix compilation in offline/restricted networks - Add stroke-width prop to Icon component and use it in actualStrokeWidth derivation - Add track element to video in MediaGallery to satisfy a11y requirements Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com> * chore: auto-sync dev with main after release * fix: add JSDoc comment to IconName type for better IDE support * fix: use GitHub API to sync dev with main after release The previous approach failed because dev branch is protected and requires PRs. * feat: use peter-evans/create-pull-request for elegant branch sync Most elegant solution for syncing protected branches: - Industry standard (175k+ repos use it) - No PAT required, works with GITHUB_TOKEN - Handles branch protection automatically - Idempotent (updates existing PR) - Auto-deletes branch after merge * fix: remove duplicate sync step (handled in build.yml) * fix: add pull-requests permission for branch sync * feat: extract reusable UI components Extract repeated UI patterns into reusable components: - Collapsible: Expandable sections with animated chevron - FeatureItem: Badge list items (numbered/icon variants) - Modal + ModalHeader + ModalContent: Dialog overlays with ESC key handling - Dropdown + DropdownHeader + DropdownSearch + DropdownList: Floating menus Reduced +page.svelte by 92 lines while improving maintainability. Co-authored-by: Copilot --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rodrigogs <2362425+rodrigogs@users.noreply.github.com> Co-authored-by: Copilot --- src/lib/components/Collapsible.svelte | 53 +++++ src/lib/components/Dropdown.svelte | 115 +++++++++++ src/lib/components/DropdownHeader.svelte | 20 ++ src/lib/components/DropdownList.svelte | 29 +++ src/lib/components/DropdownSearch.svelte | 50 +++++ src/lib/components/FeatureItem.svelte | 62 ++++++ src/lib/components/Icon.svelte | 2 +- src/lib/components/Modal.svelte | 69 +++++++ src/lib/components/ModalContent.svelte | 20 ++ src/lib/components/ModalHeader.svelte | 65 ++++++ src/lib/components/index.ts | 9 + src/routes/+page.svelte | 244 +++++++---------------- 12 files changed, 569 insertions(+), 169 deletions(-) create mode 100644 src/lib/components/Collapsible.svelte create mode 100644 src/lib/components/Dropdown.svelte create mode 100644 src/lib/components/DropdownHeader.svelte create mode 100644 src/lib/components/DropdownList.svelte create mode 100644 src/lib/components/DropdownSearch.svelte create mode 100644 src/lib/components/FeatureItem.svelte create mode 100644 src/lib/components/Modal.svelte create mode 100644 src/lib/components/ModalContent.svelte create mode 100644 src/lib/components/ModalHeader.svelte diff --git a/src/lib/components/Collapsible.svelte b/src/lib/components/Collapsible.svelte new file mode 100644 index 0000000..569afad --- /dev/null +++ b/src/lib/components/Collapsible.svelte @@ -0,0 +1,53 @@ + + +
+ + + {title} + +
+ {@render children()} +
+
diff --git a/src/lib/components/Dropdown.svelte b/src/lib/components/Dropdown.svelte new file mode 100644 index 0000000..d03285e --- /dev/null +++ b/src/lib/components/Dropdown.svelte @@ -0,0 +1,115 @@ + + +{#if open && anchor} + + + + + + +{/if} diff --git a/src/lib/components/DropdownHeader.svelte b/src/lib/components/DropdownHeader.svelte new file mode 100644 index 0000000..3a63cf2 --- /dev/null +++ b/src/lib/components/DropdownHeader.svelte @@ -0,0 +1,20 @@ + + +
+ {title} +
diff --git a/src/lib/components/DropdownList.svelte b/src/lib/components/DropdownList.svelte new file mode 100644 index 0000000..d62956d --- /dev/null +++ b/src/lib/components/DropdownList.svelte @@ -0,0 +1,29 @@ + + +
+ {@render children()} +
diff --git a/src/lib/components/DropdownSearch.svelte b/src/lib/components/DropdownSearch.svelte new file mode 100644 index 0000000..8416ed5 --- /dev/null +++ b/src/lib/components/DropdownSearch.svelte @@ -0,0 +1,50 @@ + + +
+
+
+ +
+ onInput?.(value)} + class="w-full pl-8 pr-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 border-0 rounded-md focus:ring-2 focus:ring-[var(--color-whatsapp-teal)] focus:outline-none text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400" + /> +
+
diff --git a/src/lib/components/FeatureItem.svelte b/src/lib/components/FeatureItem.svelte new file mode 100644 index 0000000..3b444db --- /dev/null +++ b/src/lib/components/FeatureItem.svelte @@ -0,0 +1,62 @@ + + +
+ {#if (effectiveVariant === 'numbered' && badge !== undefined) || (effectiveVariant === 'icon' && icon)} + + {#if effectiveVariant === 'numbered' && badge !== undefined} + {badge} + {:else if effectiveVariant === 'icon' && icon} + + {/if} + + {/if} + + {@render children()} + +
diff --git a/src/lib/components/Icon.svelte b/src/lib/components/Icon.svelte index ed0be09..42bf276 100644 --- a/src/lib/components/Icon.svelte +++ b/src/lib/components/Icon.svelte @@ -4,7 +4,7 @@ import type { HTMLAttributes } from 'svelte/elements'; /** * Supported icon names for the Icon component */ -type IconName = +export type IconName = // Navigation | 'menu' | 'close' diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte new file mode 100644 index 0000000..17fb2c3 --- /dev/null +++ b/src/lib/components/Modal.svelte @@ -0,0 +1,69 @@ + + +{#if open} + + + + + +{/if} diff --git a/src/lib/components/ModalContent.svelte b/src/lib/components/ModalContent.svelte new file mode 100644 index 0000000..d6ce968 --- /dev/null +++ b/src/lib/components/ModalContent.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children()} +
diff --git a/src/lib/components/ModalHeader.svelte b/src/lib/components/ModalHeader.svelte new file mode 100644 index 0000000..2775a6c --- /dev/null +++ b/src/lib/components/ModalHeader.svelte @@ -0,0 +1,65 @@ + + +
+
+ {#if icon} + + {/if} +
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+
+ {#if children} + {@render children()} + {/if} + + + +
diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 880a660..36975aa 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -3,11 +3,20 @@ export { default as Button } from './Button.svelte'; export { default as ChatList } from './ChatList.svelte'; export { default as ChatStats } from './ChatStats.svelte'; export { default as ChatView } from './ChatView.svelte'; +export { default as Collapsible } from './Collapsible.svelte'; +export { default as Dropdown } from './Dropdown.svelte'; +export { default as DropdownHeader } from './DropdownHeader.svelte'; +export { default as DropdownList } from './DropdownList.svelte'; +export { default as DropdownSearch } from './DropdownSearch.svelte'; +export { default as FeatureItem } from './FeatureItem.svelte'; export { default as FileDropZone } from './FileDropZone.svelte'; export { default as Icon } from './Icon.svelte'; export { default as IconButton } from './IconButton.svelte'; export { default as ListItemButton } from './ListItemButton.svelte'; export { default as MessageBubble } from './MessageBubble.svelte'; +export { default as Modal } from './Modal.svelte'; +export { default as ModalContent } from './ModalContent.svelte'; +export { default as ModalHeader } from './ModalHeader.svelte'; export { default as SearchBar } from './SearchBar.svelte'; export { default as UpdateToast } from './UpdateToast.svelte'; export { default as VersionBadge } from './VersionBadge.svelte'; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 64a7004..b555898 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -7,6 +7,12 @@ import { ChatList, ChatStats, ChatView, + Collapsible, + Dropdown, + DropdownHeader, + DropdownList, + DropdownSearch, + FeatureItem, FileDropZone, SearchBar, VersionBadge, @@ -18,6 +24,9 @@ import IconButton from '$lib/components/IconButton.svelte'; import ListItemButton from '$lib/components/ListItemButton.svelte'; import LocaleSwitcher from '$lib/components/LocaleSwitcher.svelte'; import MediaGallery from '$lib/components/MediaGallery.svelte'; +import Modal from '$lib/components/Modal.svelte'; +import ModalContent from '$lib/components/ModalContent.svelte'; +import ModalHeader from '$lib/components/ModalHeader.svelte'; import * as m from '$lib/paraglide/messages'; import { parseZipFile, readFileAsArrayBuffer } from '$lib/parser'; import { appState, type ChatData } from '$lib/state.svelte'; @@ -474,74 +483,26 @@ const currentUser = $derived.by(() => { {/if} -
- - - {m.export_instructions_title()} - -
-
- 1 - {m.export_step_1()} -
-
- 2 - {m.export_step_2()} -
-
- 3 - {m.export_step_3()} -
-
- 4 - {m.export_step_4()} -
-
- 5 - {m.export_step_5()} -
+ +
+ {m.export_step_1()} + {m.export_step_2()} + {m.export_step_3()} + {m.export_step_4()} + {m.export_step_5()}
-
+ -
- - - {m.privacy_title()} - -
-
- - - - {m.privacy_offline()} -
-
- - - - {m.privacy_local_processing()} -
-
- - - - {m.privacy_local_ai()} -
-
- - - - {m.privacy_no_tracking()} -
-
- - - - {m.privacy_open_source()} -
+ +
+ {m.privacy_offline()} + {m.privacy_local_processing()} + {m.privacy_local_ai()} + {m.privacy_no_tracking()} + {m.privacy_open_source()}
-
+
@@ -679,78 +640,46 @@ const currentUser = $derived.by(() => { > - {#if showPerspectiveDropdown && perspectiveButtonRef} - - + + { showPerspectiveDropdown = false; perspectiveSearchQuery = ''; }} + > + - - - { {/if} - {#if showParticipants && appState.selectedChat && participantStats} - - - - -
- -
-
- -
-

{m.participants_title()}

-

{m.participants_members({ count: appState.selectedChat.participants.length })}

-
-
- - - -
- -
+ + + + {#if appState.selectedChat && participantStats} {#each appState.selectedChat.participants as participant} {@const messageCount = participantStats.get(participant) || 0} {@const isPhoneNumber = /\+?\d[\d\s\-()]{8,}/.test(participant)} @@ -995,9 +903,9 @@ const currentUser = $derived.by(() => { {/if}
{/each} -
-
- {/if} + {/if} + + {:else}
From 47f04961283f85198f36fed3b9b75fd06f4e6d41 Mon Sep 17 00:00:00 2001 From: Rodrigo Gomes da Silva Date: Fri, 2 Jan 2026 15:38:05 -0300 Subject: [PATCH 2/3] feat: UI componentization with reusable components Extract repeated UI patterns into reusable components: - Collapsible: Expandable sections with animated chevron - FeatureItem: Badge list items (numbered/icon variants) - Modal + ModalHeader + ModalContent: Dialog overlays with ESC key handling - Dropdown + DropdownHeader + DropdownSearch + DropdownList: Floating menus Reduced +page.svelte by 92 lines while improving maintainability. From c473fe7553a914157188effc85a5348e9a30796f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Jan 2026 18:38:52 +0000 Subject: [PATCH 3/3] chore(release): 1.23.0 [skip ci] # [1.23.0](https://github.com/rodrigogs/whats-reader/compare/v1.22.2...v1.23.0) (2026-01-02) ### Features * UI componentization with reusable components ([47f0496](https://github.com/rodrigogs/whats-reader/commit/47f04961283f85198f36fed3b9b75fd06f4e6d41)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e7bfd..05d5b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.23.0](https://github.com/rodrigogs/whats-reader/compare/v1.22.2...v1.23.0) (2026-01-02) + + +### Features + +* UI componentization with reusable components ([47f0496](https://github.com/rodrigogs/whats-reader/commit/47f04961283f85198f36fed3b9b75fd06f4e6d41)) + ## [1.22.2](https://github.com/rodrigogs/whats-reader/compare/v1.22.1...v1.22.2) (2026-01-02) diff --git a/package-lock.json b/package-lock.json index ee4a14a..7f53e6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "whats-reader", - "version": "1.22.2", + "version": "1.23.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "whats-reader", - "version": "1.22.2", + "version": "1.23.0", "license": "AGPL-3.0", "dependencies": { "@floating-ui/dom": "^1.7.4", diff --git a/package.json b/package.json index 746959f..2e79996 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "whats-reader", "productName": "WhatsApp Backup Reader", - "version": "1.22.2", + "version": "1.23.0", "description": "A desktop app to read and visualize WhatsApp chat exports", "license": "AGPL-3.0", "author": {