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
20 changes: 20 additions & 0 deletions apps/web/e2e/unique-ids.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from '@playwright/test';

// The navbar renders the locale switcher twice, and the English flag used
// fixed clipPath ids, so every page had duplicate ids (DOS/Rate#1079).
// The English flag is drawn as the current locale's flag on /en pages.
for (const path of ['/en', '/en/smartphones']) {
test(`ids are unique on ${path}`, async ({ page }) => {
const res = await page.goto(path);
expect(res?.status()).toBe(200);
await expect(page.locator('nav').first()).toBeVisible();
const duplicates = await page.evaluate(() => {
const counts = new Map<string, number>();
for (const el of document.querySelectorAll('[id]')) {
counts.set(el.id, (counts.get(el.id) ?? 0) + 1);
}
return [...counts].filter(([, n]) => n > 1).map(([id]) => id);
});
expect(duplicates).toEqual([]);
});
}
78 changes: 45 additions & 33 deletions apps/web/src/components/elementary/LocaleSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client';

import React, { useState, useRef, useEffect, useTransition } from 'react';
import React, {
useState,
useRef,
useEffect,
useId,
useTransition,
} from 'react';
import { useLocale, useTranslations } from 'next-intl';
import { FaChevronDown } from 'react-icons/fa6';

Expand All @@ -27,38 +33,44 @@ const FlagVN = () => (
</svg>
);

const FlagEN = () => (
<svg
width="18"
height="12"
viewBox="0 0 60 30"
style={{
borderRadius: '2px',
display: 'inline-block',
flexShrink: 0,
boxShadow: '0 0 1px rgba(0,0,0,0.3)',
}}
>
<clipPath id="flag-en-s">
<path d="M0,0 v30 h60 v-30 z" />
</clipPath>
<clipPath id="flag-en-t">
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z" />
</clipPath>
<g clipPath="url(#flag-en-s)">
<path d="M0,0 v30 h60 v-30 z" fill="#012169" />
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" strokeWidth="6" />
<path
d="M0,0 L60,30 M60,0 L0,30"
clipPath="url(#flag-en-t)"
stroke="#C8102E"
strokeWidth="4"
/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" strokeWidth="10" />
<path d="M30,0 v30 M0,15 h60" stroke="#C8102E" strokeWidth="6" />
</g>
</svg>
);
// The switcher renders twice (phone and desktop), so the clip ids must be
// unique per instance: fixed ids were duplicated on every page and one copy's
// url(#...) resolved into the other (DOS/Rate#1079).
const FlagEN = () => {
const clipId = useId();
return (
<svg
width="18"
height="12"
viewBox="0 0 60 30"
style={{
borderRadius: '2px',
display: 'inline-block',
flexShrink: 0,
boxShadow: '0 0 1px rgba(0,0,0,0.3)',
}}
>
<clipPath id={`${clipId}s`}>
<path d="M0,0 v30 h60 v-30 z" />
</clipPath>
<clipPath id={`${clipId}t`}>
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z" />
</clipPath>
<g clipPath={`url(#${clipId}s)`}>
<path d="M0,0 v30 h60 v-30 z" fill="#012169" />
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" strokeWidth="6" />
<path
d="M0,0 L60,30 M60,0 L0,30"
clipPath={`url(#${clipId}t)`}
stroke="#C8102E"
strokeWidth="4"
/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" strokeWidth="10" />
<path d="M30,0 v30 M0,15 h60" stroke="#C8102E" strokeWidth="6" />
</g>
</svg>
);
};

const FlagCS = () => (
<svg
Expand Down
Loading