Skip to content

Commit 4f149b4

Browse files
author
Johannes Busch
authored
feat: add base changelog list component
* feat: adds a new base changelog list component * feat: add overridable error and loading components * feat: add changeType color resolver as prop * fix: implement changelogs reordering * docs: improve documentation of public react components
1 parent d47dce9 commit 4f149b4

16 files changed

+2898
-97
lines changed

README.md

+20-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ yarn add @wertarbyte/updatehive-react
1717

1818
Either use the react hook and render the changelog yourself or let this library fetch and render the changelog for you.
1919

20-
For a more complete example, see the [App.tsx](./src/App.tsx) in the src directory.
20+
For a more complete example, see the [App.tsx](https://github.com/TeamWertarbyte/updatehive-react/blob/master/src/App.tsx) in the src directory.
2121

2222
### Hook
2323

@@ -90,4 +90,22 @@ export type UpdateHiveConfig = {
9090
onlyLast?: boolean;
9191
};
9292
};
93-
```
93+
```
94+
95+
## Development
96+
97+
### Testing
98+
99+
The library can be easily testet in dev mode via the provided 'dev' script and App.tsx.
100+
101+
```
102+
# npm
103+
npm run rev
104+
105+
# yarn
106+
yarn dev
107+
```
108+
109+
### ChangelogLists
110+
ChangelogLists are split into public (API) classes and their internal representation, to
111+
separate concerns and allow for easier reusage.

lib/changelog.types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export type ChangelogEntryInterface = {
4646
description: string;
4747
name?: string;
4848
tags?: string[];
49+
component?: string;
4950
};
5051

