Skip to content
Merged
Show file tree
Hide file tree
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
30 changes: 30 additions & 0 deletions components/InputKeyHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Label } from '~/common/styleguide';
import tw from '~/util/tailwind';

type Props = {
content: {
key?: string;
label?: string;
}[];
};

export const focusHintLabel = tw`font-light text-palette-gray4`;
export const focusHintKey = tw`min-w-6 rounded-[3px] bg-palette-gray5 px-1 py-[3px] text-center tracking-[0.75px] text-tertiary dark:bg-powder`;

export default function InputKeyHint({ content }: Props) {
return content.map(entry => {
if ('key' in entry) {
return (
<Label key={`key-${entry.key}`} style={focusHintKey}>
{entry.key}
</Label>
);
} else if ('label' in entry) {
return (
<Label key={`key-${entry.label}`} style={focusHintLabel}>
{entry.label}
</Label>
);
}
});
}
22 changes: 5 additions & 17 deletions components/Package/VersionDownloadsChart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ParentSize } from '@visx/responsive';
import { Axis, BarSeries, Grid, Tooltip, XYChart } from '@visx/xychart';
import { keyBy } from 'es-toolkit/array';
import { omit } from 'es-toolkit/object';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react';
import { Text, View } from 'react-native';

import { Label } from '~/common/styleguide';
import { type NpmPerVersionDownloads, type NpmRegistryData } from '~/types';
import { replaceQueryParam } from '~/util/queryParams';
import { formatNumberToString, pluralize } from '~/util/strings';
import tw from '~/util/tailwind';

Expand Down Expand Up @@ -91,22 +91,10 @@ export default function VersionDownloadsChart({ npmDownloads, registryData }: Pr
}

setMode(nextMode);

const queryParams = omit(router.query, [CHART_MODE_QUERY_PARAM]);

void router.replace(
{
pathname: router.pathname,
query:
nextMode === DEFAULT_CHART_MODE
? queryParams
: { ...queryParams, [CHART_MODE_QUERY_PARAM]: nextMode },
},
undefined,
{
shallow: true,
scroll: false,
}
replaceQueryParam(
router,
CHART_MODE_QUERY_PARAM,
nextMode === DEFAULT_CHART_MODE ? undefined : nextMode
);
}

Expand Down
160 changes: 160 additions & 0 deletions components/Package/VersionsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState } from 'react';
import { type ColorValue, TextInput, View } from 'react-native';
import { useDebouncedCallback } from 'use-debounce';

import { Caption, H6, Label, useLayout } from '~/common/styleguide';
import { Button } from '~/components/Button';
import { Search } from '~/components/Icons';
import InputKeyHint from '~/components/InputKeyHint';
import { type NpmPerVersionDownloads, type NpmRegistryData } from '~/types';
import { parseQueryParams, replaceQueryParam } from '~/util/queryParams';
import { pluralize } from '~/util/strings';
import tw from '~/util/tailwind';

import VersionBox from './VersionBox';

const VERSIONS_TO_SHOW = 25;

type Props = {
registryData: NpmRegistryData;
npmDownloads?: NpmPerVersionDownloads;
};

