Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/checkmark-active-swatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@youversion/platform-react-ui': patch
---

BibleReader's verse action popover now marks active swatches with a checkmark (YPE-1034 PR3, still behind the internal `HIGHLIGHTS_LIVE` flag).

- **Checkmark swap**: active/remove swatches now render a 24px checkmark (`icons/check`) instead of the X, matching iOS (platform-sdk-swift #179). Same theme-invariant `#121212` fill and identical behavior — tapping still removes the highlight.
5 changes: 5 additions & 0 deletions docs/adr/YPE-642-verse-action-popover.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ Selection is always enabled in BibleReader (no opt-out prop for now; YAGNI).
- Tunable; swap to a ring/bg later if Figma says otherwise.

## As-built notes (deviations from the design above)
- **ADR-005 active-swatch icon (YPE-1034 PR3):** the 24px **X** on active/remove
swatches was replaced with a 24px **checkmark** (`icons/check`), matching iOS
(platform-sdk-swift #179). Same Text/Everdark (`--yv-gray-50` = `#121212`,
theme-invariant) fill and identical behavior — tapping still removes the
highlight; the swatch's `Clear highlight` aria-label is unchanged.
- **ADR-004 revised:** selection + highlights live in `Content`, **not** Root
context. Copy/Share/anchor all need the rendered verse DOM (which lives in
Content), so Root ownership would fragment the feature. BibleTextView stays
Expand Down
20 changes: 20 additions & 0 deletions packages/ui/src/components/icons/check.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ComponentProps, ReactElement } from 'react';

