Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add base changelog list component #8

Merged
merged 9 commits into from
Jul 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ yarn add @wertarbyte/updatehive-react

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

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

### Hook

@@ -90,4 +90,22 @@ export type UpdateHiveConfig = {
onlyLast?: boolean;
};
};
```
```

## Development

### Testing

The library can be easily testet in dev mode via the provided 'dev' script and App.tsx.

```
# npm
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
npm run rev
npm run dev

npm run rev

# yarn
yarn dev
```

### ChangelogLists
ChangelogLists are split into public (API) classes and their internal representation, to
separate concerns and allow for easier reusage.
1 change: 1 addition & 0 deletions lib/changelog.types.ts
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@ export type ChangelogEntryInterface = {
description: string;
name?: string;
tags?: string[];
component?: string;
};

export type Changelog = {
37 changes: 32 additions & 5 deletions lib/components/ChangelogContainer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { useChangelogs } from '../../changelog.hook.ts';
import { ChangelogContext } from '../ChangelogContext';
import { CircularProgress, CssVarsProvider, Typography } from '@mui/joy';

interface Props {
API_KEY: string;
@@ -9,16 +10,38 @@ interface Props {
url?: string;
onlyLast?: boolean;
};
Error?: React.ComponentType<{ error?: string }>;
Loading?: React.ComponentType;
children: React.ReactNode;
}

/**
* Container for all UpdateHive react components.
* This container is responsible for fetching the changelogs from the UpdateHive API and handling errors / loading states.
*
* For API_KEY, product, config see UpdateHiveConfig.
*
* @param children Child components to render loaded changelogs.
* @param Error Overridable error component to render if an error occurs.
* @param Loading Overridable loading component to render while loading.
*/
export const ChangelogContainer: React.FC<Props> = ({
API_KEY,
product,
config,
children,
Error = () => (
<Typography>
Ein Fehler ist beim Laden der Versionshistorie aufgetreten!
</Typography>
),
Loading = () => <CircularProgress />,
}) => {
const { loading, error, data } = useChangelogs({
const {
loading,
error: errorMessage,
data,
} = useChangelogs({
connection: {
API_KEY,
url: config?.url,
@@ -29,14 +52,18 @@ export const ChangelogContainer: React.FC<Props> = ({
},
});

if (error) {
console.error(error);
if (errorMessage) {
console.error(errorMessage);
}

return (
<div>
<ChangelogContext.Provider value={{ loading, error, data }}>
{children}
<ChangelogContext.Provider value={{ data }}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich würde den outer div entfernen. So forced to bei Benutzung eine Mutation im DOM

{errorMessage && <Error error={errorMessage} />}
{!errorMessage && loading && <Loading />}
{!errorMessage && !loading && data && (
<CssVarsProvider>{children}</CssVarsProvider>
)}
</ChangelogContext.Provider>
</div>
);
16 changes: 8 additions & 8 deletions lib/components/ChangelogContext/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { createContext, useContext } from "react";
import { Changelog, UpdateHiveHookResult } from "../../changelog.types.ts";
import { createContext, useContext } from 'react';
import { Changelog } from '../../changelog.types.ts';

export const ChangelogContext = createContext<{
loading: boolean;
error?: string;
export interface ChangelogContextProps {
data?: Changelog[];
}>({ loading: true });
}

export const useUpdateHiveContext: () => UpdateHiveHookResult = () => {
export const ChangelogContext = createContext<ChangelogContextProps>({});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wäre es sinnvoll data als empty array zu initilisieren?

export const useUpdateHiveContext: () => ChangelogContextProps = () => {
const context = useContext(ChangelogContext);

if (!context) {
throw new Error(
"useChangelogContext must be used within a ChangelogContainer",
'useChangelogContext must be used within a ChangelogContainer',
);
}

44 changes: 44 additions & 0 deletions lib/components/ChangelogList/ChangelogList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import { useUpdateHiveContext } from '../ChangelogContext';
import { ChangeType } from '../../changelog.types.ts';
import { ChangeTypeMap, getTypeColor } from '../changelog.util.ts';
import ComponentList from './_internal/ComponentList.tsx';
import SimpleList from './_internal/SimpleList.tsx';
import { GroupBy } from './ChangelogList.types.ts';

interface Props {
groupBy?: GroupBy;
changeTypeMapper?: Record<ChangeType, string>;
typeColorResolver?: (type: ChangeType) => string;
}

/**
* Base component to render a list of changelogs.
*
* @param changeTypeMapper Overridable mapping of change types to displayable representations.
* @param typeColorResolver Overridable function to resolve the color of a change type.
* @param groupBy Group changelogs by component or show a simple list.
* @constructor
*/
export const ChangelogList: React.FC<Props> = ({
changeTypeMapper = ChangeTypeMap,
typeColorResolver = getTypeColor,
groupBy = GroupBy.COMPONENT,
}) => {
const { data } = useUpdateHiveContext();

return (
<div>
{data &&
(groupBy === GroupBy.COMPONENT ? (
<ComponentList
changelogs={data}
changeTypeMapper={changeTypeMapper}
typeColorResolver={typeColorResolver}
/>
) : (
<SimpleList changelogs={data} changeTypeMapper={changeTypeMapper} />
))}
</div>
);
};
4 changes: 4 additions & 0 deletions lib/components/ChangelogList/ChangelogList.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum GroupBy {
COMPONENT = 'COMPONENT',
NONE = 'NONE',
}
30 changes: 30 additions & 0 deletions lib/components/ChangelogList/MinimalChangelogList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import { useUpdateHiveContext } from '../ChangelogContext';
import { ChangeTypeMap } from '../changelog.util.ts';
import { ChangeType } from '../../changelog.types.ts';
import SimpleList from './_internal/SimpleList.tsx';

interface Props {
changeTypeMapper?: Record<ChangeType, string>;
}

/**
* Component which renders a minimal changelog list.
*
* The list is only ordered by creation.
*
* @param changeTypeMapper Overridable mapping of change types to displayable representations.
*/
export const MinimalChangelogList: React.FC<Props> = ({
changeTypeMapper = ChangeTypeMap,
}) => {
const { data } = useUpdateHiveContext();

return (
<div>
{data && (
<SimpleList changeTypeMapper={changeTypeMapper} changelogs={data} />
)}
</div>
);
};
78 changes: 78 additions & 0 deletions lib/components/ChangelogList/_internal/ComponentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Box, List, ListItem, Typography } from '@mui/joy';
import * as React from 'react';
import { Changelog, ChangeType } from '../../../changelog.types.ts';
import { useMemo } from 'react';
import {
ChangelogWithComponents,
groupChangelogsByComponents,
reorderChangelogs,
} from '../../changelog.util.ts';

interface Props {
changelogs: Changelog[];
changeTypeMapper: Record<ChangeType, string>;
typeColorResolver: (type: ChangeType) => string;
}

const ComponentList: React.FC<Props> = ({
changelogs,
changeTypeMapper,
typeColorResolver,
}) => {
const componentChangelogs: ChangelogWithComponents[] = useMemo(() => {
const reorderedChangelogs = reorderChangelogs(changelogs);
return groupChangelogsByComponents(reorderedChangelogs);
}, [changelogs]);

return (
<div>
{componentChangelogs.map((changelog, index) => (
<div key={`changelog-${index}`}>
<Box sx={() => ({ marginBottom: '8px' })}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Box sx={() => ({ marginBottom: '8px' })}>
<Box sx=({ mb: 2 }}>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auch die anderen mal anschauen. Es gibt shorthandler und wenn du das theme objeckt nicht nutzt kannst du die funktion omitten

<Typography level="h3" sx={() => ({ marginRight: '8px' })}>
Version {changelog.version}
</Typography>
{changelog.description && (
<Typography>{changelog.description}</Typography>
)}
</Box>
{changelog.entries.map((entry) => (
<>
<Typography level="title-lg">{entry.component}</Typography>
<List
marker={'circle'}
sx={() => ({ '--ListItem-minHeight': 20 })}
>
{entry.changelogs.map((entry, entryIndex) => (
<ListItem
sx={() => ({
padding: '0px',
})}
key={`changelog-${index}-entry-${entryIndex}`}
>
<Box sx={() => ({ display: 'flex', flexDirection: 'row' })}>
<Typography
level="title-sm"
sx={() => ({
marginRight: '8px',
color: typeColorResolver(entry.changeType),
})}
>
{changeTypeMapper[entry.changeType]}
</Typography>
<Typography level="body-sm">
{entry.description}
</Typography>
</Box>
</ListItem>
))}
</List>
</>
))}
</div>
))}
</div>
);
};

export default ComponentList;
55 changes: 55 additions & 0 deletions lib/components/ChangelogList/_internal/SimpleList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import { Changelog, ChangeType } from '../../../changelog.types.ts';
import { Box, List, ListItem, Typography } from '@mui/joy';
import { reorderChangelogs } from '../../changelog.util.ts';
import { useMemo } from 'react';

interface Props {
changeTypeMapper: Record<ChangeType, string>;
changelogs: Changelog[];
}

const SimpleList: React.FC<Props> = ({ changelogs, changeTypeMapper }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name würde ich überdenken. Gibt es auch eine "HardList"? "ComplicatedList"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mir ist auch nicht so ganz klar warum diese Komponente fast genauso wie ComponentList aussieht. Kann man vll etwas generisches aus beiden heraus lösen und diese hier mergen?

const reorderedChangelogs = useMemo(() => {
return reorderChangelogs(changelogs);
}, [changelogs]);

return (
<div>
{reorderedChangelogs.map((changelog, index) => (
<div key={`changelog-${index}`}>
<Box sx={() => ({ marginBottom: '8px' })}>
<Typography level="h3" sx={() => ({ marginRight: '8px' })}>
Version {changelog.version}
</Typography>
{changelog.description && (
<Typography>{changelog.description}</Typography>
)}
</Box>
<List marker={'circle'} sx={() => ({ '--ListItem-minHeight': 20 })}>
{changelog.entries.map((entry, entryIndex) => (
<ListItem
sx={() => ({
padding: '0px',
})}
key={`changelog-${index}-entry-${entryIndex}`}
>
<Box sx={() => ({ display: 'flex', flexDirection: 'row' })}>
<Typography
level="title-sm"
sx={() => ({ marginRight: '8px' })}
>
{changeTypeMapper[entry.changeType]}
</Typography>
<Typography level="body-sm">{entry.description}</Typography>
</Box>
</ListItem>
))}
</List>
</div>
))}
</div>
);
};

export default SimpleList;
4 changes: 4 additions & 0 deletions lib/components/ChangelogList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { GroupBy } from './ChangelogList.types';

export { MinimalChangelogList } from './MinimalChangelogList';
export { ChangelogList } from './ChangelogList';
Loading