feat: add new ecosystem page (#1342)

This commit is contained in:
Daniel Schlabach
2024-12-06 15:40:57 -05:00
committed by GitHub
parent 5c7bed94b3
commit b21cf9cdbe
19 changed files with 1743 additions and 2077 deletions

View File

@@ -24,8 +24,6 @@
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// ESLint
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.packageManager": "yarn",
"eslint.useESLintClass": true,

View File

@@ -119,7 +119,7 @@ function IconLink({
function DesktopNav({ color }: DesktopNavProps) {
return (
<div className="hidden h-full w-fit flex-grow flex-row items-center items-center justify-between lg:flex">
<div className="hidden h-full w-fit flex-grow flex-row items-center justify-between lg:flex">
<Dropdown label="Ecosystem" color={color}>
<DropdownLink href="https://base.org/ecosystem" label="Apps" color={color} />
<DropdownLink

1
apps/web/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vercel

View File

@@ -1,4 +1,5 @@
import type { Metadata } from 'next';
import { Suspense } from 'react';
import Content from 'apps/web/src/components/Ecosystem/Content';
import Container from 'apps/web/src/components/base-org/Container';
import Button from 'apps/web/src/components/base-org/Button';
@@ -30,8 +31,8 @@ async function EcosystemHero() {
return (
<div className="flex w-full flex-col items-center overflow-hidden bg-black pb-20 pt-20">
<Container>
<div className="flex w-full flex-col items-center justify-between gap-12 py-20 md:flex-row">
<div className="flex w-full w-full flex-col gap-8 md:max-w-lg">
<div className="flex w-full flex-col items-center justify-between gap-12 py-20 md:flex-row">
<div className="flex w-full flex-col gap-8 md:max-w-lg">
<Title level={TitleLevel.Display3}>
Base ecosystem apps and integrations overview.
</Title>
@@ -39,11 +40,13 @@ async function EcosystemHero() {
href="https://github.com/base-org/web?tab=readme-ov-file#updating-the-base-ecosystem-page"
target="_blank"
rel="noreferrer noopener"
className="max-w-fit"
tabIndex={-1} // Prevents focus on anchor (want to focus on button)
>
<Button variant={ButtonVariants.Secondary}>Submit your app</Button>
</a>
</div>
<div className="flex flex-col ">
<div className="flex flex-col">
<div className="flex flex-shrink-0 justify-center gap-4">
{topKeys.map((key, i) => (
<div key={key} className="w-[80px] md:w-[100px]">
@@ -78,7 +81,9 @@ export default async function Ecosystem() {
<EcosystemHero />
<Container>
<Content />
<Suspense fallback={<div />}>
<Content />
</Suspense>
</Container>
</main>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,21 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1260_71)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8 0C12.4183 0 16 3.58171 16 8C16 12.4183 12.4183 16 8 16C3.58171 16 0 12.4183 0 8C0 3.58171 3.58171 0 8 0Z"
fill="#2775CA" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M9.82791 2.36692C9.64751 2.30928 9.5 2.41636 9.5 2.60575V3.07154C9.5 3.19854 9.59572 3.34307 9.71493 3.38677C11.6298 4.08811 13 5.92861 13 8.0834C13 10.2382 11.6298 12.0786 9.71493 12.78C9.58417 12.8279 9.5 12.956 9.5 13.0953V13.561C9.5 13.7504 9.64751 13.8575 9.82791 13.7999C12.2477 13.0267 14 10.7597 14 8.0834C14 5.4071 12.2477 3.14006 9.82791 2.36692Z"
fill="white" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M6.50002 2.60575C6.50002 2.41636 6.3525 2.30928 6.17211 2.36692C3.75225 3.14006 2 5.40707 2 8.0834C2 10.7597 3.75225 13.0267 6.17211 13.7999C6.3525 13.8575 6.50002 13.7504 6.50002 13.561V13.0953C6.50002 12.9682 6.4043 12.8237 6.28505 12.78C4.37026 12.0786 3 10.2382 3 8.0834C3 5.92864 4.37026 4.08811 6.28505 3.38677C6.4043 3.34307 6.50002 3.19854 6.50002 3.07154V2.60575Z"
fill="white" />
<path
d="M10.0458 9.64461L9.58817 9.44015C9.48742 9.39513 9.36859 9.43726 9.31981 9.53626C9.10382 9.97463 8.74483 10.254 8.2706 10.254C7.84125 10.254 7.49688 10.0438 7.23301 9.61889C7.12982 9.45449 7.04785 9.271 6.98543 9.06994H8.21433C8.29222 9.06994 8.36349 9.0261 8.39861 8.95656L8.69185 8.5608C8.76119 8.42349 8.6614 8.26129 8.50757 8.26129H6.84714C6.84233 8.17616 6.83943 8.08932 6.83943 7.99988C6.83851 7.91118 6.84101 7.82431 6.84563 7.73875H8.21433C8.29222 7.73875 8.36349 7.6949 8.39861 7.62537L8.69185 7.22961C8.76119 7.0923 8.6614 6.9301 8.50757 6.9301H6.98392C7.20679 6.20352 7.67203 5.72749 8.2706 5.73684C8.72671 5.73684 9.08082 5.98657 9.30797 6.39555C9.35888 6.48723 9.47342 6.52236 9.56935 6.48001L10.0293 6.27694C10.1394 6.22834 10.1862 6.09525 10.1279 5.98999C9.70333 5.2231 9.08423 4.83789 8.2706 4.83789C7.52817 4.83789 6.93334 5.13307 6.48162 5.71896C6.21761 6.06403 6.03383 6.46891 5.92476 6.9301H5.25655C5.17865 6.9301 5.10739 6.97395 5.07228 7.04347L4.77903 7.43924C4.70968 7.57655 4.80948 7.73875 4.9633 7.73875H5.81435C5.81024 7.82471 5.80632 7.9108 5.80632 7.99988C5.80523 8.08821 5.80731 8.17515 5.81105 8.26129H5.25655C5.17865 8.26129 5.10739 8.30514 5.07228 8.37467L4.77903 8.77043C4.70968 8.90774 4.80948 9.06994 4.9633 9.06994H5.92282C6.22628 10.3242 7.09991 11.1796 8.2706 11.1619C9.10177 11.1619 9.72828 10.7487 10.1465 9.92585C10.1998 9.8211 10.1531 9.69254 10.0458 9.64461Z"
fill="white" />
</g>
<defs>
<clipPath id="clip0_1260_71">
<rect width="100%" height="100%" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,5 @@
<svg data-name="86977684-12db-4850-8f30-233a7c267d11" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
<path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/>
<path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/>
<path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -8,14 +8,22 @@ type Props = {
url: string;
description: string;
imageUrl: string;
tags: string[];
category: string;
subcategory: string;
};
function getNiceDomainDisplayFromUrl(url: string) {
return url.replace('https://', '').replace('http://', '').replace('www.', '').split('/')[0];
}
export default function EcosystemCard({ name, url, description, imageUrl, tags }: Props) {
export default function EcosystemCard({
name,
url,
description,
imageUrl,
category,
subcategory,
}: Props) {
return (
<Card innerClassName="p-4 group/ecosystem-card">
<a
@@ -43,10 +51,17 @@ export default function EcosystemCard({ name, url, description, imageUrl, tags }
/>
</div>
</div>
<div className="flex h-6 flex-col justify-center rounded-[100px] bg-black px-2 py-1">
<span className="rounded-full border border-white px-2 py-1 font-mono text-xs uppercase text-white">
{tags[0]}
</span>
<div className="flex flex-col items-end gap-y-2.5">
<div className="flex h-6 flex-col justify-center rounded-[100px] bg-black px-2 py-1">
<span className="rounded-full border border-white px-2 py-1 font-mono text-xs uppercase text-white">
{category}
</span>
</div>
<div className="flex h-6 flex-col justify-center rounded-[100px] bg-black px-2 py-1">
<span className="rounded-full border border-gray-muted px-2 py-1 font-mono text-[10px] uppercase text-gray-muted">
{subcategory}
</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">

View File

@@ -1,29 +1,60 @@
'use client';
import ecosystemApps from 'apps/web/src/data/ecosystem.json';
import { TagChip } from 'apps/web/src/components/Ecosystem/TagChip';
import { SearchBar } from 'apps/web/src/components/Ecosystem/SearchBar';
import { useMemo, useState } from 'react';
import { useMemo, useState, useEffect } from 'react';
import { List } from 'apps/web/src/components/Ecosystem/List';
import { useSearchParams } from 'next/navigation';
import { EcosystemFilters } from 'apps/web/src/components/Ecosystem/EcosystemFilters';
import EcosystemFiltersMobile from 'apps/web/src/components/Ecosystem/EcosystemFiltersMobile';
export type EcosystemApp = {
searchName: string;
name: string;
category: string;
subcategory: string;
url: string;
description: string;
tags: string[];
imageUrl: string;
};
const tags = [
'all',
...ecosystemApps
.map((app) => app.tags)
.flat()
.filter((value, index, array) => {
return array.indexOf(value.toLocaleLowerCase()) === index;
}),
];
const config: Record<string, string[]> = {
wallet: ['account abstraction', 'multisig', 'self-custody'],
defi: [
'dex',
'dex aggregator',
'insurance',
'lending/borrowing',
'liquidity management',
'portfolio',
'stablecoin',
'yield vault',
],
consumer: [
'creator',
'crypto taxes',
'dao',
'gaming',
'messaging',
'music',
'nft',
'payments',
'real world',
'social',
],
onramp: ['centralized exchange', 'fiat on-ramp'],
infra: [
'ai',
'bridge',
'data',
'depin',
'developer tool',
'identity',
'node provider',
'raas',
'security',
],
};
function orderedEcosystemAppsAsc() {
return ecosystemApps.sort((a, b) => {
@@ -42,51 +73,90 @@ const decoratedEcosystemApps: EcosystemApp[] = orderedEcosystemAppsAsc().map((d)
searchName: d.name.toLowerCase(),
}));
const updateUrlParams = (params: { categories?: string[]; subcategories?: string[] }) => {
const searchParams = new URLSearchParams(window.location.search);
if (params.categories?.length) {
searchParams.set('category', params.categories.join(','));
} else {
searchParams.delete('category');
}
if (params.subcategories?.length) {
searchParams.set('subcategory', params.subcategories.join(','));
} else {
searchParams.delete('subcategory');
}
window.history.pushState(
{},
'',
`${window.location.pathname}${searchParams.toString() ? '?' + searchParams.toString() : ''}`,
);
};
export default function Content() {
const [selectedTags, setSelectedTags] = useState<string[]>(['all']);
const [search, setSearch] = useState<string>('');
const [search, setSearch] = useState('');
const [showCount, setShowCount] = useState<number>(16);
const searchParams = useSearchParams();
const [selectedSubcategories, setSelectedSubcategories] = useState<string[]>(() => {
const subcategories = searchParams?.get('subcategory');
return subcategories ? subcategories.split(',') : [];
});
const selectTag = (tag: string): void => {
setSelectedTags((prevTags) => {
if (tag === 'all') {
return ['all'];
}
const newTags = prevTags.includes(tag)
? prevTags.filter((t) => t !== tag)
: [...prevTags.filter((t) => t !== 'all'), tag];
return newTags.length === 0 ? ['all'] : newTags;
});
};
// If a subcategory is selected, the category is selected automatically
const selectedCategories = useMemo(
() => [
...new Set(
selectedSubcategories.map(
(subcategory) =>
Object.keys(config).find((category) => config[category].includes(subcategory)) ?? 'all',
),
),
],
[selectedSubcategories],
);
const filteredEcosystemApps = useMemo(() => {
return decoratedEcosystemApps.filter((app) => {
const isTagged =
selectedTags.includes('all') || app.tags.some((tag) => selectedTags.includes(tag));
const isSearched = search === '' || app.name.toLowerCase().includes(search.toLowerCase());
return isTagged && isSearched;
const filteredEcosystemApps = useMemo(
() =>
decoratedEcosystemApps.filter((app) => {
const isSubcategoryMatched =
selectedSubcategories.length === 0 || selectedSubcategories.includes(app.subcategory);
const isSearched = search === '' || app.searchName.includes(search.toLowerCase());
return isSubcategoryMatched && isSearched;
}),
[selectedSubcategories, search],
);
useEffect(() => {
updateUrlParams({
categories: selectedCategories,
subcategories: selectedSubcategories,
});
}, [selectedTags, search]);
}, [selectedCategories, selectedSubcategories]);
return (
<div className="flex min-h-32 w-full flex-col gap-10 pb-32">
<div className="flex flex-col justify-between gap-8 lg:flex-row lg:gap-12">
<div className="flex flex-row flex-wrap gap-3">
{tags.map((tag) => (
<TagChip
tag={tag}
isSelected={selectedTags.includes(tag)}
key={tag}
selectTag={selectTag}
/>
))}
</div>
<div className="order-first grow lg:order-last">
<EcosystemFilters
config={config}
selectedCategories={selectedCategories}
selectedSubcategories={selectedSubcategories}
setSelectedSubcategories={setSelectedSubcategories}
/>
<div className="order-first lg:order-last">
<SearchBar search={search} setSearch={setSearch} />
</div>
<EcosystemFiltersMobile
categories={config}
selectedSubcategories={selectedSubcategories}
onSubcategorySelect={setSelectedSubcategories}
/>
</div>
<List
selectedTags={selectedTags}
selectedCategories={selectedCategories}
searchText={search}
apps={filteredEcosystemApps}
showCount={showCount}

View File

@@ -0,0 +1,145 @@
'use client';
import Card from '../base-org/Card';
import classNames from 'classnames';
import * as Popover from '@radix-ui/react-popover';
import { ChevronDownIcon, CheckIcon } from '@heroicons/react/24/outline';
type EcosystemFiltersProps = {
selectedCategories: string[];
selectedSubcategories: string[];
setSelectedSubcategories: (subcategories: string[]) => void;
config: Record<string, string[]>;
};
export function EcosystemFilters({
selectedCategories,
selectedSubcategories,
setSelectedSubcategories,
config,
}: EcosystemFiltersProps) {
const categories = ['all', ...Object.keys(config)];
const handleCategorySelect = (category: string) => {
if (category === 'all') {
setSelectedSubcategories([]);
return;
}
const categorySubcats = config[category] || [];
const hasAnyCategorySelected = categorySubcats.some((sub) =>
selectedSubcategories.includes(sub),
);
if (hasAnyCategorySelected) {
setSelectedSubcategories(
selectedSubcategories.filter((sub) => !categorySubcats.includes(sub)),
);
} else {
setSelectedSubcategories([...selectedSubcategories, ...categorySubcats]);
}
};
const handleSubcategorySelect = (subcategory: string) => {
if (selectedSubcategories.includes(subcategory)) {
setSelectedSubcategories(selectedSubcategories.filter((sub) => sub !== subcategory));
} else {
setSelectedSubcategories([...selectedSubcategories, subcategory]);
}
};
const isAllActive = selectedSubcategories.length === 0;
return (
<div className="relative flex flex-col items-start gap-2">
<div className="relative flex flex-wrap gap-2">
{categories.map((category, index) => {
const categoryIsSelected = selectedCategories.includes(category);
return index === 0 ? (
<button
type="button"
key={category}
onClick={() => handleCategorySelect('all')}
className={classNames(
'h-10 whitespace-nowrap rounded-full border border-white/20 px-4 uppercase tracking-wider transition-colors',
{
'bg-white text-black': isAllActive,
'text-white/50 hover:bg-white/20 hover:text-white': !isAllActive,
},
)}
>
{category}
</button>
) : (
<Popover.Root key={category}>
<div className="flex h-10 items-stretch">
<button
type="button"
className={classNames(
'peer rounded-full border border-white/20 px-4 uppercase tracking-wider transition-colors sm:rounded-r-none sm:border-r-0',
categoryIsSelected
? 'bg-white text-black'
: 'text-white/50 hover:bg-white/20 hover:text-white',
)}
onClick={() => handleCategorySelect(category)}
>
{category}
</button>
<div className="hidden w-px bg-white/20 peer-hover:bg-white/30 sm:block" />
<Popover.Trigger asChild>
<button
type="button"
aria-label="Open Subcategory Menu"
className={classNames(
'group peer hidden rounded-r-full border border-white/20 px-2 pr-3 transition-colors sm:block sm:border-l-0',
categoryIsSelected
? 'bg-white text-black'
: 'text-white/50 hover:bg-white/20 hover:text-white',
)}
>
<ChevronDownIcon className="h-4 w-4 transition-transform ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-180" />
</button>
</Popover.Trigger>
</div>
<Popover.Portal>
<Popover.Content className="z-50 w-64" sideOffset={5}>
<Card radius={8} innerClassName="bg-[#191919] p-4">
<div className="flex flex-col gap-2">
{config[category]?.map((subcategory) => {
const subcategoryIsSelected = selectedSubcategories.includes(subcategory);
return (
<button
key={subcategory}
type="button"
onClick={() => handleSubcategorySelect(subcategory)}
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm uppercase transition-colors hover:bg-white/10"
>
<div className="h-4 w-4 flex-shrink-0">
{subcategoryIsSelected && (
<CheckIcon className="h-4 w-4 text-white" />
)}
</div>
<span
className={classNames(
subcategoryIsSelected ? 'text-white' : 'text-white/50',
)}
>
{subcategory}
</span>
</button>
);
})}
</div>
</Card>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,86 @@
import { TagChip } from 'apps/web/src/components/Ecosystem/TagChip';
import { Icon } from 'apps/web/src/components/Icon/Icon';
import { useCallback, useState } from 'react';
export default function EcosystemFiltersMobile({
categories,
selectedSubcategories,
onSubcategorySelect,
}: {
categories: Record<string, string[]>;
selectedSubcategories: string[];
onSubcategorySelect: (subcategories: string[]) => void;
}) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const toggleMenu = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen]);
const handleSubcategorySelect = useCallback(
(subcategory: string) => {
if (selectedSubcategories.includes(subcategory)) {
onSubcategorySelect(selectedSubcategories.filter((sc) => sc !== subcategory));
} else {
onSubcategorySelect([...selectedSubcategories, subcategory]);
}
},
[onSubcategorySelect, selectedSubcategories],
);
return (
<div className="mr-auto sm:hidden">
<button
type="button"
onClick={toggleMenu}
aria-label="Open Menu"
className="h-10 whitespace-nowrap rounded-full border border-white/20 px-4 uppercase tracking-wider text-white/50 transition-colors hover:bg-white/20 hover:text-white"
>
Advanced Filters
</button>
{isOpen && (
<div className="fixed inset-0 z-50 overflow-auto bg-black px-[1.75rem] pb-20 pt-5">
<div className="mb-8 flex h-10 items-end justify-between gap-4">
{selectedSubcategories.length > 0 ? (
<TagChip
tag="Clear filters"
isSelected={false}
selectTag={() => onSubcategorySelect([])}
className="text-xs"
/>
) : (
<div />
)}
<button
type="button"
onClick={toggleMenu}
aria-label="Close Menu"
className="relative z-20 rounded-xl bg-black px-4 py-2 pr-1"
>
<Icon name="close" color="currentColor" height="1rem" width="1rem" />
</button>
</div>
<div className="flex flex-col gap-5">
{Object.entries(categories).map(([category, subcategories]) => (
<div key={`${category}-mobile`} className="flex flex-col gap-1 uppercase">
<div className="font-medium">{category}</div>
<div className="flex flex-wrap gap-2">
{subcategories.map((subcategory) => (
<TagChip
key={subcategory}
tag={subcategory}
isSelected={selectedSubcategories.includes(subcategory)}
selectTag={handleSubcategorySelect}
className="text-xs"
/>
))}
</div>
</div>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -8,13 +8,13 @@ import Button from 'apps/web/src/components/base-org/Button';
import { ButtonSizes, ButtonVariants } from 'apps/web/src/components/base-org/Button/types';
export function List({
selectedTags,
selectedCategories,
searchText,
apps,
showCount,
setShowCount,
}: {
selectedTags: string[];
selectedCategories: string[];
searchText: string;
apps: EcosystemApp[];
showCount: number;
@@ -43,7 +43,7 @@ export function List({
<div className="flex flex-col items-center gap-12">
<ImageAdaptive src={ErrorImg} alt="No search results" />
<span className="font-mono text-4xl text-white">
NO RESULTS FOR &ldquo;{searchText === '' ? selectedTags.join(', ') : searchText}
NO RESULTS FOR &ldquo;{searchText === '' ? selectedCategories.join(', ') : searchText}
&rdquo;
</span>
<span className="font-sans text-gray-muted">Try searching for another term</span>

View File

@@ -7,9 +7,10 @@ type Props = {
tag: string;
isSelected: boolean;
selectTag: (tag: string) => void;
className?: string;
};
export function TagChip({ tag, isSelected, selectTag }: Props) {
export function TagChip({ tag, isSelected, selectTag, className }: Props) {
const onClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
@@ -19,8 +20,8 @@ export function TagChip({ tag, isSelected, selectTag }: Props) {
);
const buttonClasses = classNames(
'uppercase tracking-wider border border-white/20 h-10 whitespace-nowrap rounded-full px-4 transition-colors ',
'uppercase tracking-wider border border-white/20 h-10 whitespace-nowrap rounded-full px-4 transition-colors',
className,
{
'bg-white text-black': isSelected,
'text-white/50 hover:bg-white/20 hover:text-white': !isSelected,

File diff suppressed because it is too large Load Diff