Skip to content

Commit 3c59de3

Browse files
authored
feat: MetaBar Component (#6010)
* feat: MetaBar component * fix: AvatarGroup layout shift * refactor: Unordered list to ordered list * refactor: Unnecessary condition removed * refactor: Heading depth control * refactor: Metabar type imports * refactor: More generic prop types for MetaBar component * chore: Constant naming * refactor: Link hover state styling * refactor: Heading default depth * refactor: Unnecessary constant
1 parent 930344d commit 3c59de3

File tree

5 files changed

+237
-0
lines changed

5 files changed

+237
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.avatarGroup {
22
@apply flex
3+
h-8
34
items-center;
45
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
.wrapper {
2+
@apply flex
3+
w-80
4+
flex-col
5+
items-start
6+
gap-8
7+
border-l
8+
border-neutral-200
9+
p-6
10+
dark:border-neutral-900;
11+
12+
dt {
13+
@apply mb-2
14+
text-sm
15+
font-medium
16+
text-neutral-800
17+
dark:text-neutral-200;
18+
}
19+
20+
dd {
21+
@apply mb-8
22+
flex
23+
items-center
24+
gap-2
25+
text-sm
26+
text-neutral-900
27+
dark:text-white;
28+
29+
a {
30+
@apply font-semibold
31+
text-neutral-900
32+
underline
33+
hover:text-neutral-800
34+
dark:text-white
35+
dark:hover:text-neutral-200;
36+
}
37+
38+
ol {
39+
@apply flex
40+
list-none
41+
flex-col
42+
gap-1.5
43+
p-0;
44+
}
45+
46+
svg {
47+
@apply h-4
48+
w-4
49+
text-neutral-600
50+
dark:text-neutral-400;
51+
}
52+
53+
&:last-child {
54+
@apply mb-0;
55+
}
56+
}
57+
58+
[data-on-dark] {
59+
@apply hidden
60+
dark:block;
61+
}
62+
63+
[data-on-light] {
64+
@apply block
65+
dark:hidden;
66+
}
67+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { CodeBracketIcon } from '@heroicons/react/24/outline';
2+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
3+
import Image from 'next/image';
4+
import { FormattedMessage } from 'react-intl';
5+
6+
import AvatarGroup from '@/components/Common/AvatarGroup';
7+
import LocalizedLink from '@/components/LocalizedLink';
8+
9+
import MetaBar from './index';
10+
11+
type Story = StoryObj<typeof MetaBar>;
12+
type Meta = MetaObj<typeof MetaBar>;
13+
14+
export const Default: Story = {
15+
args: {
16+
items: {
17+
'components.metabar.lastUpdated': new Date().toLocaleDateString(),
18+
'components.metabar.readingTime': '15 minutes',
19+
'components.metabar.addedIn': 'v1.0.0',
20+
'components.metabar.author': 'The Node.js Project',
21+
'components.metabar.authors': (
22+
<AvatarGroup
23+
avatars={[
24+
{
25+
src: 'https://avatars.githubusercontent.com/canerakdas',
26+
alt: 'Caner Akdas',
27+
},
28+
{
29+
src: 'https://avatars.githubusercontent.com/bmuenzenmeyer',
30+
alt: 'Brian Muenzenmeyer',
31+
},
32+
{
33+
src: 'https://avatars.githubusercontent.com/ovflowd',
34+
alt: 'Claudio W',
35+
},
36+
]}
37+
/>
38+
),
39+
'components.metabar.contribute': (
40+
<>
41+
<Image
42+
src="/static/images/logos/social-github-dark.svg"
43+
alt="GitHub Logo"
44+
width={16}
45+
height={16}
46+
data-on-light
47+
/>
48+
<Image
49+
src="/static/images/logos/social-github.svg"
50+
alt="GitHub Logo"
51+
width={16}
52+
height={16}
53+
data-on-dark
54+
/>
55+
<LocalizedLink href="/contribute">
56+
<FormattedMessage id="components.metabar.contributeText" />
57+
</LocalizedLink>
58+
</>
59+
),
60+
'components.metabar.viewAs': (
61+
<>
62+
<CodeBracketIcon />
63+
<LocalizedLink href="/json">JSON</LocalizedLink>
64+
</>
65+
),
66+
},
67+
headings: {
68+
items: [
69+
{
70+
value: 'OpenSSL update assessment, and Node.js project plans',
71+
depth: 1,
72+
data: { id: 'heading-1' },
73+
},
74+
{
75+
value: 'Summary',
76+
depth: 2,
77+
data: { id: 'summary' },
78+
},
79+
{
80+
value: 'Analysis',
81+
depth: 2,
82+
data: { id: 'analysis' },
83+
},
84+
{
85+
value: 'The c_rehash script allows command injection (CVE-2022-2068)',
86+
depth: 3,
87+
data: { id: 'the_c_rehash' },
88+
},
89+
{
90+
value: 'Contact and future updates',
91+
depth: 3,
92+
data: { id: 'contact_and_future_updates' },
93+
},
94+
],
95+
},
96+
},
97+
};
98+
99+
export default { component: MetaBar } as Meta;

components/Common/MetaBar/index.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Heading } from '@vcarl/remark-headings';
2+
import { Fragment, useMemo } from 'react';
3+
import type { FC } from 'react';
4+
import { FormattedMessage } from 'react-intl';
5+
6+
import LocalizedLink from '@/components/LocalizedLink';
7+
8+
import styles from './index.module.css';
9+
10+
type MetaBarProps = {
11+
items: Record<string, React.ReactNode>;
12+
headings?: {
13+
items: Heading[];
14+
depth?: number;
15+
};
16+
};
17+
18+
const MetaBar: FC<MetaBarProps> = ({ items, headings }) => {
19+
// The default depth of headings to display in the table of contents.
20+
const { depth = 2, items: headingItems = [] } = headings || {};
21+
22+
const heading = useMemo(
23+
() => headingItems.filter(head => head.depth === depth),
24+
[depth, headingItems]
25+
);
26+
27+
return (
28+
<div className={styles.wrapper}>
29+
<dl>
30+
{Object.entries(items).map(([key, value]) => (
31+
<Fragment key={key}>
32+
<dt>
33+
<FormattedMessage id={key} />
34+
</dt>
35+
<dd>{value}</dd>
36+
</Fragment>
37+
))}
38+
{heading.length > 0 && (
39+
<Fragment key="tableOfContents">
40+
<dt>
41+
<FormattedMessage id="components.metabar.tableOfContents" />
42+
</dt>
43+
<dd>
44+
<ol>
45+
{heading.map(head => (
46+
<li key={head.value}>
47+
<LocalizedLink href={`#${head?.data?.id || head.value}`}>
48+
{head.value}
49+
</LocalizedLink>
50+
</li>
51+
))}
52+
</ol>
53+
</dd>
54+
</Fragment>
55+
)}
56+
</dl>
57+
</div>
58+
);
59+
};
60+
61+
export default MetaBar;

i18n/locales/en.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
"components.common.crossLink.previous": "Prev",
4444
"components.common.crossLink.next": "Next",
4545
"components.common.codebox.copied": "Copied to clipboard!",
46+
"components.metabar.lastUpdated": "Last Updated",
47+
"components.metabar.readingTime": "Reading Time",
48+
"components.metabar.addedIn": "Added In",
49+
"components.metabar.author": "Author",
50+
"components.metabar.authors": "Authors",
51+
"components.metabar.contribute": "Contribute",
52+
"components.metabar.contributeText": "Edit this page",
53+
"components.metabar.viewAs": "View as",
54+
"components.metabar.tableOfContents": "Table of Contents",
4655
"layouts.blogPost.author.byLine": "{author, select, null {} other {By {author}, }}",
4756
"layouts.blogIndex.currentYear": "News from {year}",
4857
"components.api.jsonLink.title": "View as JSON",

0 commit comments

Comments
 (0)