export default function VersionsSection({ registryData, npmDownloads }: Props) {
const router = useRouter();
const { isSmallScreen } = useLayout();

const [shouldShowAll, setShowAll] = useState(false);
const [isInputFocused, setInputFocused] = useState(false);
const inputRef = useRef<TextInput>(null);

const routeVersionSearch = useMemo(
() => parseQueryParams(router.query).versionSearch?.toLowerCase() ?? '',
[router.query]
);
const [versionSearch, setVersionSearch] = useState(routeVersionSearch);

useEffect(() => {
setVersionSearch(currentVersionSearch =>
currentVersionSearch === routeVersionSearch ? currentVersionSearch : routeVersionSearch
);
}, [routeVersionSearch]);

useEffect(() => setShowAll(false), [versionSearch]);

const versions = useMemo(
() =>
Object.entries(registryData.versions).sort(
(a, b) => -registryData.time[a[1].version].localeCompare(registryData.time[b[1].version])
),
[registryData]
);

const filteredVersions = useMemo(
() =>
versionSearch
? versions.filter(([version, versionData]) =>
[version, versionData.version].some(value =>
value.toLowerCase().includes(versionSearch)
)
)
: versions,
[versionSearch, versions]
);
const visibleVersions = useMemo(
() => filteredVersions.slice(0, shouldShowAll ? filteredVersions.length : VERSIONS_TO_SHOW),
[filteredVersions, shouldShowAll]
);

const updateVersionSearchQuery = useDebouncedCallback((versionSearch: string) => {
replaceQueryParam(router, 'versionSearch', versionSearch);
}, 200);

return (
<>
<H6 style={tw`mt-3 flex items-end justify-between text-secondary`}>
<span>Versions</span>
<Label style={tw`font-light text-secondary`}>
<span style={tw`font-medium text-primary-darker dark:text-primary-dark`}>
{filteredVersions.length}
</span>{' '}
matching {pluralize('version', filteredVersions.length)}
</Label>
</H6>
<View style={tw`gap-2`}>
<View
style={tw`flex-row items-center rounded-lg border-2 border-default bg-palette-gray1 dark:bg-dark`}>
<View style={tw`pointer-events-none absolute left-4`}>
<Search style={tw`text-icon`} />
</View>
<TextInput
ref={inputRef}
id="version-search"
autoComplete="off"
value={versionSearch}
onChangeText={text => {
setVersionSearch(text);
updateVersionSearchQuery(text.trim());
}}
onKeyPress={event => {
if ('key' in event) {
if (inputRef.current && event.key === 'Escape') {
if (versionSearch) {
event.preventDefault();
inputRef.current.clear();
setVersionSearch('');
replaceQueryParam(router, 'versionSearch', undefined);
} else {
inputRef.current.blur();
}
}
}
}}
onFocus={() => setInputFocused(true)}
onBlur={() => setInputFocused(false)}
placeholder="Filter versions…"
style={tw`h-11 flex-1 rounded-lg bg-palette-gray1 p-3 pl-11 text-base text-black dark:bg-dark dark:text-white`}
placeholderTextColor={tw`text-palette-gray4`.color as ColorValue}
/>
{!isSmallScreen && (
<View style={tw`pointer-events-none absolute right-4 flex-row items-center gap-1`}>
{isInputFocused && (
<InputKeyHint
content={[
{ label: 'press' },
{ key: 'Esc' },
{ label: `to ${(versionSearch?.length ?? 0) > 0 ? 'clear' : 'blur'}` },
]}
/>
)}
</View>
)}
</View>
</View>
<View style={tw`gap-2`}>
{visibleVersions.length ? (
visibleVersions.map(([version, versionData]) => (
<VersionBox
key={version}
time={registryData.time[versionData.version]}
versionData={versionData}
downloads={npmDownloads?.downloads[versionData.version]}
/>
))
) : (
<View style={tw`rounded-xl border border-dashed border-default px-4 py-5`}>
<Label style={tw`text-center text-secondary`}>
No versions match &quot;{versionSearch?.trim()}&quot; query.
</Label>
</View>
)}
</View>
{!shouldShowAll && filteredVersions.length > VERSIONS_TO_SHOW && (
<Button onPress={() => setShowAll(true)} style={tw`mx-auto mt-2 px-4 py-2`}>
<Caption style={tw`text-white`}>Show all versions</Caption>
</Button>
)}
</>
);
}
43 changes: 18 additions & 25 deletions components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useEffect, useEffectEvent, useMemo, useRef, useState } from 'react';
import { type ColorValue, type StyleProp, TextInput, View, type ViewStyle } from 'react-native';
import { useDebouncedCallback } from 'use-debounce';

import { Label, P, useLayout } from '~/common/styleguide';
import { P, useLayout } from '~/common/styleguide';
import InputKeyHint from '~/components/InputKeyHint';
import { type Query } from '~/types';
import isAppleDevice from '~/util/isAppleDevice';
import tw from '~/util/tailwind';
Expand Down Expand Up @@ -61,9 +62,6 @@ export default function Search({ query, total, style, isHomePage = false }: Prop
void replace(urlWithQuery('/packages', { search, offset: undefined }));
}

const focusHintLabel = tw`font-light text-palette-gray4`;
const focusHintKey = tw`min-w-6 rounded-[3px] bg-palette-gray5 px-1 py-[3px] text-center tracking-[0.75px] text-tertiary dark:bg-powder`;

