Skip to content

Commit ed8bfb2

Browse files
committed
ntp: new show/hide buttons
1 parent b51367f commit ed8bfb2

File tree

14 files changed

+211
-150
lines changed

14 files changed

+211
-150
lines changed

special-pages/pages/new-tab/app/components/Icons.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export function ChevronButton() {
1717

1818
export function Chevron() {
1919
return (
20-
<svg fill="none" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
20+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2121
<path
22-
fill="currentColor"
2322
fill-rule="evenodd"
24-
d="M3.293 7.793a1 1 0 0 0 0 1.414l8 8a1 1 0 0 0 1.414 0l8-8a1 1 0 0 0-1.414-1.414L12 15.086 4.707 7.793a1 1 0 0 0-1.414 0Z"
2523
clip-rule="evenodd"
24+
d="M12.0694 9.48822C11.7999 9.80271 11.3264 9.83913 11.0119 9.56956L7.99999 6.98793L4.98808 9.56956C4.67359 9.83913 4.20011 9.80271 3.93054 9.48822C3.66098 9.17372 3.6974 8.70025 4.01189 8.43068L7.51189 5.43068C7.79276 5.18994 8.20721 5.18994 8.48808 5.43068L11.9881 8.43068C12.3026 8.70025 12.339 9.17372 12.0694 9.48822Z"
25+
fill="currentColor"
2626
/>
2727
</svg>
2828
);
Lines changed: 98 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
.button {
2-
opacity: 0;
3-
transition: opacity 0.3s;
42
cursor: pointer;
53
background: none;
64
border: none;
@@ -9,108 +7,130 @@
97
justify-content: center;
108
align-items: center;
119
color: var(--ntp-text-normal);
12-
height: var(--ntp-gap);
13-
width: 100%;
14-
border-radius: var(--border-radius-sm);
15-
16-
&.round {
17-
height: 2rem;
18-
width: 2rem;
19-
border-radius: 50%;
20-
padding-inline: 0;
21-
background-color: transparent;
22-
color: var(--ntp-text-muted);
2310

11+
> * {
12+
pointer-events: none;
13+
}
2414

25-
.iconBlock {
26-
backdrop-filter: unset;
27-
background-color: transparent;
28-
box-shadow: none;
29-
transition: all 0.3s ease-in;
30-
31-
[data-theme=dark] & {
32-
box-shadow: none;
33-
background-color: transparent;
34-
}
35-
}
15+
svg {
16+
transition: transform .3s;
17+
}
3618

37-
&:hover {
38-
.iconBlock {
39-
background-color: var(--color-black-at-6);
19+
&[aria-pressed=false] svg {
20+
transform: rotate(-180deg);
21+
}
4022

41-
[data-theme=dark] & {
42-
background-color: var(--color-white-at-12);
43-
}
44-
}
45-
}
23+
&:focus-visible {
24+
box-shadow: var(--focus-ring-thin);
25+
}
26+
}
27+
28+
.iconBlock {
29+
backdrop-filter: blur(48px);
30+
background-color: var(--ntp-surface-background-color);
31+
border-radius: 50%;
32+
height: 1.5rem;
33+
width: 1.5rem;
34+
display: flex;
35+
align-items: center;
36+
justify-content: center;
37+
box-shadow: 0px 2px 4px 0px var(--color-black-at-12), 0px 0px 3px 0px var(--color-black-at-18);
38+
color: var(--ntp-text-muted);
4639

47-
&:focus-visible {
48-
box-shadow: var(--focus-ring);
49-
}
40+
[data-theme="dark"] & {
41+
box-shadow: 0px 2px 4px 0px var(--color-white-at-6), 0px 0px 3px 0px var(--color-white-at-9);
5042
}
43+
}
5144

52-
&.withText {
53-
border: 1px solid var(--color-black-at-9);
45+
.round {
46+
height: 2rem;
47+
width: 2rem;
48+
border-radius: 50%;
49+
padding-inline: 0;
50+
background-color: transparent;
51+
color: var(--ntp-text-muted);
5452

55-
svg {
56-
margin-right: var(--sp-2);
57-
}
53+
.iconBlock {
54+
backdrop-filter: unset;
55+
background-color: transparent;
56+
box-shadow: none;
57+
transition: all 0.3s ease-in;
5858

59-
&:hover {
60-
background-color: var(--color-black-at-9);
59+
[data-theme=dark] & {
60+
box-shadow: none;
61+
background-color: transparent;
6162
}
63+
}
6264

63-
&:active {
64-
background-color: var(--color-black-at-12);
65+
&:hover {
66+
.iconBlock {
67+
background-color: var(--color-black-at-6);
68+
69+
[data-theme=dark] & {
70+
background-color: var(--color-white-at-12);
71+
}
6572
}
6673
}
6774

68-
>* {
69-
pointer-events: none;
75+
&:focus-visible {
76+
box-shadow: var(--focus-ring);
7077
}
78+
}
79+
80+
.pill {
81+
height: calc(26 * var(--px-in-rem));
82+
border-radius: 9999px;
83+
padding-left: 8px;
84+
padding-right: 11px;
85+
font-size: var(--callout-font-size);
86+
font-weight: var(--callout-font-weight);
87+
line-height: var(--callout-line-height);
88+
border: 1px solid var(--ntp-surface-border-color);
89+
backdrop-filter: blur(48px);
90+
background-color: var(--ntp-surface-background-color);
91+
color: var(--ntp-text-muted);
92+
}
93+
94+
.full {
95+
width: 100%;
96+
border-radius: var(--border-radius-sm);
97+
height: var(--ntp-gap);
98+
border: 1px solid var(--color-black-at-9);
99+
}
71100

72-
svg {
73-
transition: transform .3s;
74-
}
101+
.hover {
102+
transition: background-color .2s;
75103

76-
.iconBlock {
77-
backdrop-filter: blur(48px);
78-
background-color: var(--ntp-surface-background-color);
79-
border-radius: 50%;
80-
height: 1.5rem;
81-
width: 1.5rem;
82-
display: flex;
83-
align-items: center;
84-
justify-content: center;
85-
box-shadow: 0px 2px 4px 0px var(--color-black-at-12), 0px 0px 3px 0px var(--color-black-at-18);
86-
color: var(--ntp-text-muted);
87-
88-
89-
[data-theme="dark"] & {
90-
box-shadow: 0px 2px 4px 0px var(--color-white-at-6), 0px 0px 3px 0px var(--color-white-at-9);
91-
}
104+
svg {
105+
margin-right: calc(6 * var(--px-in-rem));
92106
}
93107

94-
&[aria-pressed=true] svg {
95-
transform: rotate(-180deg);
108+
&:hover {
109+
background-color: var(--color-black-at-6);
96110
}
97111

98-
&:focus-visible {
99-
opacity: 1;
100-
box-shadow: var(--focus-ring-thin);
112+
&:active {
113+
background-color: var(--color-black-at-12);
101114
}
102115

103116
[data-theme=dark] & {
104-
&.withText {
105-
border-color: var(--color-white-at-9);
117+
border-color: var(--color-white-at-9);
106118

107-
&:hover {
108-
background-color: var(--color-white-at-9);
109-
}
119+
&:hover {
120+
border-color: var(--color-white-at-18);
121+
background-color: var(--color-white-at-6);
122+
}
110123

111-
&:active {
112-
background-color: var(--color-white-at-12);
113-
}
124+
&:active {
125+
background-color: var(--color-white-at-12);
114126
}
115127
}
128+
}
129+
130+
.bar {
131+
padding-top: 11px;
132+
padding-bottom: 11px;
133+
display: flex;
134+
justify-content: center;
135+
font-size: 12px;
116136
}
Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,78 @@
11
import styles from './ShowHide.module.css';
22
import cn from 'classnames';
33
import { Chevron } from './Icons.js';
4-
import { Fragment, h } from 'preact';
4+
import { h } from 'preact';
55

66
/**
77
* Function to handle showing or hiding content based on certain conditions.
88
*
99
* @param {Object} props - Input parameters for controlling the behavior of the ShowHide functionality.
10+
* @param {string} props.label
11+
* @param {() => void} props.onClick
12+
* @param {import("preact").ComponentProps<'button'>} [props.buttonAttrs]
13+
*/
14+
export function ShowHideButtonCircle({ label, onClick, buttonAttrs = {} }) {
15+
return (
16+
<button {...buttonAttrs} class={cn(styles.button, styles.round)} aria-label={label} data-toggle="true" onClick={onClick}>
17+
<div class={styles.iconBlock}>
18+
<Chevron />
19+
</div>
20+
</button>
21+
);
22+
}
23+
24+
/**
25+
* Use this version for a bar that will fill its container.
26+
* @param {object} props
27+
* @param {string} props.text
28+
* @param {() => void} props.onClick
29+
* @param {import("preact").ComponentProps<'button'>} [props.buttonAttrs]
30+
*/
31+
export function ShowHideButtonFullWidth({ onClick, text, buttonAttrs = {} }) {
32+
return (
33+
<button {...buttonAttrs} class={cn(styles.button, styles.hover, styles.full)} data-toggle="true" onClick={onClick}>
34+
<Chevron />
35+
{text}
36+
</button>
37+
);
38+
}
39+
40+
/**
41+
* Use this version for a small pill version with text and option aria-label
42+
* @param {object} props
1043
* @param {string} props.text
44+
* @param {string|undefined} props.label
1145
* @param {() => void} props.onClick
12-
* @param {'none'|'round'} [props.shape] - when "none", is a full width btn w/ icon inside (used for below Favorites and NextSteps), Round is the PrivacyStats heading button
13-
* @param {boolean} [props.showText] - btn w/ icon and text (used to expand PrivacyStats list), should be used with shape="none"
1446
* @param {import("preact").ComponentProps<'button'>} [props.buttonAttrs]
1547
*/
16-
export function ShowHideButton({ text, onClick, buttonAttrs = {}, shape = 'none', showText = false }) {
48+
export function ShowHideButtonPill({ label, onClick, text, buttonAttrs = {} }) {
49+
// if a different label was given, make the main text aria-hidden=true
50+
const btnText = label ? <span aria-hidden="true">{text}</span> : text;
51+
1752
return (
1853
<button
1954
{...buttonAttrs}
20-
class={cn(styles.button, shape === 'round' && styles.round, !!showText && styles.withText)}
21-
aria-label={text}
55+
aria-label={label}
56+
class={cn(styles.button, styles.hover, styles.pill)}
2257
data-toggle="true"
2358
onClick={onClick}
2459
>
25-
{showText ? (
26-
<Fragment>
27-
<Chevron />
28-
{text}
29-
</Fragment>
30-
) : (
31-
<div class={styles.iconBlock}>
32-
<Chevron />
33-
</div>
34-
)}
60+
<Chevron />
61+
{btnText}
3562
</button>
3663
);
3764
}
65+
66+
/**
67+
* A container you can place a <ShowHideButtonPill /> into.
68+
* Consumers can use the `data-show-hide` to show/hide the bar
69+
* @param {object} props
70+
* @param {import("preact").ComponentChild} props.children
71+
*/
72+
export function ShowHideBar({ children }) {
73+
return (
74+
<div class={styles.bar} data-show-hide>
75+
{children}
76+
</div>
77+
);
78+
}

special-pages/pages/new-tab/app/favorites/components/Favorites.js

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { createContext, Fragment, h } from 'preact';
22
import { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
33
import { memo } from 'preact/compat';
4-
import cn from 'classnames';
54

65
import styles from './Favorites.module.css';
7-
import { ShowHideButton } from '../../components/ShowHideButton.jsx';
6+
import { ShowHideBar, ShowHideButtonPill } from '../../components/ShowHideButton.jsx';
87
import { useTypedTranslationWith } from '../../types.js';
98
import { usePlatformName } from '../../settings.provider.js';
109
import { useDropzoneSafeArea } from '../../dropzone.js';
@@ -67,11 +66,7 @@ export function Favorites({ gridRef, favorites, expansion, toggle, openContextMe
6766

6867
return (
6968
<FavoritesThemeContext.Provider value={{ theme: main.value, animateItems }}>
70-
<div
71-
class={cn(styles.root, !canToggleExpansion && styles.noExpansionBtn)}
72-
data-testid="FavoritesConfigured"
73-
data-background-kind={kind}
74-
>
69+
<div class={styles.root} data-testid="FavoritesConfigured" data-background-kind={kind}>
7570
<VirtualizedGridRows
7671
WIDGET_ID={WIDGET_ID}
7772
favorites={favorites}
@@ -82,27 +77,23 @@ export function Favorites({ gridRef, favorites, expansion, toggle, openContextMe
8277
openContextMenu={openContextMenu}
8378
/>
8479
{canToggleExpansion && (
85-
<div
86-
className={cn({
87-
[styles.showhide]: true,
88-
[styles.showhideVisible]: canToggleExpansion,
89-
})}
90-
>
91-
<ShowHideButton
80+
<ShowHideBar>
81+
<ShowHideButtonPill
9282
buttonAttrs={{
9383
'aria-expanded': expansion === 'expanded',
9484
'aria-pressed': expansion === 'expanded',
9585
'aria-controls': WIDGET_ID,
9686
id: TOGGLE_ID,
9787
}}
98-
text={
88+
text={expansion === 'expanded' ? t('ntp_show_less') : t('ntp_show_more')}
89+
label={
9990
expansion === 'expanded'
10091
? t('favorites_show_less')
10192
: t('favorites_show_more', { count: String(hiddenCount) })
10293
}
10394
onClick={toggle}
10495
/>
105-
</div>
96+
</ShowHideBar>
10697
)}
10798
</div>
10899
</FavoritesThemeContext.Provider>

0 commit comments

Comments
 (0)