mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-20 00:33:07 +01:00
feat(analytics-docs): Add changelog sidebar
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -23,6 +23,9 @@ import { Filter } from './components/Filter';
|
||||
import { GlobalStyle } from './components/GlobalStyle';
|
||||
import { ResultsInfo } from './components/ResultsInfo';
|
||||
import { ThemeSwitch } from './components/ThemeSwitch';
|
||||
import { SIDEBAR_WIDTH, VersionsSidebar } from './components/VersionsSidebar';
|
||||
import { HEADER_HEIGHT } from './constants';
|
||||
import { getEventId, getVersionsWithEvents } from './utils/filterUtils';
|
||||
import { useFilteredEvents } from './utils/useFilteredEvents';
|
||||
|
||||
type AppTheme = SuiteThemeColors & { variant: 'light' | 'dark'; mode: 'light' | 'dark' };
|
||||
@@ -48,7 +51,7 @@ export const Content = styled.div`
|
||||
padding: 20px 10px;
|
||||
|
||||
@media (min-width: ${variables.SCREEN_SIZE.MD}) {
|
||||
margin: 130px 20px 20px;
|
||||
margin: ${HEADER_HEIGHT}px 20px 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -58,7 +61,30 @@ export const ContentContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const MainWithSidebar = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
@media (min-width: ${variables.SCREEN_SIZE.MD}) {
|
||||
margin: ${HEADER_HEIGHT}px 0 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ContentArea = styled.div`
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 20px 10px;
|
||||
|
||||
@media (min-width: ${variables.SCREEN_SIZE.MD}) {
|
||||
margin: 0 20px 20px 0;
|
||||
margin-left: ${SIDEBAR_WIDTH + 20}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const App = ({ theme }: AppProps) => {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const {
|
||||
filteredEvents,
|
||||
setQuery,
|
||||
@@ -75,8 +101,18 @@ export const App = ({ theme }: AppProps) => {
|
||||
|
||||
const hasActiveFilters = !!query || platform !== 'all' || sort !== 'az';
|
||||
|
||||
const versionsWithEvents = useMemo(
|
||||
() => getVersionsWithEvents(filteredEvents),
|
||||
[filteredEvents],
|
||||
);
|
||||
|
||||
const eventCards = useMemo(
|
||||
() => filteredEvents.map(event => <EventCard key={event.name} event={event} />),
|
||||
() =>
|
||||
filteredEvents.map(event => (
|
||||
<div key={event.name} id={getEventId(event.name)}>
|
||||
<EventCard event={event} />
|
||||
</div>
|
||||
)),
|
||||
[filteredEvents],
|
||||
);
|
||||
const isMobile = useMediaQuery(`(max-width: ${variables.SCREEN_SIZE.MD})`);
|
||||
@@ -104,6 +140,21 @@ export const App = ({ theme }: AppProps) => {
|
||||
{isFiltering && <Spinner size={20} />}
|
||||
</Row>
|
||||
<Row gap={8} alignItems="center">
|
||||
<Tooltip
|
||||
content={
|
||||
isSidebarOpen
|
||||
? 'Hide versions'
|
||||
: 'Show versions by last updated'
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon="sidebar"
|
||||
onClick={() => setIsSidebarOpen(prev => !prev)}
|
||||
intent={isSidebarOpen ? 'brand' : 'neutral'}
|
||||
size="small"
|
||||
priority={isSidebarOpen ? 'primary' : 'secondary'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ThemeSwitch />
|
||||
<Tooltip content="Add event">
|
||||
{isMobile ? (
|
||||
@@ -142,11 +193,22 @@ export const App = ({ theme }: AppProps) => {
|
||||
</Column>
|
||||
</ContentContainer>
|
||||
</TopBar>
|
||||
<Content>
|
||||
<ContentContainer>
|
||||
<Column gap={40}>{eventCards}</Column>
|
||||
</ContentContainer>
|
||||
</Content>
|
||||
{isSidebarOpen ? (
|
||||
<MainWithSidebar>
|
||||
<VersionsSidebar versionsWithEvents={versionsWithEvents} />
|
||||
<ContentArea>
|
||||
<ContentContainer>
|
||||
<Column gap={40}>{eventCards}</Column>
|
||||
</ContentContainer>
|
||||
</ContentArea>
|
||||
</MainWithSidebar>
|
||||
) : (
|
||||
<Content>
|
||||
<ContentContainer>
|
||||
<Column gap={40}>{eventCards}</Column>
|
||||
</ContentContainer>
|
||||
</Content>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
74
packages/analytics-docs/src/components/VersionsSidebar.tsx
Normal file
74
packages/analytics-docs/src/components/VersionsSidebar.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Badge, Box, CardList, Column, H3, Text, variables } from '@trezor/components';
|
||||
import type { SuiteThemeColors } from '@trezor/components';
|
||||
|
||||
import { HEADER_HEIGHT } from '../constants';
|
||||
import type { EventDoc } from '../types';
|
||||
import type { VersionWithEvents } from '../utils/filterUtils';
|
||||
import { getEventId } from '../utils/filterUtils';
|
||||
|
||||
const scrollToEvent = (eventName: string) => {
|
||||
const el = document.getElementById(getEventId(eventName));
|
||||
el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
};
|
||||
|
||||
export const SIDEBAR_WIDTH = 280;
|
||||
|
||||
const SidebarWrapper = styled.aside<{ theme: SuiteThemeColors }>`
|
||||
width: ${SIDEBAR_WIDTH}px;
|
||||
flex-shrink: 0;
|
||||
background: ${({ theme }) => theme.backgroundSurfaceElevation1};
|
||||
border-right: 1px solid ${({ theme }) => theme.borderOnElevation1};
|
||||
padding: 16px 0;
|
||||
overflow-y: auto;
|
||||
|
||||
@media (min-width: ${variables.SCREEN_SIZE.MD}) {
|
||||
position: fixed;
|
||||
top: ${HEADER_HEIGHT}px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: calc(100vh - ${HEADER_HEIGHT}px);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media (max-width: ${variables.SCREEN_SIZE.MD}) {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.borderOnElevation1};
|
||||
}
|
||||
`;
|
||||
|
||||
type VersionsSidebarProps = {
|
||||
versionsWithEvents: VersionWithEvents[];
|
||||
};
|
||||
|
||||
export const VersionsSidebar = ({ versionsWithEvents }: VersionsSidebarProps) => (
|
||||
<SidebarWrapper>
|
||||
<Column gap={0}>
|
||||
<H3 margin={{ left: 20, top: 8, bottom: 16 }}>Changelog</H3>
|
||||
{versionsWithEvents.map(({ version, events }) => (
|
||||
<Box key={version} padding={{ top: 0, bottom: 16, horizontal: 16 }}>
|
||||
<Badge intent="brand" size="small">
|
||||
{version}
|
||||
</Badge>
|
||||
|
||||
<CardList>
|
||||
{events
|
||||
.sort((a: EventDoc, b: EventDoc) =>
|
||||
(a.name ?? '').localeCompare(b.name ?? ''),
|
||||
)
|
||||
.map(event => (
|
||||
<CardList.Item
|
||||
onClick={() => scrollToEvent(event.name)}
|
||||
key={event.name}
|
||||
>
|
||||
<Text>{event.name}</Text>
|
||||
</CardList.Item>
|
||||
))}
|
||||
</CardList>
|
||||
</Box>
|
||||
))}
|
||||
</Column>
|
||||
</SidebarWrapper>
|
||||
);
|
||||
@@ -3,6 +3,8 @@ import { Icon, Row } from '@trezor/components';
|
||||
import { Platform, Sort } from './types';
|
||||
import { getPlatformIcon } from './utils/getPlatformIcon';
|
||||
|
||||
export const HEADER_HEIGHT = 110;
|
||||
|
||||
const PlatformItem = ({ platform }: { platform: string }) => (
|
||||
<Row alignItems="center" gap={8}>
|
||||
<Icon name={getPlatformIcon(platform)} size="medium" />
|
||||
|
||||
@@ -63,3 +63,39 @@ export const getEventAddedVersion = (e: EventDoc): string | undefined =>
|
||||
|
||||
export const getEventUpdatedVersion = (e: EventDoc): string | undefined =>
|
||||
e.changelog?.lastUpdatedInVersion ?? e.changelog?.addedInVersion;
|
||||
|
||||
/** Safe DOM id for scrolling to an event card. */
|
||||
export const getEventId = (eventName: string): string => `event-${eventName.replace(/\//g, '-')}`;
|
||||
|
||||
export type VersionWithEvents = {
|
||||
version: string;
|
||||
events: EventDoc[];
|
||||
};
|
||||
|
||||
/** Returns versions (desc by last updated) with events that were added or updated in that version. */
|
||||
export const getVersionsWithEvents = (events: EventDoc[]): VersionWithEvents[] => {
|
||||
const versionToEvents = new Map<string, EventDoc[]>();
|
||||
|
||||
for (const event of events) {
|
||||
const added = event.changelog?.addedInVersion;
|
||||
const updated = event.changelog?.lastUpdatedInVersion ?? added;
|
||||
|
||||
if (added) {
|
||||
const list = versionToEvents.get(added) ?? [];
|
||||
list.push(event);
|
||||
versionToEvents.set(added, list);
|
||||
}
|
||||
if (updated && updated !== added) {
|
||||
const list = versionToEvents.get(updated) ?? [];
|
||||
list.push(event);
|
||||
versionToEvents.set(updated, list);
|
||||
}
|
||||
}
|
||||
|
||||
const versions = Array.from(versionToEvents.keys()).sort(compareVersionsDesc);
|
||||
|
||||
return versions.map(version => ({
|
||||
version,
|
||||
events: versionToEvents.get(version) ?? [],
|
||||
}));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user