5152
export type Changelog = {

lib/components/ChangelogContainer/index.tsx

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import { useChangelogs } from '../../changelog.hook.ts';
33
import { ChangelogContext } from '../ChangelogContext';
4+
import { CircularProgress, CssVarsProvider, Typography } from '@mui/joy';
45

56
interface Props {
67
API_KEY: string;
@@ -9,16 +10,38 @@ interface Props {
910
url?: string;
1011
onlyLast?: boolean;
1112
};
13+
Error?: React.ComponentType<{ error?: string }>;
14+
Loading?: React.ComponentType;
1215
children: React.ReactNode;
1316
}
1417

18+
/**
19+
* Container for all UpdateHive react components.
20+
* This container is responsible for fetching the changelogs from the UpdateHive API and handling errors / loading states.
21+
*
22+
* For API_KEY, product, config see UpdateHiveConfig.
23+
*
24+
* @param children Child components to render loaded changelogs.
25+
* @param Error Overridable error component to render if an error occurs.
26+
* @param Loading Overridable loading component to render while loading.
27+
*/
1528
export const ChangelogContainer: React.FC<Props> = ({
1629
API_KEY,
1730
product,
1831
config,
1932
children,
33+
Error = () => (
34+
<Typography>
35+
Ein Fehler ist beim Laden der Versionshistorie aufgetreten!
36+
</Typography>
37+
),
38+
Loading = () => <CircularProgress />,
2039
}) => {
21-
const { loading, error, data } = useChangelogs({
40+
const {
41+
loading,
42+
error: errorMessage,
43+
data,
44+
} = useChangelogs({
2245
connection: {
2346
API_KEY,
2447
url: config?.url,
@@ -29,14 +52,18 @@ export const ChangelogContainer: React.FC<Props> = ({
2952
},
3053
});
3154

32-
if (error) {
33-
console.error(error);
55+
if (errorMessage) {
56+
console.error(errorMessage);
3457
}
3558

3659
return (
3760
<div>
38-
<ChangelogContext.Provider value={{ loading, error, data }}>
39-
{children}
61+
<ChangelogContext.Provider value={{ data }}>
62+
{errorMessage && <Error error={errorMessage} />}
63+
{!errorMessage && loading && <Loading />}
64+
{!errorMessage && !loading && data && (
65+
<CssVarsProvider>{children}</CssVarsProvider>
66+
)}
4067
</ChangelogContext.Provider>
4168
</div>
4269
);

lib/components/ChangelogContext/index.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { createContext, useContext } from "react";
2-
import { Changelog, UpdateHiveHookResult } from "../../changelog.types.ts";
1+
import { createContext, useContext } from 'react';
2+
import { Changelog } from '../../changelog.types.ts';
33

4-
export const ChangelogContext = createContext<{
5-
loading: boolean;
6-
error?: string;
4+
export interface ChangelogContextProps {
75
data?: Changelog[];
8-
}>({ loading: true });
6+
}
97

10-
export const useUpdateHiveContext: () => UpdateHiveHookResult = () => {
8+
export const ChangelogContext = createContext<ChangelogContextProps>({});
9+
10+
export const useUpdateHiveContext: () => ChangelogContextProps = () => {
1111
const context = useContext(ChangelogContext);
1212

1313
if (!context) {
1414
throw new Error(
15-
"useChangelogContext must be used within a ChangelogContainer",
15+
'useChangelogContext must be used within a ChangelogContainer',
1616
);
1717
}
1818

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as React from 'react';
2+
import { useUpdateHiveContext } from '../ChangelogContext';
3+
import { ChangeType } from '../../changelog.types.ts';
4+
import { ChangeTypeMap, getTypeColor } from '../changelog.util.ts';
5+
import ComponentList from './_internal/ComponentList.tsx';
6+
import SimpleList from './_internal/SimpleList.tsx';
7+
import { GroupBy } from './ChangelogList.types.ts';
8+
9+
interface Props {
10+
groupBy?: GroupBy;
11+
changeTypeMapper?: Record<ChangeType, string>;
12+
typeColorResolver?: (type: ChangeType) => string;
13+
}
14+
15+
/**
16+
* Base component to render a list of changelogs.
17+
*
18+
* @param changeTypeMapper Overridable mapping of change types to displayable representations.
19+
* @param typeColorResolver Overridable function to resolve the color of a change type.
20+
* @param groupBy Group changelogs by component or show a simple list.
21+
* @constructor
22+
*/
23+
export const ChangelogList: React.FC<Props> = ({
24+
changeTypeMapper = ChangeTypeMap,
25+
typeColorResolver = getTypeColor,
26+
groupBy = GroupBy.COMPONENT,
27+
}) => {
28+
const { data } = useUpdateHiveContext();
29+
30+
return (
31+
<div>
32+
{data &&
33+
(groupBy === GroupBy.COMPONENT ? (
34+
<ComponentList
35+
changelogs={data}
36+
changeTypeMapper={changeTypeMapper}
37+
typeColorResolver={typeColorResolver}
38+
/>
39+
) : (
40+
<SimpleList changelogs={data} changeTypeMapper={changeTypeMapper} />
41+
))}
42+
</div>
43+
);
44+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum GroupBy {
2+
COMPONENT = 'COMPONENT',
3+
NONE = 'NONE',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react';
2+
import { useUpdateHiveContext } from '../ChangelogContext';
3+
import { ChangeTypeMap } from '../changelog.util.ts';
4+
import { ChangeType } from '../../changelog.types.ts';
5+
import SimpleList from './_internal/SimpleList.tsx';
6+
7+
interface Props {
8+
changeTypeMapper?: Record<ChangeType, string>;
9+
}
10+
11+
/**
12+
* Component which renders a minimal changelog list.
13+
*
14+
* The list is only ordered by creation.
15+
*
16+
* @param changeTypeMapper Overridable mapping of change types to displayable representations.
17+
*/
18+
export const MinimalChangelogList: React.FC<Props> = ({
19+
changeTypeMapper = ChangeTypeMap,
20+
}) => {
21+
const { data } = useUpdateHiveContext();
22+
23+
return (
24+
<div>
25+
{data && (
26+
<SimpleList changeTypeMapper={changeTypeMapper} changelogs={data} />
27+
)}
28+
</div>
29+
);
30+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Box, List, ListItem, Typography } from '@mui/joy';
2+
import * as React from 'react';
3+
import { Changelog, ChangeType } from '../../../changelog.types.ts';
4+
import { useMemo } from 'react';
5+
import {
6+
ChangelogWithComponents,
7+
groupChangelogsByComponents,
8+
reorderChangelogs,
9+
} from '../../changelog.util.ts';
10+
11+
interface Props {
12+
changelogs: Changelog[];
13+
changeTypeMapper: Record<ChangeType, string>;
14+
typeColorResolver: (type: ChangeType) => string;
15+
}
16+
17+
const ComponentList: React.FC<Props> = ({
18+
changelogs,
19+
changeTypeMapper,
20+
typeColorResolver,
21+
}) => {
22+
const componentChangelogs: ChangelogWithComponents[] = useMemo(() => {
23+
const reorderedChangelogs = reorderChangelogs(changelogs);
24+
return groupChangelogsByComponents(reorderedChangelogs);
25+
}, [changelogs]);
26+
27+
return (
28+
<div>
29+
{componentChangelogs.map((changelog, index) => (
30+
<div key={`changelog-${index}`}>
31+
<Box sx={() => ({ marginBottom: '8px' })}>
32+
<Typography level="h3" sx={() => ({ marginRight: '8px' })}>
33+
Version {changelog.version}
34+
</Typography>
35+
{changelog.description && (
36+
<Typography>{changelog.description}</Typography>
37+
)}
38+
</Box>
39+
{changelog.entries.map((entry) => (
40+
<>
41+
<Typography level="title-lg">{entry.component}</Typography>
42+
<List
43+
marker={'circle'}
44+
sx={() => ({ '--ListItem-minHeight': 20 })}
45+
>
46+
{entry.changelogs.map((entry, entryIndex) => (
47+
<ListItem
48+
sx={() => ({
49+
padding: '0px',
50+
})}
51+
key={`changelog-${index}-entry-${entryIndex}`}
52+
>
53+
<Box sx={() => ({ display: 'flex', flexDirection: 'row' })}>
54+
<Typography
55+
level="title-sm"
56+
sx={() => ({
57+
marginRight: '8px',
58+
color: typeColorResolver(entry.changeType),
59+
})}
60+
>
61+
{changeTypeMapper[entry.changeType]}
62+
</Typography>
63+
<Typography level="body-sm">
64+
{entry.description}
65+
</Typography>
66+
</Box>
67+
</ListItem>
68+
))}
69+
</List>
70+
</>
71+
))}
72+
</div>
73+
))}
74+
</div>
75+
);
76+
};
77+
78+
export default ComponentList;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as React from 'react';
2+
import { Changelog, ChangeType } from '../../../changelog.types.ts';
3+
import { Box, List, ListItem, Typography } from '@mui/joy';
4+
import { reorderChangelogs } from '../../changelog.util.ts';
5+
import { useMemo } from 'react';
6+
7+
interface Props {
8+
changeTypeMapper: Record<ChangeType, string>;
9+
changelogs: Changelog[];
10+
}
11+
12+
const SimpleList: React.FC<Props> = ({ changelogs, changeTypeMapper }) => {
13+
const reorderedChangelogs = useMemo(() => {
14+
return reorderChangelogs(changelogs);
15+
}, [changelogs]);
16+
17+
return (
18+
<div>
19+
{reorderedChangelogs.map((changelog, index) => (
20+
<div key={`changelog-${index}`}>
21+
<Box sx={() => ({ marginBottom: '8px' })}>
22+
<Typography level="h3" sx={() => ({ marginRight: '8px' })}>
23+
Version {changelog.version}
24+
</Typography>
25+
{changelog.description && (
26+
<Typography>{changelog.description}</Typography>
27+
)}
28+
</Box>
29+
<List marker={'circle'} sx={() => ({ '--ListItem-minHeight': 20 })}>
30+
{changelog.entries.map((entry, entryIndex) => (
31+
<ListItem
32+
sx={() => ({
33+
padding: '0px',
34+
})}
35+
key={`changelog-${index}-entry-${entryIndex}`}
36+
>
37+
<Box sx={() => ({ display: 'flex', flexDirection: 'row' })}>
38+
<Typography
39+
level="title-sm"
40+
sx={() => ({ marginRight: '8px' })}
41+
>
42+
{changeTypeMapper[entry.changeType]}
43+
</Typography>
44+
<Typography level="body-sm">{entry.description}</Typography>
45+
</Box>
46+
</ListItem>
47+
))}
48+
</List>
49+
</div>
50+
))}
51+
</div>
52+
);
53+
};
54+
55+
export default SimpleList;

lib/components/ChangelogList/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { GroupBy } from './ChangelogList.types';
2+
3+
export { MinimalChangelogList } from './MinimalChangelogList';
4+
export { ChangelogList } from './ChangelogList';

0 commit comments

Comments
 (0)