Skip to content

Commit 61a1fa6

Browse files
authored
MinimalFooter a11y fixes and social link reordering (#994)
* refactored and simplified MinimalFooter social icons * increase touch area size of social links to 24px * increase contrast of social icons * add changeset * respect order of social links * add changeset * update changeset * remove unused css * update snapshots * update changeset * update stories * refactor type * only update icon colours in light mode * github-actions[bot] Regenerated snapshots --------- Co-authored-by: joshfarrant <[email protected]>
1 parent ce2973b commit 61a1fa6

8 files changed

+138
-114
lines changed

.changeset/fresh-carrots-fly.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@primer/react-brand': minor
3+
---
4+
5+
`MinimalFooter` now respects the ordering of the `socialLinks` prop when rendering social links, e.g. `<MinimalFooter socialLinks={['x', 'tiktok', 'youtube']} />` will render the links in that order.
6+
7+
Note: This may constitute a visual breaking change if you were relying on the social link order to _not_ be respected.

.changeset/olive-candles-share.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@primer/react-brand': patch
3+
---
4+
5+
- Increased minimum touch target size of social icons in the `MinimalFooter` component to 24px.
6+
- Increased contrast of social icons in the `MinimalFooter` component to over 3:1.

packages/react/src/MinimalFooter/MinimalFooter.features.stories.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ export const NoSocialLinks = () => <MinimalFooter socialLinks={false} />
8080

8181
export const FilteredSocialLinks = () => <MinimalFooter socialLinks={['facebook', 'x']} />
8282

83+
export const ReversedSocialLinks = () => (
84+
<MinimalFooter socialLinks={['x', 'github', 'linkedin', 'youtube', 'facebook', 'twitch', 'tiktok', 'instagram']} />
85+
)
86+
8387
export const DefaultNarrow = () => (
8488
<MinimalFooter>
8589
<MinimalFooter.Footnotes>

packages/react/src/MinimalFooter/MinimalFooter.module.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@
5959
padding-bottom: var(--base-size-48);
6060
}
6161

62+
.Footer__social-link {
63+
position: relative;
64+
}
65+
66+
.Footer__social-link::before {
67+
content: '';
68+
display: block;
69+
position: absolute;
70+
min-width: var(--base-size-24);
71+
min-height: var(--base-size-24);
72+
}
73+
6274
.Footer__social-link:hover .Footer__social-icon,
6375
.Footer__social-link:focus-visible .Footer__social-icon {
6476
filter: var(--brand-footer-socialIcon-hoverFilter);
@@ -69,6 +81,10 @@
6981
height: auto;
7082
}
7183

84+
[data-color-mode='light'] .Footer__social-icon {
85+
filter: brightness(95%) saturate(100%);
86+
}
87+
7288
/**
7389
* Footer legal section
7490
*/

packages/react/src/MinimalFooter/MinimalFooter.tsx

Lines changed: 96 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,75 @@ import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/compone
1515
*/
1616
import styles from './MinimalFooter.module.css'
1717

18-
export const MinimalFooterSocialLinks = [
19-
'x',
20-
'github',
21-
'linkedin',
22-
'youtube',
23-
'facebook',
24-
'twitch',
25-
'tiktok',
26-
'instagram',
27-
] as const
18+
const socialLinkData = {
19+
x: {
20+
fullName: 'X',
21+
url: 'https://x.com/github',
22+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/x.svg',
23+
iconWidth: 20,
24+
iconHeight: 16,
25+
},
26+
github: {
27+
fullName: 'GitHub',
28+
url: 'https://github.com/github',
29+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/github-mark.svg',
30+
iconWidth: 20,
31+
iconHeight: 20,
32+
},
33+
linkedin: {
34+
fullName: 'LinkedIn',
35+
url: 'https://www.linkedin.com/company/github',
36+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/linkedin.svg',
37+
iconWidth: 19,
38+
iconHeight: 18,
39+
},
40+
youtube: {
41+
fullName: 'YouTube',
42+
url: 'https://www.youtube.com/github',
43+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/youtube.svg',
44+
iconWidth: 23,
45+
iconHeight: 16,
46+
},
47+
facebook: {
48+
fullName: 'Facebook',
49+
url: 'https://www.facebook.com/GitHub',
50+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/facebook.svg',
51+
iconWidth: 18,
52+
iconHeight: 18,
53+
},
54+
twitch: {
55+
fullName: 'Twitch',
56+
url: 'https://www.twitch.tv/github',
57+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/twitch.svg',
58+
iconWidth: 18,
59+
iconHeight: 18,
60+
},
61+
tiktok: {
62+
fullName: 'TikTok',
63+
url: 'https://www.tiktok.com/@github',
64+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/tiktok.svg',
65+
iconWidth: 18,
66+
iconHeight: 18,
67+
},
68+
instagram: {
69+
fullName: 'Instagram',
70+
url: 'https://www.instagram.com/github/',
71+
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/instagram.svg',
72+
iconWidth: 24,
73+
iconHeight: 24,
74+
},
75+
} as const
2876

29-
type SocialLinks = (typeof MinimalFooterSocialLinks)[number]
77+
type SocialLinkName = keyof typeof socialLinkData
78+
type SocialLink = (typeof socialLinkData)[SocialLinkName]
79+
80+
const socialLinkNames = Object.keys(socialLinkData) as SocialLinkName[]
3081

3182
export type MinimalFooterProps = {
3283
/**
3384
* An array of social links to be displayed in the footer.
3485
*/
35-
socialLinks?: SocialLinks[] | false
86+
socialLinks?: SocialLinkName[] | false
3687
/**
3788
* The href for the GitHub logo.
3889
*/
@@ -149,104 +200,40 @@ function Footnotes({children, className}: PropsWithChildren<FootnoteProps>) {
149200
)
150201
}
151202

203+
type SocialLinkProps = {name: SocialLinkName}
204+
205+
const SocialLink = ({name}: SocialLinkProps) => {
206+
const link = socialLinkData[name]
207+
return (
208+
<li key={name}>
209+
<a
210+
href={link.url}
211+
className={styles['Footer__social-link']}
212+
data-analytics-event={`{"category":"Footer","action":"go to ${link.fullName}","label":"text:${name}"}`}
213+
>
214+
<img
215+
className={styles['Footer__social-icon']}
216+
src={link.icon}
217+
height={link.iconHeight}
218+
width={link.iconWidth}
219+
loading="lazy"
220+
decoding="async"
221+
alt=""
222+
/>
223+
<span className="visually-hidden">GitHub on {link.fullName}</span>
224+
</a>
225+
</li>
226+
)
227+
}
228+
152229
type SocialLogomarksProps = {
153-
socialLinks?: SocialLinks[] | false
230+
socialLinks?: SocialLinkName[] | false
154231
logoHref?: string
155232
}
156233

157-
function SocialLogomarks({socialLinks, logoHref}: SocialLogomarksProps) {
234+
function SocialLogomarks({socialLinks = socialLinkNames, logoHref}: SocialLogomarksProps) {
158235
const {colorMode} = useTheme()
159236

160-
const socialLinkData = [
161-
{
162-
name: 'x',
163-
fullName: 'X',
164-
url: 'https://x.com/github',
165-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/x.svg',
166-
iconWidth: 20,
167-
iconHeight: 16,
168-
},
169-
{
170-
name: 'github',
171-
fullName: 'GitHub',
172-
url: 'https://github.com/github',
173-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/github-mark.svg',
174-
iconWidth: 20,
175-
iconHeight: 20,
176-
},
177-
{
178-
name: 'linkedin',
179-
fullName: 'LinkedIn',
180-
url: 'https://www.linkedin.com/company/github',
181-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/linkedin.svg',
182-
iconWidth: 19,
183-
iconHeight: 18,
184-
},
185-
{
186-
name: 'youtube',
187-
fullName: 'YouTube',
188-
url: 'https://www.youtube.com/github',
189-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/youtube.svg',
190-
iconWidth: 23,
191-
iconHeight: 16,
192-
},
193-
{
194-
name: 'facebook',
195-
fullName: 'Facebook',
196-
url: 'https://www.facebook.com/GitHub',
197-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/facebook.svg',
198-
iconWidth: 18,
199-
iconHeight: 18,
200-
},
201-
{
202-
name: 'twitch',
203-
fullName: 'Twitch',
204-
url: 'https://www.twitch.tv/github',
205-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/twitch.svg',
206-
iconWidth: 18,
207-
iconHeight: 18,
208-
},
209-
{
210-
name: 'tiktok',
211-
fullName: 'TikTok',
212-
url: 'https://www.tiktok.com/@github',
213-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/tiktok.svg',
214-
iconWidth: 18,
215-
iconHeight: 18,
216-
},
217-
{
218-
name: 'instagram',
219-
fullName: 'Instagram',
220-
url: 'https://www.instagram.com/github/',
221-
icon: 'https://static-gh.moeloli.ml/images/modules/site/icons/footer/instagram.svg',
222-
iconWidth: 24,
223-
iconHeight: 24,
224-
},
225-
]
226-
227-
const renderLink = (link: (typeof socialLinkData)[number]) => {
228-
return (
229-
<li key={link.name}>
230-
<a
231-
href={link.url}
232-
className={styles['Footer__social-link']}
233-
data-analytics-event={`{"category":"Footer","action":"go to ${link.fullName}","label":"text:${link.name}"}`}
234-
>
235-
<img
236-
className={styles['Footer__social-icon']}
237-
src={link.icon}
238-
height={link.iconHeight}
239-
width={link.iconWidth}
240-
loading="lazy"
241-
decoding="async"
242-
alt=""
243-
/>
244-
<span className="visually-hidden">GitHub on {link.fullName}</span>
245-
</a>
246-
</li>
247-
)
248-
}
249-
250237
return (
251238
<section className={clsx(styles['Footer__logomarks'])}>
252239
<div className={styles['Footer__container']}>
@@ -266,18 +253,13 @@ function SocialLogomarks({socialLinks, logoHref}: SocialLogomarksProps) {
266253
<LogoGithubIcon fill={colorMode === ColorModesEnum.DARK ? 'white' : 'black'} size="medium" />
267254
</a>
268255
</div>
269-
{socialLinks !== false ? (
256+
{socialLinks ? (
270257
<ul className={styles['Footer__social-links']}>
271-
{socialLinkData.map((link: (typeof socialLinkData)[number]) => {
272-
if (socialLinks && !socialLinks.includes(link.name as SocialLinks)) {
273-
return null
274-
}
275-
return renderLink(link)
276-
})}
258+
{socialLinks.map(name => (
259+
<SocialLink key={name} name={name} />
260+
))}
277261
</ul>
278-
) : (
279-
<></>
280-
)}
262+
) : null}
281263
</Stack>
282264
</div>
283265
</section>

packages/react/src/MinimalFooter/MinimalFooter.visual.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ test.describe('Visual Comparison: MinimalFooter', () => {
6666
expect(await page.screenshot({fullPage: true})).toMatchSnapshot()
6767
})
6868

69+
test('MinimalFooter / Reversed Social Links', async ({page}) => {
70+
await page.goto(
71+
'http://localhost:6006/iframe.html?args=&id=components-minimalfooter-features--reversed-social-links&viewMode=story',
72+
)
73+
74+
await page.waitForTimeout(500)
75+
expect(await page.screenshot({fullPage: true})).toMatchSnapshot()
76+
})
77+
6978
// eslint-disable-next-line i18n-text/no-en
7079
test.describe('Mobile viewport test for Default (Narrow viewport)', () => {
7180
test.use({viewport: {width: 360, height: 800}})

0 commit comments

Comments
 (0)