return (
<>
<View style={[tw`items-center bg-palette-gray6 py-3.5 dark:bg-dark`, style]}>
Expand Down Expand Up @@ -123,28 +121,23 @@ export default function Search({ query, total, style, isHomePage = false }: Prop
{!isSmallScreen && (
<View style={tw`pointer-events-none absolute right-4 flex-row items-center gap-1`}>
{isInputFocused ? (
<>
<Label style={focusHintLabel}>press</Label>
{isHomePage ? (
<>
<Label style={focusHintKey}>Enter</Label>
<Label style={focusHintLabel}>to search</Label>
</>
) : (
<>
<Label style={focusHintKey}>Esc</Label>
<Label style={focusHintLabel}>
to {(search?.length ?? 0) > 0 ? 'clear' : 'blur'}
</Label>
</>
)}
</>
isHomePage ? (
<InputKeyHint
content={[{ label: 'press' }, { key: 'Enter' }, { label: 'to search' }]}
/>
) : (
<InputKeyHint
content={[
{ label: 'press' },
{ key: 'Esc' },
{ label: `to ${(search?.length ?? 0) > 0 ? 'clear' : 'blur'}` },
]}
/>
)
) : (
<>
<Label style={focusHintKey}>{isApple ? 'Cmd' : 'Ctrl'}</Label>
<Label style={focusHintLabel}>+</Label>
<Label style={focusHintKey}>K</Label>
</>
<InputKeyHint
content={[{ key: isApple ? 'Cmd' : 'Ctrl' }, { label: '+' }, { key: 'K' }]}
/>
)}
</View>
)}
Expand Down
2 changes: 1 addition & 1 deletion pages/api/libraries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import data from '~/assets/data.json';
import { getBookmarksFromCookie } from '~/context/BookmarksContext';
import { type DataAssetType, type QueryOrder, type SortedDataType } from '~/types';
import { NUM_PER_PAGE } from '~/util/Constants';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { handleFilterLibraries } from '~/util/search';
import * as Sorting from '~/util/sorting';

Expand Down
2 changes: 1 addition & 1 deletion pages/api/library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type NextApiRequest, type NextApiResponse } from 'next';

import data from '~/assets/data.json';
import { type DataAssetType } from '~/types';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';

const DATASET = data as DataAssetType;

Expand Down
2 changes: 1 addition & 1 deletion pages/api/proxy/npm-stat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type NextApiRequest, type NextApiResponse } from 'next';

import { NEXT_10M_CACHE_HEADER } from '~/util/Constants';
import { TimeRange } from '~/util/datetime';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { name } = parseQueryParams(req.query);
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/[scopedName]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageOverviewScene from '~/scenes/PackageOverviewScene';
import { type PackageOverviewPageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA, NEXT_10M_CACHE_HEADER } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function ScopedOverviewPage({
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/[scopedName]/score.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageScoreScene from '~/scenes/PackageScoreScene';
import { type PackageScorePageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function ScorePage({ apiData, packageName, errorMessage }: PackageScorePageProps) {
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/[scopedName]/versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageVersionsScene from '~/scenes/PackageVersionsScene';
import { type PackageVersionsPageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA, NEXT_10M_CACHE_HEADER } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function ScopedVersionsPage({
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageOverviewScene from '~/scenes/PackageOverviewScene';
import { type PackageOverviewPageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA, NEXT_10M_CACHE_HEADER } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function OverviewPage({
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/score.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageScoreScene from '~/scenes/PackageScoreScene';
import { type PackageScorePageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function ScorePage({ apiData, packageName, errorMessage }: PackageScorePageProps) {
Expand Down
2 changes: 1 addition & 1 deletion pages/package/[name]/versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageVersionsScene from '~/scenes/PackageVersionsScene';
import { type PackageVersionsPageProps } from '~/types/pages';
import { EMPTY_PACKAGE_DATA, NEXT_10M_CACHE_HEADER } from '~/util/Constants';
import { getPackagePageErrorProps } from '~/util/getPackagePageErrorProps';
import { parseQueryParams } from '~/util/parseQueryParams';
import { parseQueryParams } from '~/util/queryParams';
import { ssrFetch } from '~/util/SSRFetch';

export default function VersionsPage({
Expand Down
Loading
Loading