Skip to content

Commit

Permalink
feat: add dropdown for topics in Explore (#877)
Browse files Browse the repository at this point in the history
Fixes #533
  • Loading branch information
Deadreyo committed Feb 17, 2023
1 parent dd085d2 commit 9579902
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 12 deletions.
4 changes: 2 additions & 2 deletions components/atoms/LanguagePill/LanguagePill.tsx
Expand Up @@ -7,7 +7,7 @@ import PythonIcon from "/img/icons/🐍.svg";
import AIIcon from "/img/icons/🤖.svg";
import MLIcon from "/img/icons/🧠.svg";
import RustIcon from "/img/icons/🦀.svg";
import checkCamelCaseNaming from "lib/utils/check-camelcase-naming";
import topicNameFormatting from "lib/utils/topic-name-formatting";

interface LanguagePillProps {
topic: "react" | "javascript" | "python" | "ML" | "AI" | "rust" | string;
Expand Down Expand Up @@ -36,7 +36,7 @@ const LanguagePill = ({ topic, classNames, onClick }: LanguagePillProps) => {
}`}
>
<Image src={renderTopicIcon(topic)} alt={topic} />
<span className="font-normal capitalize">{checkCamelCaseNaming(topic)}</span>
<span className="font-normal capitalize">{topicNameFormatting(topic)}</span>
</div>
);
};
Expand Down
4 changes: 2 additions & 2 deletions components/atoms/Radio/radio.tsx
Expand Up @@ -8,7 +8,7 @@ interface RadioProps {
checked?: boolean;
onClick?: () => void;
value?: string;
withLabel: string;
withLabel?: string;
css?: string;
}

Expand All @@ -32,7 +32,7 @@ const Radio = ({ css, withLabel, id, children, value, checked = false, onClick }
onChange={onClick}
id={id}
/>
<label className="flex -mt-5 cursor-pointer justify-between item-center" htmlFor={id}>
<label className="flex -mt-5 cursor-pointer item-center" htmlFor={id}>
{checked ? (
<BsFillCheckCircleFill className="text-xl text-light-orange-9" />
) : (
Expand Down
36 changes: 36 additions & 0 deletions components/atoms/Selector/selector.tsx
@@ -0,0 +1,36 @@
import Radio from "components/atoms/Radio/radio";
import humanizeNumber from "lib/utils/humanizeNumber";

interface SelectorProps {
filterOptions: string[];
handleFilterClick: (filter: string) => void;
selected?: string;
}

const Selector = ({
filterOptions,
handleFilterClick,
selected
}: SelectorProps) => {
return (
<div className="mt-2 absolute transform md:translate-x-0 space-y-1 mt-1 shadow-superlative z-10 w-72 bg-white rounded-lg px-1.5 py-2">
{filterOptions.length > 0 &&
filterOptions.map((option, index) => {
return (
<Radio
key={index}
onClick={() => {
handleFilterClick(option);
}}
css="!w-full"
checked={selected === option ? true : false}
>
{option}
</Radio>
);
})}
</div>
);
};

export default Selector;
86 changes: 86 additions & 0 deletions components/molecules/FilterCardSelect/filter-card-select.tsx
@@ -0,0 +1,86 @@
import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";
import Text from "../../atoms/Typography/text";
import hashIcon from "../../../img/icons/hash.svg";
import orgIcon from "../../../img/icons/org.svg";
import personIcon from "../../../img/icons/person.svg";
import repoIcon from "../../../img/icons/repo.svg";
import Selector from "../../atoms/Selector/selector";

interface FilterCardSelectProps {
selected: string;
icon?: "topic" | "repo" | "org" | "contributor";
options: string[];
handleFilterClick: (filter: string) => void;
}

const icons = {
topic: {
src: hashIcon.src,
alt: "Topic"
},
org: {
src: orgIcon.src,
alt: "Organization"
},
contributor: {
src: personIcon.src,
alt: "Contributor"
},
repo: {
src: repoIcon.src,
alt: "Repository"
}
};

const FilterCardSelect: React.FC<FilterCardSelectProps> = ({ selected: filterName, icon = "topic", options, handleFilterClick }) => {

const ref = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const toggleFilter = () => {
setIsOpen(!isOpen);
};

useEffect(() => {
const checkIfClickedOutside = (e: MouseEvent) => {
// If the menu is open and the clicked target is not within the menu,
// then close the menu
if (isOpen && ref.current && !ref.current.contains(e.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", checkIfClickedOutside);

return () => {
// Cleanup the event listener
document.removeEventListener("mousedown", checkIfClickedOutside);
};
}, [isOpen]);

return (
<>
<div
onClick={toggleFilter}
ref={ref}
className={"inline-block py-1 border border-slate-300 outline-none hover:bg-slate-50 focus:ring-2 bg-slate-100 focus:ring-slate-300 rounded-lg cursor-pointer"}>
<button className="flex items-center gap-1 mx-2">
<Image
width={14} height={14}
alt={icon === "topic" ? icons.topic.alt : icon === "org" ? icons.org.alt : icon === "contributor" ? icons.contributor.alt : icon === "repo" ? icons.repo.alt : "Icon"}
src={icon === "topic" ? icons.topic.src : icon === "org" ? icons.org.src : icon === "contributor" ? icons.contributor.src : icon === "repo" ? icons.repo.src : icons.topic.src} />
<Text className="!text-sm font-semibold tracking-tight !text-slate-900">
{filterName}
</Text>
</button>
{ isOpen && <Selector
filterOptions={options}
handleFilterClick={handleFilterClick}
selected={filterName}
/>
}
</div>
</>
);
};

export default FilterCardSelect;
14 changes: 11 additions & 3 deletions components/molecules/FilterHeader/filter-header.tsx
Expand Up @@ -12,11 +12,14 @@ import useFilterOptions from "lib/hooks/useFilterOptions";
import { captureAnayltics } from "lib/utils/analytics";
import useFilterPrefetch from "lib/hooks/useFilterPrefetch";
import uppercaseFirst from "lib/utils/uppercase-first";
import checkCamelCaseNaming from "lib/utils/check-camelcase-naming";
import topicNameFormatting from "lib/utils/topic-name-formatting";
import FilterCardSelect from "components/molecules/FilterCardSelect/filter-card-select";
import useTopicOptions from "lib/utils/getTopicOptions";

const HeaderFilter = () => {
const router = useRouter();
const filterOptions = useFilterOptions();
const topicOptions = useTopicOptions();

const { filterValues } = useFilterPrefetch();
const { filterName, toolName, selectedFilter } = router.query;
Expand All @@ -29,21 +32,26 @@ const HeaderFilter = () => {
const cancelFilterRouting = () => {
router.push(`/${filterName}/${toolName}`);
};

const topicRouting = (topic: string) => {
router.push(`/${topic}/${toolName}${selectedFilter ? `/filter/${selectedFilter}` : ""}`);
};

return (
<>
<div className="header-image mr-2 p-2 min-w-[130px]">
<ContextThumbnail size={120} ContextThumbnailURL={isHacktoberfest ? Thumbnail.src : ""}></ContextThumbnail>
</div>
<div className="header-info md:truncate flex flex-col grow justify-center p-2">
<Title level={1} className="!text-3xl font-semibold tracking-tight text-slate-900">
{isHacktoberfest ? "Hacktoberfest 2022" : checkCamelCaseNaming(filterName as string)}
{isHacktoberfest ? "Hacktoberfest 2022" : topicNameFormatting(filterName as string)}
</Title>
<Text className="mt-1 !text-base text-slate-500">
Insights on GitHub repositories{" "}
{isHacktoberfest ? "opted into the largest open source hackathon." : `using the ${filterName} topic.`}
</Text>
<div className="flex mt-4 items-center gap-2">
<FilterCard filterName={filterName as string} isRemovable={false} icon="topic" />
<FilterCardSelect selected={filterName as string} options={topicOptions} icon="topic" handleFilterClick={topicRouting} />
<SuperativeSelector
filterOptions={filterOptions}
filterValues={filterValues}
Expand Down
7 changes: 7 additions & 0 deletions lib/utils/getTopicOptions.ts
@@ -0,0 +1,7 @@
const getTopicOptions = () => {
const topics = ["javascript", "rust", "python", "ml", "ai", "react"];

return topics;
};

export default getTopicOptions;
@@ -1,11 +1,13 @@
import uppercaseFirst from "./uppercase-first";

/** Checks the input and if it should be camelcased, it will camelcase it. Uppercases the output. */
const checkCamelCaseNaming = (name: string) => {
const topicNameFormatting = (name: string) => {
if (!name) return "";

if(name.toLowerCase() == "javascript") return "JavaScript";
if(name.toLowerCase() == "ai") return "AI";
if(name.toLowerCase() == "ml") return "Machine Learning";
else return uppercaseFirst(name);
};

export default checkCamelCaseNaming;
export default topicNameFormatting;
20 changes: 20 additions & 0 deletions stories/atoms/selector.stories.tsx
@@ -0,0 +1,20 @@
import React from "react";
import { ComponentStory } from "@storybook/react";
import Selector from "components/atoms/Selector/selector";

const storyConfig = {
title: "Design System/Atoms/Selector",
component: "Select"
};

export default storyConfig;

//Select Template
const SelectTemplate: ComponentStory<typeof Selector> = (args) => <Selector {...args} />;

export const Default = SelectTemplate.bind({});

Default.args = {
filterOptions: ["option1", "option2", "option3"],
selected: "option1"
};
21 changes: 21 additions & 0 deletions stories/molecules/filter-card-select.stories.tsx
@@ -0,0 +1,21 @@
import React from "react";
import { ComponentStory } from "@storybook/react";
import Selector from "components/atoms/Selector/selector";
import FilterCardSelect from "components/molecules/FilterCardSelect/filter-card-select";

const storyConfig = {
title: "Design System/Molecules/FilterCardSelect",
component: "FilterCardSelect"
};

export default storyConfig;

//Select Template
const SelectTemplate: ComponentStory<typeof FilterCardSelect> = (args) => <FilterCardSelect {...args} />;

export const Default = SelectTemplate.bind({});

Default.args = {
options: ["option1", "option2", "option3"],
selected: "option1"
};
@@ -1,15 +1,15 @@
import checkCamelCaseNaming from "lib/utils/check-camelcase-naming";
import topicNameFormatting from "lib/utils/topic-name-formatting";

describe("[lib] checkCamelCaseNaming()", () => {
it("should make javascript camelCase and uppercase first letter", () => {
const testString = "javascript";
const result = checkCamelCaseNaming(testString);
const result = topicNameFormatting(testString);
expect(result).toBe("JavaScript");
});

it("should uppercase python without camelCase", () => {
const testString = "python";
const result = checkCamelCaseNaming(testString);
const result = topicNameFormatting(testString);
expect(result).toBe("Python");
});
});

0 comments on commit 9579902

Please sign in to comment.