export function CheckIcon(props: ComponentProps<'svg'>): ReactElement {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
{...props}
>
<path
d="M19.6627 6.13882C20.0663 6.51547 20.0888 7.14784 19.7124 7.55165L19.7007 7.56339C19.693 7.57162 19.6815 7.58408 19.6661 7.60068C19.6349 7.63447 19.5879 7.68562 19.5266 7.75259C19.404 7.88675 19.2237 8.08624 18.9942 8.343C18.5352 8.85666 17.8803 9.60146 17.1022 10.5223C15.5446 12.3656 13.497 14.9071 11.5296 17.708C11.3594 17.9502 11.1725 18.22 10.9889 18.4172C10.7971 18.6231 10.45 18.9278 9.92408 18.942C9.39431 18.9563 9.02907 18.666 8.8282 18.4718C8.63358 18.2836 8.43144 18.0219 8.24539 17.7854C7.25297 16.5234 6.27013 15.4686 5.53573 14.7291C5.16905 14.3598 4.8656 14.0704 4.6553 13.8749C4.5502 13.7771 4.46885 13.7022 4.41431 13.6532C4.38726 13.6289 4.36672 13.611 4.35354 13.5993C4.34694 13.5935 4.34194 13.5888 4.33904 13.5862C4.33767 13.585 4.33607 13.5846 4.33558 13.5842C3.91934 13.2213 3.8761 12.589 4.23891 12.1727C4.60179 11.7565 5.23338 11.7139 5.64967 12.0767L5.65036 12.076L5.65796 12.0836C5.66273 12.0878 5.67017 12.094 5.67867 12.1016C5.69565 12.1166 5.72014 12.1379 5.75118 12.1658C5.81368 12.222 5.90392 12.3044 6.01772 12.4102C6.24522 12.6218 6.56756 12.9297 6.95478 13.3197C7.72857 14.0989 8.76556 15.2122 9.81704 16.5493C9.83185 16.5681 9.84664 16.5862 9.86055 16.6039C9.87102 16.589 9.88266 16.574 9.89369 16.5583C11.9059 13.6936 13.9928 11.1031 15.5747 9.23103C16.3663 8.29427 17.0331 7.53573 17.5027 7.01027C17.7375 6.7475 17.9228 6.54269 18.0503 6.40329C18.1138 6.33381 18.1632 6.28075 18.1967 6.24447C18.2133 6.22647 18.2266 6.2125 18.2353 6.20304C18.2397 6.1983 18.2434 6.19382 18.2457 6.1913L18.2492 6.18784C18.6259 5.784 19.2588 5.76208 19.6627 6.13882Z"
fill="currentColor"
/>
</svg>
);
Comment thread
cameronapak marked this conversation as resolved.
}
28 changes: 28 additions & 0 deletions packages/ui/src/components/verse-action-popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,34 @@ describe('VerseActionPopover', () => {
.filter((btn) => btn.getAttribute('aria-label')?.includes('Clear'));
}

describe('Active swatch checkmark', () => {
// The checkmark path (Swift #179) starts at these coords — asserts we render
// the check, not the old X.
const CHECK_PATH_PREFIX = 'M19.6627';

it('renders a checkmark (not an X) on the active/remove swatch', () => {
render(
<VerseActionPopover
{...defaultProps}
activeHighlights={new Set<HighlightColor>([HIGHLIGHT_COLORS[0]])}
selectedVerses={[1]}
highlightedVerses={{ 1: HIGHLIGHT_COLORS[0] }}
/>,
);

const removeButton = screen.getByRole('button', { name: /Clear highlight/ });
const path = removeButton.querySelector('svg path');
expect(path?.getAttribute('d')).toContain(CHECK_PATH_PREFIX);
});

it('apply swatches render no icon', () => {
render(<VerseActionPopover {...defaultProps} />);
applyButtons().forEach((btn) => {
expect(btn.querySelector('svg')).toBeNull();
});
});
});

describe('Highlights disabled (flag off)', () => {
it('hides the color row and remove circles but keeps Copy / Share', () => {
render(
Expand Down
28 changes: 15 additions & 13 deletions packages/ui/src/components/verse-action-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import i18n from '@/i18n';
import { cn } from '../lib/utils';
import { BoxStackIcon } from './icons/box-stack';
import { BoxArrowUpIcon } from './icons/box-arrow-up';
import { XIcon } from './icons/x';
import { CheckIcon } from './icons/check';

type Measurable = { getBoundingClientRect: () => DOMRect };

Expand Down Expand Up @@ -47,12 +47,12 @@ type VerseActionPopoverProps = {

type ColorCircleProps = {
color: string;
showX: boolean;
showRemove: boolean;
label: string;
onClick: () => void;
};

function ColorCircle({ color, showX, label, onClick }: ColorCircleProps) {
function ColorCircle({ color, showRemove, label, onClick }: ColorCircleProps) {
return (
<button
type="button"
Expand All @@ -70,9 +70,11 @@ function ColorCircle({ color, showX, label, onClick }: ColorCircleProps) {
}}
aria-label={label}
>
{/* Active/remove swatch: a 24px X in the Text/Everdark color (always dark,
regardless of theme) on the solid color circle. */}
{showX && <XIcon className="yv:size-6 yv:text-(--yv-gray-50)" />}
{/* Active/remove swatch: a 24px checkmark in the Text/Everdark color
(always dark, regardless of theme) on the solid color circle. Matches
iOS (platform-sdk-swift #179), which swapped the earlier X for a check.
Tapping it still removes the highlight — icon-only change. */}
{showRemove && <CheckIcon className="yv:size-6 yv:text-(--yv-gray-50)" />}
</button>
);
}
Expand Down Expand Up @@ -201,10 +203,10 @@ export const VerseActionPopover: FC<VerseActionPopoverProps> = ({
? HIGHLIGHT_COLORS
: HIGHLIGHT_COLORS.filter((c) => !activeHighlights.has(c));

// X (remove) circles come first, then apply circles in canonical order.
// Remove (checkmark) circles come first, then apply circles in canonical order.
const colorCircles = [
...activeColors.map((color) => ({ color, showX: true, key: `${color}-clear` })),
...colorsToApply.map((color) => ({ color, showX: false, key: `${color}-apply` })),
...activeColors.map((color) => ({ color, showRemove: true, key: `${color}-clear` })),
...colorsToApply.map((color) => ({ color, showRemove: false, key: `${color}-apply` })),
];

// Snapshot of everything the Content renders. While open we keep it fresh; the
Expand Down Expand Up @@ -291,13 +293,13 @@ export const VerseActionPopover: FC<VerseActionPopoverProps> = ({
role="group"
aria-label={t('highlightColorsAriaLabel')}
>
{view.colorCircles.map(({ color, showX, key }) => (
{view.colorCircles.map(({ color, showRemove, key }) => (
<ColorCircle
key={key}
color={color}
showX={showX}
label={showX ? t('clearHighlightAriaLabel') : t('applyHighlightAriaLabel')}
onClick={() => (showX ? onClearHighlight(color) : onHighlight(color))}
showRemove={showRemove}
label={showRemove ? t('clearHighlightAriaLabel') : t('applyHighlightAriaLabel')}
onClick={() => (showRemove ? onClearHighlight(color) : onHighlight(color))}
/>
))}
</div>
Expand Down
Loading