Skip to content
Open
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
6 changes: 6 additions & 0 deletions fern/assets/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ button[class^="Sidebar-link-buttonWrapper"] {
color: #ebebf8;
white-space: nowrap;
}
/*
Styles for cookbooks
*/
.dark-theme-text-color {
color: var(--body-text);
}
}

.small-tag-container {
Expand Down
41 changes: 41 additions & 0 deletions fern/components/cookbooks/CookbookCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { Cookbook } from './types';
import { generateDisplayTag } from './data/tags';

interface CookbookCardProps {
cookbook: Cookbook;
bgColor: string;
}

export const CookbookCard: React.FC<CookbookCardProps> = ({ cookbook, bgColor }) => (
<a
href={cookbook.href}
className={`block border border-gray-200 rounded-lg hover:shadow-md transition-shadow ${bgColor} dark:border-gray-800 dark:hover:brightness-125 dark:hover:border-gray-400 transition-colors `}
>
<div className="p-6 flex flex-col justify-between h-full">
<h3 className="text-base font-medium text-gray-800 dark:dark-theme-text-color mb-3">{cookbook.title}</h3>
<div>
{cookbook.author?.name && (
<div className="flex items-center mb-4">
<img
src={cookbook.author.image}
alt={cookbook.author.name}
className="w-5 h-5 rounded-full mr-2"
/>
<span className="text-xs text-gray-600 dark:dark-theme-text-color">{cookbook.author.name}</span>
</div>
)}
<div className="flex flex-wrap gap-1.5">
{cookbook.tags.products.map(tag => (
<span
key={tag}
className="px-1.5 py-0.5 text-[10px] text-gray-600 bg-white/50 rounded-full dark:dark-theme-text-color"
>
{generateDisplayTag(tag)}
</span>
))}
</div>
</div>
</div>
</a>
);
72 changes: 72 additions & 0 deletions fern/components/cookbooks/Cookbooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useState } from 'react';
import { Sidebar } from './Sidebar';
import { SearchBar } from './SearchBar';
import { CookbookCard } from './CookbookCard';
import { TagCategories } from './types';
import { cookbooks } from './data/cookbooks';

const CARD_COLORS = ['bg-[#EEF0EF] dark:bg-[#636363]', 'bg-[#FDF2F0] dark:bg-[#3b3b3b]', 'bg-[#F8F1F9] dark:bg-[#222222]', 'bg-[#F0F2FB] dark:bg-[#141414]'];

export const Cookbooks: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('');
const [selectedTags, setSelectedTags] = useState<TagCategories>({
useCases: [],
products: [],
thirdParty: []
});

const toggleTag = (category: keyof TagCategories, tag: { original: string; display: string }) => {
setSelectedTags(prev => ({
...prev,
[category]: prev[category].some(t => t.original === tag.original)
? prev[category].filter(t => t.original !== tag.original)
: [...prev[category], tag]
}));
};

const filteredCookbooks = cookbooks.filter(cookbook => {
const matchesQuery = searchQuery === '' ||
cookbook.title.toLowerCase().includes(searchQuery.toLowerCase());

const matchesUseCases = selectedTags.useCases.length === 0 ||
selectedTags.useCases.every(tag =>
cookbook.tags.useCases.some(c => c === tag.original)
);

const matchesEndpoints = selectedTags.products.length === 0 ||
selectedTags.products.every(tag =>
cookbook.tags.products.some(p => p === tag.original)
);

const matchesTechStack = selectedTags.thirdParty.length === 0 ||
selectedTags.thirdParty.every(tag =>
cookbook.tags.thirdParty.some(t => t === tag.original)
);

return matchesQuery && matchesUseCases && matchesEndpoints && matchesTechStack;
});

return (
<div className="w-full">
<div className="flex flex-col md:flex-row gap-8">
<Sidebar selectedTags={selectedTags} toggleTag={toggleTag} />

<div className="flex-1">
<div className="mb-6">
<SearchBar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredCookbooks.map((cookbook, index) => (
<CookbookCard
key={cookbook.href}
cookbook={cookbook}
bgColor={CARD_COLORS[index % CARD_COLORS.length]}
/>
))}
</div>
</div>
</div>
</div>
);
};
15 changes: 15 additions & 0 deletions fern/components/cookbooks/CookbooksPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Cookbooks } from './Cookbooks';

export const CookbookPage: React.FC = () => (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 dark:dark-theme-text-color">
<div className="max-w-3xl">
<h1 className="text-3xl font-bold mb-4">Cookbooks</h1>
<p className="text-gray-600 mb-8 dark:dark-theme-text-color">
Explore what you can build on Cohere's generative AI platform with our new interface.
Search and filter cookbooks by title, description, or tags to find exactly what you need.
</p>
</div>
<Cookbooks />
</div>
);
23 changes: 23 additions & 0 deletions fern/components/cookbooks/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

interface SearchBarProps {
searchQuery: string;
setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
}

export const SearchBar: React.FC<SearchBarProps> = ({ searchQuery, setSearchQuery }) => (
<div className="relative w-full dark:bg-[var(--header-background)]">
<input
type="text"
placeholder="Search"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-4 py-2 pl-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-black dark:bg-transparent dark:text-white"
/>
<div className="absolute left-3 top-1/2 transform -translate-y-1/2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="text-gray-400" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
</div>
</div>
);
40 changes: 40 additions & 0 deletions fern/components/cookbooks/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { ALL_TAGS } from './data/tags';
import { TagCategories } from './types';
import { TagButton } from './TagButton';

interface SidebarProps {
selectedTags: TagCategories;
toggleTag: (category: keyof TagCategories, tag: { original: string; display: string }) => void;
}

function formatCategoryLabel(key: string): string {
return key
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/^./, str => str.toUpperCase());
}


export const Sidebar: React.FC<SidebarProps> = ({ selectedTags, toggleTag }) => (
<div className="w-full md:w-64 flex-shrink-0">
<div className="sticky top-4">
{(Object.keys(ALL_TAGS) as (keyof TagCategories)[]).map(category => (
<div key={category} className="mb-6">
<h3 className="text-sm font-medium mb-4">
{formatCategoryLabel(category)}
</h3>
<div className="flex flex-wrap gap-2">
{ALL_TAGS[category].map(tag => (
<TagButton
key={tag.original}
tag={tag.display}
isSelected={selectedTags[category].some(t => t.original === tag.original)}
onClick={() => toggleTag(category, tag)}
/>
))}
</div>
</div>
))}
</div>
</div>
);
20 changes: 20 additions & 0 deletions fern/components/cookbooks/TagButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

interface TagButtonProps {
tag: string;
isSelected: boolean;
onClick: () => void;
}

const tagButtonClass = "px-2 py-1 text-[12px] rounded-full transition-colors cursor-pointer font-normal";
const selectedTagClass = "bg-[#333293] text-white hover:bg-[#333293]/90";
const unselectedTagClass = "bg-transparent text-[#666666] shadow-[0_0_0_1px_#E5E5E5] outline outline-1 outline-[#E5E5E5] hover:bg-[#d4d4f0] hover:text-[#4d4d4d] dark:dark-theme-text-color";

export const TagButton: React.FC<TagButtonProps> = ({ tag, isSelected, onClick }) => (
<button
onClick={onClick}
className={`${tagButtonClass} ${isSelected ? selectedTagClass : unselectedTagClass}`}
>
{tag}
</button>
);
Loading