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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
scripts/raw-github-results.json
secrets.json
.env
.env.local

# Standard stuff
node_modules/**/*
Expand Down
282 changes: 126 additions & 156 deletions bun.lock

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions components/Libraries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Image, StyleSheet, View } from 'react-native';

import { H3, A, P } from '~/common/styleguide';
import LoadingContent from '~/components/Library/LoadingContent';
import { Library as LibraryType } from '~/types';
import { type Library as LibraryType } from '~/types';

type Props = {
libraries: LibraryType[];
Expand All @@ -19,7 +19,7 @@ const Libraries = ({ libraries }: Props) => {
<View style={styles.container}>
<Image style={styles.img} source={require('~/assets/notfound.png')} alt="No results" />
<H3 style={styles.text}>Nothing was found! Try another search.</H3>
<View style={{ marginTop: 20 }} />
<View style={styles.separator} />
<P style={styles.text}>
Want to contribute a library you like? Submit a PR to the{' '}
<A href="https://github.com/react-native-community/react-native-directory">GitHub Repo</A>
Expand All @@ -31,7 +31,7 @@ const Libraries = ({ libraries }: Props) => {

return (
<View style={styles.librariesContainer}>
{libraries.map((item: LibraryType, index: number) => (
{libraries.map((item, index) => (
<LibraryWithLoading key={`list-item-${index}-${item.github.name}`} library={item} />
))}
</View>
Expand All @@ -58,6 +58,9 @@ const styles = StyleSheet.create({
text: {
textAlign: 'center',
},
separator: {
marginTop: 20,
},
});

export default Libraries;
10 changes: 8 additions & 2 deletions components/Library/LoadingContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { useContext } from 'react';
import { SVGAttributes, useContext } from 'react';
import ContentLoader from 'react-content-loader';

import { colors, darkColors, useLayout } from '~/common/styleguide';
import CustomAppearanceContext from '~/context/CustomAppearanceContext';

const LoadingContent = ({ width = '100%', height = 232, wrapperStyle = {} }) => {
type Props = {
width?: string | number;
height?: string | number;
wrapperStyle?: SVGAttributes<SVGSVGElement>['style'];
};

const LoadingContent = ({ width = '100%', height = 202, wrapperStyle = {} }: Props) => {
const { isDark } = useContext(CustomAppearanceContext);
const { isSmallScreen } = useLayout();
return (
Expand Down
10 changes: 5 additions & 5 deletions components/PageMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ type PageMetaProps = {
title?: string;
description?: string;
path?: string;
query?: string | string[];
searchQuery?: string | string[];
};

const PageMeta = ({ title, query, path, description = site.description }: PageMetaProps) => {
const PageMeta = ({ title, searchQuery, path, description = site.description }: PageMetaProps) => {
const pageTitle = `${title ? title + ' β€’ ' : ''}${site.title}`;
const parsedQuery = Array.isArray(query) ? query[0] : query;
const finalDescription = parsedQuery
? `Search results for keyword: '${parsedQuery}'`
const parsedSearchQuery = Array.isArray(searchQuery) ? searchQuery[0] : searchQuery;
const finalDescription = parsedSearchQuery
? `Search results for keyword: '${parsedSearchQuery}'`
: description;

const socialImage = `${BASE_OG_URL}&title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`;
Expand Down
7 changes: 2 additions & 5 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ export default defineConfig([
'@next/next/no-img-element': 'off',
'import/no-named-as-default': 'off',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'react/no-this-in-sfc': 'off',
'react/no-unknown-property': ['error', { ignore: ['css', 'mask-type'] }],
'react/no-unescaped-entities': 'off',
'react/jsx-key': [
'error',
{
Expand All @@ -117,7 +114,7 @@ export default defineConfig([
},
},

// Typed Node files configuration
// Typed Bun files configuration
{
files: ['**/*.ts'],
extends: [universeNodeConfig],
Expand All @@ -128,7 +125,7 @@ export default defineConfig([
},
},

// Non-typed Node files configuration
// Non-typed Bun files configuration
{
files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
extends: [universeNodeConfig],
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,40 @@
"dependencies": {
"@expo/html-elements": "^0.12.5",
"@expo/match-media": "^0.4.0",
"@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-tooltip": "^1.2.7",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-picker/picker": "^2.11.1",
"@sentry/react": "^9.40.0",
"@vercel/blob": "^0.27.3",
"expo": "54.0.0-preview.6",
"expo-font": "^14.0.2",
"expo": "54.0.0-preview.11",
"expo-font": "^14.0.5",
"lodash": "^4.17.21",
"next": "^15.5.0",
"next": "^15.5.2",
"node-emoji": "^2.2.0",
"react": "19.1.1",
"react-content-loader": "^7.1.1",
"react-dom": "19.1.1",
"react-easy-linkify": "^1.0.8",
"react-native": "0.81.0",
"react-native": "0.81.1",
"react-native-safe-area-context": "^5.6.1",
"react-native-svg": "^15.12.1",
"react-native-web": "^0.21.1",
"use-debounce": "^10.0.5"
},
"devDependencies": {
"@expo/next-adapter": "^6.0.0",
"@next/bundle-analyzer": "^15.5.0",
"@types/bun": "^1.2.20",
"@next/bundle-analyzer": "^15.5.2",
"@types/bun": "^1.2.21",
"@types/lodash": "^4.17.20",
"@types/react": "~19.1.11",
"@types/react": "^19.1.12",
"ajv-cli": "^5.0.0",
"browserslist": "^4.25.3",
"browserslist": "^4.25.4",
"cheerio": "^1.1.2",
"cross-fetch": "^4.1.0",
"dotenv": "^17.2.1",
"eslint": "^9.34.0",
"eslint-config-next": "^15.5.0",
"eslint-config-next": "^15.5.2",
"eslint-config-universe": "^15.0.3",
"lint-staged": "^15.5.1",
"next-compose-plugins": "^2.2.1",
Expand Down
14 changes: 4 additions & 10 deletions pages/api/libraries/check.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { type NextApiRequest, type NextApiResponse } from 'next';

import data from '~/assets/data.json';
import { Library } from '~/types';
import { type APIResponseType } from '~/types';
import { getNewArchSupportStatus } from '~/util/newArchStatus';

// Copy data into an object that is keyed by npm package name for faster lookup
const dataByNpmPackage = {};
data.libraries.forEach(library => {
const npmPackageName = getNpmPackageName(library);
dataByNpmPackage[npmPackageName] = {
(data as APIResponseType).libraries.forEach(library => {
dataByNpmPackage[library.npmPkg] = {
unmaintained: library.unmaintained,
newArchitecture: getNewArchSupportStatus(library),
};
Expand Down Expand Up @@ -38,8 +37,3 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {

return res.json(result);
}

// if npmPkg, use that, otherwise use the last path segment on githubUrl
function getNpmPackageName(library: Library): string {
return library.npmPkg ?? library.githubUrl.split('/').pop() ?? '';
}
36 changes: 12 additions & 24 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
import fetch from 'cross-fetch';
import { NextPageContext } from 'next';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { View, StyleSheet } from 'react-native';
import { type ParsedUrlQuery } from 'node:querystring';
import { StyleSheet } from 'react-native';

import ContentContainer from '~/components/ContentContainer';
import LoadingContent from '~/components/Library/LoadingContent';
import Libraries from '~/components/Libraries';
import Navigation from '~/components/Navigation';
import PageMeta from '~/components/PageMeta';
import Pagination from '~/components/Pagination';
import Search from '~/components/Search';
import { type APIResponseType } from '~/types';
import getApiUrl from '~/util/getApiUrl';
import urlWithQuery from '~/util/urlWithQuery';

const LibrariesWithLoading = dynamic(() => import('~/components/Libraries'), {
loading: () => (
<View
style={{
paddingTop: 12,
}}>
<LoadingContent />
<LoadingContent />
<LoadingContent />
<LoadingContent />
<LoadingContent />
<LoadingContent />
<LoadingContent />
<LoadingContent />
</View>
),
});
type Props = {
data: APIResponseType;
query: ParsedUrlQuery;
};

const Index = ({ data, query }) => {
const Index = ({ data, query }: Props) => {
const router = useRouter();
const total = data && data.total;
return (
<>
<PageMeta query={router.query?.search} />
<PageMeta searchQuery={router.query?.search} />
<Navigation noHeader />
<Search query={router.query} total={total} />
<ContentContainer style={styles.container}>
<Pagination query={query} total={total} />
<LibrariesWithLoading libraries={data && data.libraries} />
<Libraries libraries={data && data.libraries} />
<Pagination query={query} total={total} />
</ContentContainer>
</>
Expand All @@ -51,7 +39,7 @@ const Index = ({ data, query }) => {
Index.getInitialProps = async (ctx: NextPageContext) => {
const url = getApiUrl(urlWithQuery('/libraries', ctx.query), ctx);
const response = await fetch(url);
const result = await response.json();
const result: APIResponseType = await response.json();

return {
data: result,
Expand Down
9 changes: 7 additions & 2 deletions pages/popular.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import {
} from '~/components/Icons';
import Navigation from '~/components/Navigation';
import PageMeta from '~/components/PageMeta';
import { type APIResponseType, type Library } from '~/types';
import getApiUrl from '~/util/getApiUrl';
import urlWithQuery from '~/util/urlWithQuery';

const Popular = ({ data }) => {
type Props = {
data: Library[];
};

const Popular = ({ data }: Props) => {
return (
<>
<PageMeta
Expand Down Expand Up @@ -101,7 +106,7 @@ Popular.getInitialProps = async (ctx: NextPageContext) => {
ctx
);
const response = await fetch(url);
const result = await response.json();
const result: APIResponseType = await response.json();

return {
data: result.libraries,
Expand Down
16 changes: 13 additions & 3 deletions pages/trending.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@ import Navigation from '~/components/Navigation';
import PageMeta from '~/components/PageMeta';
import Pagination from '~/components/Pagination';
import CustomAppearanceContext from '~/context/CustomAppearanceContext';
import { Library as LibraryType, QueryOrder } from '~/types';
import {
type APIResponseType,
type Library as LibraryType,
type Query,
type QueryOrder,
} from '~/types';
import getApiUrl from '~/util/getApiUrl';
import urlWithQuery from '~/util/urlWithQuery';

const LibraryWithLoading = dynamic(() => import('~/components/Library'), {
loading: () => <LoadingContent />,
});

const Trending = ({ data, query }) => {
type Props = {
data: APIResponseType;
query: Query;
};

const Trending = ({ data, query }: Props) => {
const [isFilterVisible, setFilterVisible] = useState(Object.keys(query).length > 2);
const { isDark } = useContext(CustomAppearanceContext);
const total = data && data.total;
Expand Down Expand Up @@ -97,7 +107,7 @@ Trending.getInitialProps = async (ctx: NextPageContext) => {
};
const url = getApiUrl(urlWithQuery('/libraries', trendingQuery), ctx);
const response = await fetch(url);
const result = await response.json();
const result: APIResponseType = await response.json();

return {
data: result,
Expand Down
5 changes: 5 additions & 0 deletions types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,8 @@ export type LibraryDataEntry = {
examples?: string[];
images?: string[];
};

export type APIResponseType = {
libraries: Library[];
total?: number;
};
2 changes: 1 addition & 1 deletion util/newArchStatus.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Library } from '~/types';
import { type Library } from '~/types';

export enum NewArchSupportStatus {
NewArchOnly = 'new-arch-only',
Expand Down
2 changes: 1 addition & 1 deletion util/urlWithQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Query } from '../types';
import { Query } from '~/types';

function toQueryString(query: Query) {
return new URLSearchParams(query as Record<string, string>).toString();
Expand Down