feat: release notes (#283)
* feat: add release.ts * feat: add utils * chore: add scripts to tsconfig.json include * feat: add changelog logic, component, storybook file, scripts, and update to Node v20.9.0 (LTS) * chore: update packages * feat: use conventionalcommits for changelog preset * feat: update padding, width, and change font to mono * feat: refactor to use DialogProvider * chore: remove extra args * feat: update CHANGELOG.md, add title, and add button * refactor: use hook * chore: fix typo
This commit is contained in:
38
src/stories/components/ChangelogPopup.stories.tsx
Normal file
38
src/stories/components/ChangelogPopup.stories.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from '@views/components/common/Button';
|
||||
import ChangelogPopup from '@views/components/common/ChangelogPopup';
|
||||
import DialogProvider from '@views/components/common/DialogProvider/DialogProvider';
|
||||
import useChangelog from '@views/hooks/useChangelog';
|
||||
import React from 'react';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Common/ChangelogPopup',
|
||||
component: ChangelogPopup,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
} satisfies Meta<typeof ChangelogPopup>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary: Story = {
|
||||
render: () => (
|
||||
<DialogProvider>
|
||||
<InnerComponent />
|
||||
</DialogProvider>
|
||||
),
|
||||
};
|
||||
|
||||
const InnerComponent = () => {
|
||||
const handleOnClick = useChangelog();
|
||||
|
||||
return (
|
||||
<Button variant='filled' color='ut-burntorange' onClick={handleOnClick}>
|
||||
Open Changelog Popup
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
171
src/views/components/common/ChangelogPopup.tsx
Normal file
171
src/views/components/common/ChangelogPopup.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { Options as RMOptions } from 'react-markdown';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
const changelog = new URL('/CHANGELOG.md', import.meta.url).href;
|
||||
|
||||
/**
|
||||
* Renders a popup component for displaying the changelog.
|
||||
*
|
||||
* @param isOpen - A boolean indicating whether the popup is open or not.
|
||||
* @param onClose - A function to be called when the popup is closed.
|
||||
*
|
||||
* @returns The JSX element representing the ChangelogPopup component.
|
||||
*/
|
||||
export default function ChangelogPopup(): JSX.Element {
|
||||
const [markdownContent, setMarkdownContent] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetch(changelog)
|
||||
.then(response => response.text())
|
||||
.then(text => setMarkdownContent(text))
|
||||
.catch(error => console.error('Error fetching changelog:', error));
|
||||
}, []);
|
||||
|
||||
const MarkdownComponents: RMOptions['components'] = {
|
||||
h1: ({ children, ...props }) => (
|
||||
<h1 className='mb-4 mt-6 text-3xl font-bold' {...props}>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children, ...props }) => (
|
||||
<h2 className='mb-3 mt-5 text-2xl font-semibold' {...props}>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children, ...props }) => (
|
||||
<h3 className='mb-2 mt-4 text-xl font-medium' {...props}>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children, ...props }) => (
|
||||
<h4 className='mb-2 mt-3 text-lg font-medium' {...props}>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children, ...props }) => (
|
||||
<h5 className='mb-1 mt-2 text-base font-medium' {...props}>
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children, ...props }) => (
|
||||
<h6 className='mb-1 mt-2 text-sm font-medium' {...props}>
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children, ...props }) => (
|
||||
<p className='mb-4' {...props}>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
a: ({ children, ...props }) => (
|
||||
<a className='text-blue-500 hover:underline' {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
ul: ({ children, ...props }) => (
|
||||
<ul className='mb-4 list-disc pl-6' {...props}>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children, ...props }) => (
|
||||
<ol className='mb-4 list-decimal pl-6' {...props}>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children, ...props }) => (
|
||||
<li className='mb-1' {...props}>
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
blockquote: ({ children, ...props }) => (
|
||||
<blockquote className='mb-4 border-l-4 border-gray-300 py-2 pl-4 italic' {...props}>
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
hr: props => <hr className='my-4 border-t border-gray-300' {...props} />,
|
||||
table: ({ children, ...props }) => (
|
||||
<div className='mb-4 overflow-x-auto'>
|
||||
<table className='min-w-full divide-y divide-gray-300' {...props}>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children, ...props }) => (
|
||||
<thead className='bg-gray-50' {...props}>
|
||||
{children}
|
||||
</thead>
|
||||
),
|
||||
tbody: ({ children, ...props }) => (
|
||||
<tbody className='divide-y divide-gray-200' {...props}>
|
||||
{children}
|
||||
</tbody>
|
||||
),
|
||||
tr: ({ children, ...props }) => (
|
||||
<tr className='hover:bg-gray-50' {...props}>
|
||||
{children}
|
||||
</tr>
|
||||
),
|
||||
th: ({ children, ...props }) => (
|
||||
<th className='px-6 py-3 text-left text-xs text-gray-500 font-medium tracking-wider uppercase' {...props}>
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children, ...props }) => (
|
||||
<td className='whitespace-nowrap px-6 py-4' {...props}>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
pre: ({ children, ...props }) => (
|
||||
<pre className='mb-4' {...props}>
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
code: ({ className, children, ...props }) => {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter PreTag='div' style={vscDarkPlus} language={match[1]}>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className='rounded bg-gray-100 px-1 py-0.5 dark:bg-gray-800' {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
em: ({ children, ...props }) => (
|
||||
<em className='italic' {...props}>
|
||||
{children}
|
||||
</em>
|
||||
),
|
||||
strong: ({ children, ...props }) => (
|
||||
<strong className='font-bold' {...props}>
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
del: ({ children, ...props }) => (
|
||||
<del className='line-through' {...props}>
|
||||
{children}
|
||||
</del>
|
||||
),
|
||||
img: ({ src, alt, ...props }) => (
|
||||
<img src={src} alt={alt} className='my-4 h-auto max-w-full rounded-md' {...props} />
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='px-4'>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={MarkdownComponents}
|
||||
className='text-gray-800 dark:text-gray-200'
|
||||
>
|
||||
{markdownContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/views/hooks/useChangelog.tsx
Normal file
36
src/views/hooks/useChangelog.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import ChangelogPopup from '@views/components/common/ChangelogPopup';
|
||||
import Text from '@views/components/common/Text/Text';
|
||||
import { useDialog } from '@views/contexts/DialogContext';
|
||||
import React from 'react';
|
||||
|
||||
import MaterialSymbolsClose from '~icons/material-symbols/close';
|
||||
|
||||
/**
|
||||
* Custom hook that provides a function to display a changelog dialog.
|
||||
*
|
||||
* @returns A function that, when called, shows a dialog with the changelog.
|
||||
*/
|
||||
export default function useChangelog(): () => void {
|
||||
const showDialog = useDialog();
|
||||
|
||||
const handleOnClick = () => {
|
||||
showDialog(close => ({
|
||||
title: (
|
||||
<div className='flex items-center justify-between p-4'>
|
||||
<Text variant='h1' className='text-theme-black'>
|
||||
Changelog
|
||||
</Text>
|
||||
<button
|
||||
onClick={close}
|
||||
className='text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'
|
||||
>
|
||||
<MaterialSymbolsClose className='text-2xl' />
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
description: <ChangelogPopup />,
|
||||
}));
|
||||
};
|
||||
|
||||
return handleOnClick;
|
||||
}
|
||||
Reference in New Issue
Block a user