Skip to content

Commit f8898f5

Browse files
authored
history: ship review changes (#1522)
1 parent 17297e5 commit f8898f5

26 files changed

+751
-296
lines changed

special-pages/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"build": "node index.mjs",
1111
"build.dev": "npm run build -- --env development",
1212
"lint-fix": "cd ../ && npm run lint-fix",
13-
"test-unit": "node --test unit-test/translations.mjs unit-test/color.spec.mjs pages/duckplayer/unit-tests/embed-settings.mjs pages/new-tab/app/freemium-pir-banner/unit-tests/utils.spec.mjs",
13+
"test-unit": "node --test \"unit-test/*\" \"pages/history/unit-tests/*\" \"pages/duckplayer/unit-tests/*\" \"pages/new-tab/app/freemium-pir-banner/unit-tests/*\"",
1414
"test-int": "npm run test-unit && npm run build.dev && playwright test --grep-invert '@screenshots'",
1515
"test-int-x": "npm run test-int",
1616
"test.screenshots": "npm run test-unit && npm run build.dev && playwright test --grep '@screenshots'",

special-pages/pages/history/app/components/App.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ import { useSearchCommitForRange } from '../global/hooks/useSearchCommitForRange
1717
import { useURLReflection } from '../global/hooks/useURLReflection.js';
1818
import { useSearchCommit } from '../global/hooks/useSearchCommit.js';
1919
import { useRangesData } from '../global/Providers/HistoryServiceProvider.js';
20+
import { usePlatformName } from '../types.js';
21+
import { useLayoutMode } from '../global/hooks/useLayoutMode.js';
2022

2123
export function App() {
24+
const platformName = usePlatformName();
2225
const mainRef = useRef(/** @type {HTMLElement|null} */ (null));
2326
const { isDarkMode } = useEnv();
2427
const ranges = useRangesData();
2528
const query = useQueryContext();
29+
const mode = useLayoutMode();
2630

2731
/**
2832
* Handlers that are global in nature
@@ -58,7 +62,7 @@ export function App() {
5862
}, [onKeyDown, query]);
5963

6064
return (
61-
<div class={styles.layout} data-theme={isDarkMode ? 'dark' : 'light'}>
65+
<div class={styles.layout} data-theme={isDarkMode ? 'dark' : 'light'} data-platform={platformName} data-layout-mode={mode}>
6266
<aside class={styles.aside}>
6367
<Sidebar ranges={ranges} />
6468
</aside>

special-pages/pages/history/app/components/App.module.css

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@ body {
1010
}
1111

1212
.layout {
13+
--sidebar-width: 250px;
14+
--main-padding-left: 48px;
15+
--main-padding-right: 76px;
16+
--windows-scrollbar: 15px;
17+
18+
&[data-layout-mode="reduced"] {
19+
--sidebar-width: 230px;
20+
--main-padding-left: 28px;
21+
--main-padding-right: 56px;
22+
}
23+
1324
display: grid;
14-
grid-template-columns: 250px 1fr;
25+
grid-template-columns: var(--sidebar-width) 1fr;
1526
grid-template-rows: max-content 1fr;
1627
grid-template-areas:
1728
'aside header'
@@ -22,8 +33,8 @@ body {
2233
}
2334
.header {
2435
grid-area: header;
25-
padding-left: 48px;
26-
padding-right: 76px;
36+
padding-left: var(--main-padding-left);
37+
padding-right: var(--main-padding-right);
2738
}
2839
.search {
2940
justify-self: flex-end;
@@ -36,24 +47,17 @@ body {
3647
.main {
3748
grid-area: main;
3849
overflow: auto;
39-
padding-left: 48px;
40-
padding-right: 76px;
50+
padding-left: var(--main-padding-left);
51+
padding-right: var(--main-padding-right);
4152
padding-top: 24px;
4253
}
43-
4454
.customScroller {
4555
overflow-y: scroll;
46-
&::-webkit-scrollbar {
47-
width: 12px;
48-
}
49-
50-
&::-webkit-scrollbar-track {
51-
border-radius: 6px;
52-
}
56+
scrollbar-gutter: stable;
57+
}
5358

54-
&::-webkit-scrollbar-thumb {
55-
background: rgb(108, 108, 108);
56-
border: 4px solid var(--history-background-color);
57-
border-radius: 6px;
59+
[data-platform="windows"] {
60+
.customScroller {
61+
padding-right: calc(var(--main-padding-right) - var(--windows-scrollbar));
5862
}
5963
}

special-pages/pages/history/app/components/Header.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function Controls({ term, range, domain }) {
8484

8585
return (
8686
<div class={styles.controls}>
87-
<button class={styles.largeButton} onClick={onClick} aria-disabled={ariaDisabled} title={title}>
87+
<button class={styles.largeButton} onClick={onClick} aria-disabled={ariaDisabled} title={title} tabindex={0}>
8888
<Trash />
8989
<span>{buttonTxt}</span>
9090
</button>

special-pages/pages/history/app/components/Item.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ export const Item = memo(
5353
<a href={props.url} data-url={props.url} class={styles.entryLink} tabindex={0}>
5454
{title}
5555
</a>
56-
<span class={styles.domain} data-testid="Item.domain">
56+
<span class={styles.domain} data-testid="Item.domain" title={props.domain}>
5757
{props.domain}
5858
</span>
5959
<span class={styles.time}>{dateTimeOfDay}</span>
60-
<button class={styles.dots} data-action={BTN_ACTION_ENTRIES_MENU} data-index={index} value={props.id} tabindex={0}>
60+
<button class={styles.dots} data-action={BTN_ACTION_ENTRIES_MENU} data-index={index} value={props.id} tabindex={-1}>
6161
<Dots />
6262
</button>
6363
</div>

special-pages/pages/history/app/components/Item.module.css

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
.item {
2+
23
}
34

45
.title {
5-
height: 32px;
6+
--title-height: 32px;
7+
height: var(--title-height);
68
width: 100%;
79
font-size: var(--title-3-em-font-size);
810
font-weight: var(--title-3-em-font-weight);
@@ -17,7 +19,8 @@
1719
}
1820

1921
.row {
20-
height: 28px;
22+
--row-height: 28px;
23+
height: var(--row-height);
2124
display: flex;
2225
gap: 8px;
2326
align-items: center;
@@ -39,7 +42,7 @@
3942
color: var(--row-color);
4043
border-radius: var(--row-radius);
4144

42-
&:hover, &:focus-within {
45+
&:hover {
4346
--dots-opacity: visible;
4447
--time-opacity: 0;
4548
--time-visibility: hidden;
@@ -63,28 +66,31 @@
6366
}
6467
}
6568

66-
/**
67-
* The following code handles handles the multi-row selections. It's responsible
68-
* for ensuring only the first and last elements in a selection have rounded corners.
69-
*/
70-
[data-is-selected='true'] .row {
71-
border-radius: 0;
72-
}
69+
@supports selector(:has(*)) {
70+
/**
71+
* The following code handles handles the multi-row selections. It's responsible
72+
* for ensuring only the first and last elements in a selection have rounded corners.
73+
*/
74+
[data-is-selected='true'] .row {
75+
border-radius: 0;
76+
}
7377

74-
[data-is-selected='true']:first-of-type .row,
75-
[data-is-selected='true']:not([data-is-selected='true'] + [data-is-selected='true']) .row
76-
{
77-
border-top-left-radius: var(--row-radius);
78-
border-top-right-radius: var(--row-radius);
79-
}
78+
[data-is-selected='true']:first-of-type .row,
79+
[data-is-selected='true']:not([data-is-selected='true'] + [data-is-selected='true']) .row
80+
{
81+
border-top-left-radius: var(--row-radius);
82+
border-top-right-radius: var(--row-radius);
83+
}
8084

81-
[data-is-selected='true']:last-of-type .row,
82-
[data-is-selected='true']:not(:has(+ [data-is-selected='true'])) .row
83-
{
84-
border-bottom-left-radius: var(--row-radius);
85-
border-bottom-right-radius: var(--row-radius);
85+
[data-is-selected='true']:last-of-type .row,
86+
[data-is-selected='true']:not(:has(+ [data-is-selected='true'])) .row
87+
{
88+
border-bottom-left-radius: var(--row-radius);
89+
border-bottom-right-radius: var(--row-radius);
90+
}
8691
}
8792

93+
8894
.favicon {
8995
flex-shrink: 0;
9096
min-width: 0;
@@ -99,6 +105,7 @@
99105
text-overflow: ellipsis;
100106
text-decoration: none;
101107
color: inherit;
108+
line-height: var(--row-height);
102109
}
103110

104111
.domain {
@@ -110,7 +117,7 @@
110117
overflow: hidden;
111118
text-overflow: ellipsis;
112119

113-
@media screen and (min-width: 800px) {
120+
[data-layout-mode="normal"] & {
114121
flex-shrink: 0;
115122
}
116123

@@ -124,6 +131,8 @@
124131
flex-shrink: 0;
125132
opacity: var(--time-opacity);
126133
visibility: var(--time-visibility);
134+
font-feature-settings: "tnum";
135+
font-variant-numeric: tabular-nums;
127136
}
128137

129138
.dots {
@@ -148,10 +157,6 @@
148157
&:hover {
149158
background: var(--dots-bg-hover);
150159
}
151-
152-
&:focus-visible {
153-
opacity: 1;
154-
}
155160
}
156161

157162
.last {

special-pages/pages/history/app/components/Results.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,31 @@ import { Item } from './Item.js';
44
import styles from './VirtualizedList.module.css';
55
import { VisibleItems } from './VirtualizedList.js';
66
import { Empty } from './Empty.js';
7-
import { useSelected } from '../global/Providers/SelectionProvider.js';
7+
import { useSelected, useSelectionState } from '../global/Providers/SelectionProvider.js';
88
import { useHistoryServiceDispatch, useResultsData } from '../global/Providers/HistoryServiceProvider.js';
9-
import { useCallback } from 'preact/hooks';
9+
import { useCallback, useEffect } from 'preact/hooks';
1010

1111
/**
1212
* Access global state and render the results
1313
*/
1414
export function ResultsContainer() {
1515
const results = useResultsData();
1616
const selected = useSelected();
17+
const selectionState = useSelectionState();
1718
const dispatch = useHistoryServiceDispatch();
19+
20+
/**
21+
* When the selection state changed in a way that might cause an element to be off-screen, re-focus it
22+
*/
23+
useEffect(() => {
24+
return selectionState.subscribe(({ lastAction, focusedIndex }) => {
25+
if (lastAction === 'move-selection' || lastAction === 'inc-or-dec-selected') {
26+
const match = document.querySelector(`[aria-selected][data-index="${focusedIndex}"]`);
27+
match?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
28+
}
29+
});
30+
}, [selectionState]);
31+
1832
/**
1933
* Let the history service know when it might want to load more
2034
*/

special-pages/pages/history/app/components/SearchForm.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function useSearchShortcut(platformName) {
101101
* @param {string} [message]
102102
* @return {asserts condition}
103103
*/
104-
function invariant(condition, message) {
104+
export function invariant(condition, message) {
105105
if (condition) return;
106106
if (message) throw new Error('Invariant failed: ' + message);
107107
throw new Error('Invariant failed');

special-pages/pages/history/app/components/Sidebar.js

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,44 +80,53 @@ export function Sidebar({ ranges }) {
8080
<h1 class={styles.pageTitle}>{t('page_title')}</h1>
8181
<nav class={styles.nav}>
8282
{ranges.value.map((range) => {
83-
const { buttonLabel, linkLabel } = labels(range, t);
84-
return (
85-
<div class={styles.item} key={range}>
86-
<RowLink onClick={() => onClick(range)} current={current} range={range} label={linkLabel}>
87-
{titleMap[range](t)}
88-
</RowLink>
89-
{range === 'all' && (
90-
<DeleteAllButton onClick={onDelete} ariaLabel={buttonLabel} range={range} ranges={ranges} count={count} />
91-
)}
92-
{range !== 'all' && <DeleteButton onClick={() => onDelete(range)} label={buttonLabel} range={range} />}
93-
</div>
94-
);
83+
return <Item onClick={onClick} onDelete={onDelete} current={current} range={range} ranges={ranges} count={count} />;
9584
})}
9685
</nav>
9786
</div>
9887
);
9988
}
10089

101-
function RowLink({ range, current, label, children, onClick }) {
90+
/**
91+
* A component that renders a list item with optional delete actions and a link.
92+
*
93+
* @param {Object} props
94+
* @param {import('@preact/signals').ReadonlySignal<Range|null>} props.current The current selection with a value property.
95+
* @param {Range} props.range The range represented by this item.
96+
* @param {(range: Range) => void} props.onClick Callback function triggered when the range is clicked.
97+
* @param {(range: Range) => void} props.onDelete Callback function triggered when the delete action is clicked.
98+
* @param {import("@preact/signals").Signal<Range[]>} props.ranges
99+
* @param {import('@preact/signals').ReadonlySignal<number>} props.count The count value associated with the ranges.
100+
*/
101+
function Item({ current, range, onClick, onDelete, ranges, count }) {
102+
const { t } = useTypedTranslation();
103+
const { buttonLabel, linkLabel } = labels(range, t);
102104
const classNames = useComputed(() => {
103-
return cn(styles.link, current.value === range && styles.active);
105+
if (range === 'all' && current.value === null) {
106+
return cn(styles.item, styles.active);
107+
}
108+
return cn(styles.item, current.value === range && styles.active);
104109
});
110+
105111
return (
106-
<a
107-
href="#"
108-
aria-label={label}
109-
class={classNames}
110-
tabindex={0}
111-
onClick={(e) => {
112-
e.preventDefault();
113-
onClick(range);
114-
}}
115-
>
116-
<span class={styles.icon}>
117-
<img src={iconMap[range]} />
118-
</span>
119-
{children}
120-
</a>
112+
<div class={classNames} key={range}>
113+
<button
114+
aria-label={linkLabel}
115+
className={styles.link}
116+
tabIndex={0}
117+
onClick={(e) => {
118+
e.preventDefault();
119+
onClick(range);
120+
}}
121+
>
122+
<span className={styles.icon}>
123+
<img src={iconMap[range]} />
124+
</span>
125+
{titleMap[range](t)}
126+
</button>
127+
{range === 'all' && <DeleteAllButton onClick={onDelete} ariaLabel={buttonLabel} range={range} ranges={ranges} count={count} />}
128+
{range !== 'all' && <DeleteButton onClick={() => onDelete(range)} label={buttonLabel} range={range} />}
129+
</div>
121130
);
122131
}
123132

0 commit comments

Comments
 (0)