Skip to content

Commit e3f500c

Browse files
committed
feat(ui-components): add change history component
1 parent 1ccf19c commit e3f500c

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
@reference "../../styles/index.css";
2+
3+
.changeHistory {
4+
@apply relative
5+
inline-block;
6+
}
7+
8+
.summary {
9+
@apply outline-hidden
10+
flex
11+
h-9
12+
cursor-pointer
13+
select-none
14+
items-center
15+
gap-2
16+
rounded-md
17+
border
18+
border-neutral-200
19+
p-2
20+
text-sm
21+
text-neutral-700
22+
motion-safe:transition-colors
23+
dark:border-neutral-900
24+
dark:text-neutral-300;
25+
26+
&:hover,
27+
&:focus-visible {
28+
@apply bg-neutral-100
29+
dark:bg-neutral-900;
30+
}
31+
32+
&.disabled {
33+
@apply cursor-not-allowed
34+
opacity-50;
35+
36+
&:hover,
37+
&:focus-visible {
38+
@apply bg-transparent
39+
dark:bg-transparent;
40+
}
41+
}
42+
}
43+
44+
.icon {
45+
@apply h-4
46+
w-4;
47+
}
48+
49+
.chevron {
50+
@apply h-3
51+
w-3
52+
group-open:rotate-180
53+
motion-safe:transition-transform;
54+
}
55+
56+
.dropdownContent {
57+
@apply absolute
58+
top-full
59+
z-50
60+
mt-1
61+
max-h-80
62+
w-52
63+
overflow-hidden
64+
rounded-sm
65+
border
66+
border-neutral-200
67+
bg-white
68+
shadow-lg
69+
dark:border-neutral-900
70+
dark:bg-neutral-950;
71+
}
72+
73+
.dropdownContent.alignLeft {
74+
@apply left-0;
75+
}
76+
77+
.dropdownContent.alignRight {
78+
@apply right-0;
79+
}
80+
81+
.dropdownContentInner {
82+
@apply max-h-80
83+
w-52
84+
overflow-y-auto;
85+
}
86+
87+
.dropdownItem {
88+
@apply outline-hidden
89+
block
90+
px-2.5
91+
py-1.5
92+
text-sm
93+
font-medium
94+
text-neutral-800
95+
no-underline
96+
motion-safe:transition-colors
97+
dark:text-white;
98+
99+
&:hover,
100+
&:focus-visible {
101+
@apply bg-green-600
102+
text-white;
103+
}
104+
}
105+
106+
.dropdownLabel {
107+
@apply block
108+
text-sm
109+
font-medium
110+
leading-tight;
111+
}
112+
113+
.dropdownVersions {
114+
@apply block
115+
text-xs
116+
leading-tight
117+
opacity-75;
118+
}
119+
120+
.emptyState {
121+
@apply px-2.5
122+
py-1.5
123+
text-sm
124+
font-medium
125+
text-neutral-800
126+
opacity-75
127+
dark:text-white;
128+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
2+
3+
import ChangeHistory from '#ui/Common/ChangeHistory';
4+
5+
type Story = StoryObj<typeof ChangeHistory>;
6+
type Meta = MetaObj<typeof ChangeHistory>;
7+
8+
const SAMPLE_CHANGES = [
9+
{
10+
versions: ['v15.4.0'],
11+
label: 'No longer experimental',
12+
url: 'https://github.com/nodejs/node/pull/12345',
13+
},
14+
{
15+
versions: ['v15.0.0', 'v14.17.0'],
16+
label: 'Added in v15.0.0, v14.17.0',
17+
url: 'https://github.com/nodejs/node/pull/67890',
18+
},
19+
{
20+
versions: ['v16.0.0'],
21+
label: 'Deprecated in 16',
22+
},
23+
];
24+
25+
const LARGE_SAMPLE_CHANGES = [
26+
{
27+
versions: ['v20.0.0'],
28+
label: 'Breaking change in v20',
29+
url: 'https://github.com/nodejs/node/pull/50001',
30+
},
31+
{
32+
versions: ['v19.8.0'],
33+
label: 'Performance improvement',
34+
url: 'https://github.com/nodejs/node/pull/49999',
35+
},
36+
{
37+
versions: ['v19.0.0'],
38+
label: 'API redesign',
39+
url: 'https://github.com/nodejs/node/pull/49000',
40+
},
41+
{
42+
versions: ['v18.17.0', 'v18.16.1'],
43+
label: 'Security fix backported',
44+
url: 'https://github.com/nodejs/node/pull/48500',
45+
},
46+
{
47+
versions: ['v18.0.0'],
48+
label: 'Major version release',
49+
url: 'https://github.com/nodejs/node/pull/47000',
50+
},
51+
{
52+
versions: ['v17.9.0'],
53+
label: 'Experimental feature added',
54+
url: 'https://github.com/nodejs/node/pull/46500',
55+
},
56+
{
57+
versions: ['v17.0.0'],
58+
label: 'Node.js 17 release',
59+
url: 'https://github.com/nodejs/node/pull/45000',
60+
},
61+
{
62+
versions: ['v16.15.0', 'v16.14.2'],
63+
label: 'Bug fix release',
64+
url: 'https://github.com/nodejs/node/pull/44000',
65+
},
66+
{
67+
versions: ['v16.0.0'],
68+
label: 'Deprecated in v16',
69+
url: 'https://github.com/nodejs/node/pull/43000',
70+
},
71+
{
72+
versions: ['v15.14.0'],
73+
label: 'Feature enhancement',
74+
url: 'https://github.com/nodejs/node/pull/42000',
75+
},
76+
{
77+
versions: ['v15.0.0', 'v14.17.0'],
78+
label: 'Initial implementation',
79+
url: 'https://github.com/nodejs/node/pull/41000',
80+
},
81+
{
82+
versions: ['v14.18.0'],
83+
label: 'Documentation update',
84+
url: 'https://github.com/nodejs/node/pull/40000',
85+
},
86+
{
87+
versions: ['v14.0.0'],
88+
label: 'Added to stable API',
89+
url: 'https://github.com/nodejs/node/pull/39000',
90+
},
91+
{
92+
versions: ['v13.14.0'],
93+
label: 'Experimental flag removed',
94+
url: 'https://github.com/nodejs/node/pull/38000',
95+
},
96+
{
97+
versions: ['v12.22.0', 'v12.21.0'],
98+
label: 'Backported to LTS',
99+
url: 'https://github.com/nodejs/node/pull/37000',
100+
},
101+
{
102+
versions: ['v12.0.0'],
103+
label: 'First experimental version',
104+
url: 'https://github.com/nodejs/node/pull/36000',
105+
},
106+
];
107+
108+
export const Default: Story = {
109+
render: args => (
110+
<div className="flex justify-end">
111+
<ChangeHistory {...args} />
112+
</div>
113+
),
114+
args: {
115+
changes: SAMPLE_CHANGES,
116+
},
117+
};
118+
119+
export const NoChanges: Story = {
120+
render: args => (
121+
<div className="flex justify-end">
122+
<ChangeHistory {...args} />
123+
</div>
124+
),
125+
args: {
126+
changes: [],
127+
},
128+
};
129+
130+
export const LeftAligned: Story = {
131+
args: {
132+
align: 'left',
133+
changes: SAMPLE_CHANGES,
134+
},
135+
};
136+
137+
export const LargeHistory: Story = {
138+
render: args => (
139+
<div className="flex justify-end">
140+
<ChangeHistory {...args} />
141+
</div>
142+
),
143+
args: {
144+
changes: LARGE_SAMPLE_CHANGES,
145+
},
146+
};
147+
148+
export default { component: ChangeHistory } as Meta;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ChevronDownIcon, ClockIcon } from '@heroicons/react/24/outline';
2+
import type { FC } from 'react';
3+
4+
import styles from './index.module.css';
5+
6+
type ChangeHistoryProps = React.JSX.IntrinsicElements['div'] & {
7+
label: string;
8+
changes: Array<{
9+
versions: Array<string>;
10+
label: string;
11+
url?: string;
12+
}>;
13+
align?: 'left' | 'right';
14+
};
15+
16+
const ChangeHistory: FC<ChangeHistoryProps> = ({
17+
label = 'History',
18+
changes = [],
19+
align = 'right',
20+
className,
21+
'aria-label': ariaLabel = label,
22+
...props
23+
}) => (
24+
<div className={`${styles.changeHistory} ${className}`} {...props}>
25+
{changes.length > 0 ? (
26+
<details className="group">
27+
<summary className={styles.summary} role="button" aria-haspopup="true">
28+
<ClockIcon className={styles.icon} aria-hidden="true" />
29+
<span>{label}</span>
30+
<ChevronDownIcon className={styles.chevron} aria-hidden="true" />
31+
</summary>
32+
<div
33+
className={`${styles.dropdownContent} ${
34+
align === 'left' ? styles.alignLeft : styles.alignRight
35+
}`}
36+
role="menu"
37+
aria-label={ariaLabel}
38+
>
39+
<div className={styles.dropdownContentInner}>
40+
{changes.map((change, index) => {
41+
const content = (
42+
<>
43+
<div className={styles.dropdownLabel}>{change.label}</div>
44+
<div className={styles.dropdownVersions}>
45+
{change.versions.join(', ')}
46+
</div>
47+
</>
48+
);
49+
50+
return (
51+
<a
52+
key={index}
53+
href={change.url}
54+
className={styles.dropdownItem}
55+
role="menuitem"
56+
tabIndex={0}
57+
aria-label={`${change.label}: ${change.versions.join(', ')}`}
58+
>
59+
{content}
60+
</a>
61+
);
62+
})}
63+
</div>
64+
</div>
65+
</details>
66+
) : (
67+
<div
68+
className={`${styles.summary} ${styles.disabled}`}
69+
role="button"
70+
aria-disabled="true"
71+
>
72+
<ClockIcon className={styles.icon} aria-hidden="true" />
73+
<span>History</span>
74+
<ChevronDownIcon className={styles.chevron} aria-hidden="true" />
75+
</div>
76+
)}
77+
</div>
78+
);
79+
80+
export default ChangeHistory;

0 commit comments

Comments
 (0)