Skip to content

Commit

Permalink
Merge pull request #318 from EscolaLMS/feature/categories-dropdown
Browse files Browse the repository at this point in the history
Feature/categories dropdown
  • Loading branch information
myslaf committed Feb 20, 2024
2 parents 6289795 + 3c4b4d4 commit 5395fd6
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@types/react": "^17||^18",
"@types/react-pdf": "^5.7.2",
"@types/react-slick": "^0.23.8",
"@types/react-transition-group": "^4.4.5",
"@types/react-transition-group": "^4.4.10",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
Expand Down
190 changes: 190 additions & 0 deletions src/components/molecules/DropdownCategories/DropdownCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import React, { FC, useRef, useState, useCallback, cloneElement } from "react";
import { CSSTransition } from "react-transition-group";
import styled, { withTheme } from "styled-components";
import { useOnClickOutside } from "../../../hooks/useOnClickOutside";
import { Checkbox } from "components/atoms/Option/Checkbox";
import Text from "components/atoms/Typography/Text";
import { Category } from "@escolalms/sdk/lib/types/api";

interface Props {
categories: Category[];
checkedCategories: Category[];
isInitiallyOpen?: boolean;
onChange: (category: Category) => void;
onClick?: () => void;
onClear?: () => void;
child?: React.ReactElement<{ onClick?: () => void; $isMenuOpen?: boolean }>;
}

const Wrapper = styled.div`
position: relative;
width: max-content;
cursor: pointer;
`;

const DropdownMenuWrapper = styled.ul`
top: 10px;
position: absolute;
left: 0;
z-index: 1000;
display: flex;
flex-direction: column;
width: max-content;
box-shadow: 0px 10px 15px #00000019;
min-width: 175px;
padding: 0px;
padding-bottom: 19px;
border: 1px solid ${({ theme }) => theme.gray3};
background: ${({ theme }) => theme.white};
border-radius: ${({ theme }) => theme.buttonRadius}px;
min-width: 300px;
&.fade-enter {
opacity: 0;
}
&.fade-enter-active {
opacity: 1;
transition: 0.3s;
}
&.fade-enter-done {
opacity: 1;
}
&.fade-exit-active {
opacity: 0;
transition: 0.3s;
}
`;

const MenuItem = styled.li`
display: flex;
padding: 0px 14px;
flex-direction: column;
&:not(&:last-child) {
margin-bottom: 20px;
}
> div {
display: flex;
flex-direction: column;
label {
font-size: 16px;
font-weight: 700;
}
}
.subcategories {
margin-top: 15px;
margin-left: 20px;
}
`;

const ClearItem = styled.li`
display: flex;
justify-content: space-between;
margin-bottom: 20px;
border-bottom: 1px solid #eaeaea;
padding: 13px 14px;
p {
margin: 0;
}
button {
all: unset;
opacity: 0.55;
}
`;

const DropdownCategoriesRecursive: FC<
Pick<Props, "categories" | "checkedCategories" | "onChange">
> = ({ categories, checkedCategories, onChange }: Props) => {
const handleCategoryClick = (category: Category) => {
onChange(category);
};

return (
<>
{categories.map((category: Category) => (
<MenuItem key={category.id}>
<Checkbox
name={category.name}
label={category.name}
checked={
checkedCategories.find((item) => item.id === category.id)
? true
: false
}
onChange={() => handleCategoryClick(category)}
/>

{category.subcategories && category.subcategories.length > 0 && (
<div className="subcategories">
<DropdownCategoriesRecursive
checkedCategories={checkedCategories}
categories={category.subcategories}
onChange={onChange}
/>
</div>
)}
</MenuItem>
))}
</>
);
};

export const DropdownCategories: React.FC<Props> = ({
child,
categories,
checkedCategories,
isInitiallyOpen,
onClick,
onChange,
onClear,
}) => {
const dropdownMenuRef = useRef<HTMLUListElement | null>(null);
const [isOpen, setIsOpen] = useState(isInitiallyOpen);
const closeMenu = () => setIsOpen(false);
useOnClickOutside(dropdownMenuRef, () => closeMenu());

const handleCategoryClick = useCallback(
(category: Category) => {
onChange?.(category);
},
[onChange]
);

const handleClear = () => {
onClear?.();
closeMenu();
};

return (
<Wrapper onClick={onClick}>
{cloneElement(child as React.ReactElement, {
onClick: () => setIsOpen((prev) => !prev),
$isMenuOpen: isOpen,
})}
<CSSTransition
in={isOpen}
timeout={300}
nodeRef={dropdownMenuRef}
classNames="fade"
unmountOnExit
>
<DropdownMenuWrapper ref={dropdownMenuRef}>
<ClearItem>
<Text size="16">Wybierz</Text>
<button onClick={handleClear}>
<Text size="13">Wyczyść</Text>
</button>
</ClearItem>
<DropdownCategoriesRecursive
checkedCategories={checkedCategories}
categories={categories}
onChange={handleCategoryClick}
/>
</DropdownMenuWrapper>
</CSSTransition>
</Wrapper>
);
};

export default withTheme(styled(DropdownCategories)``);
11 changes: 5 additions & 6 deletions src/components/molecules/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface Props {
isInitiallyOpen?: boolean;
onClick?: () => void;
onChange?: (listItem: DropdownMenuItem) => void;
top?: number;
}

const Wrapper = styled.div`
Expand All @@ -26,8 +27,8 @@ const Wrapper = styled.div`
cursor: pointer;
`;

const DropdownMenuWrapper = styled.ul`
top: 30px;
const DropdownMenuWrapper = styled.ul<{ $top?: number }>`
top: ${({ $top }) => ($top ? $top : 30)}px;
position: absolute;
left: 0;
z-index: 1000;
Expand Down Expand Up @@ -86,16 +87,15 @@ const DropdownMenu: FC<Props> = ({
isInitiallyOpen,
onClick,
onChange,
top,
}) => {
// const [currID, setCurrID] = useState(0);
const dropdownMenuRef = useRef<HTMLUListElement | null>(null);
const [isOpen, setIsOpen] = useState(isInitiallyOpen);
const closeMenu = () => setIsOpen(false);
useOnClickOutside(dropdownMenuRef, () => closeMenu());

const onListItemClick = useCallback(
(ind: number) => {
// setCurrID(ind);
onChange?.(menuItems[ind]);
closeMenu();
},
Expand All @@ -107,7 +107,6 @@ const DropdownMenu: FC<Props> = ({
{cloneElement(child as React.ReactElement, {
onClick: () => setIsOpen((prev) => !prev),
$isMenuOpen: isOpen,
// children: isOpen ? "currID" : "Menu",
})}
<CSSTransition
in={isOpen}
Expand All @@ -116,7 +115,7 @@ const DropdownMenu: FC<Props> = ({
classNames="fade"
unmountOnExit
>
<DropdownMenuWrapper ref={dropdownMenuRef}>
<DropdownMenuWrapper ref={dropdownMenuRef} $top={top}>
{menuItems.map(({ id, content }, index) => (
<MenuItem
key={id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { ProgressBarProps } from "../../atoms/ProgressBar/ProgressBar";
import { RatioBox } from "../../atoms/RatioBox/RatioBox";
import { getStylesBasedOnTheme } from "../../../utils/utils";
import { ExtendableStyledComponent } from "types/component";
import { Title } from "components/atoms/Typography/Title";
import Text from "components/atoms/Typography/Text";
import { Link } from "react-router-dom";

type ImageObject = {
path?: string;
Expand Down Expand Up @@ -181,7 +179,6 @@ const StyledCategory = styled.span`

export const NewCourseCard: React.FC<CourseCardProps> = (props) => {
const {
id,
mobile,
title,
image,
Expand Down Expand Up @@ -264,13 +261,7 @@ export const NewCourseCard: React.FC<CourseCardProps> = (props) => {
</Text>
)
)}
<div className="course-title">
<Link to={`/courses/${id}`}>
<Title level={3} as="h3" className="title">
{title}
</Title>
</Link>
</div>
<div className="course-title">{title}</div>
<div className="course-price">
<Text size="16" bold>
{price}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import styled from "styled-components";
import "react-loading-skeleton/dist/skeleton.css";
import { useId } from "react";
import { Col, ScreenClass } from "react-grid-system";
import { isMobile, isTablet } from "react-device-detect";

const CardSkeleton = styled.div<{ $isMobile: boolean; $isTablet: boolean }>`
const CardSkeleton = styled.div`
max-width: 278px;
min-height: 414px;
`;
Expand All @@ -28,7 +27,7 @@ export const CourseCardSkeleton: React.FC<Props> = ({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<Col key={`card-skeleton-${useId()}`} {...colProps}>
<CardSkeleton $isMobile={isMobile} $isTablet={isTablet}>
<CardSkeleton>
<Skeleton
height="264px"
borderRadius={14}
Expand All @@ -39,11 +38,7 @@ export const CourseCardSkeleton: React.FC<Props> = ({
</CardSkeleton>
</Col>
) : (
<CardSkeleton
key={`card-skeleton-${useId()}`}
$isMobile={isMobile}
$isTablet={isTablet}
>
<CardSkeleton key={`card-skeleton-${useId()}`}>
<Skeleton
height="264px"
borderRadius={14}
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export { Tutor } from "./components/molecules/Tutor/Tutor";
export { Search } from "./components/molecules/Search/Search";
export { default as List } from "./components/molecules/List/List";
export { default as DropdownMenu } from "./components/molecules/DropdownMenu/DropdownMenu";
export { NewCourseCard } from "./components/molecules/NewCourseCard/index";
export { DropdownCategories } from "./components/molecules/DropdownCategories/DropdownCategories";
export { NewCourseCard } from "./components/molecules/NewCourseCard/NewCourseCard";

//ORGANISMS
export { default as CourseAgenda } from "./components/organisms/CourseAgenda/CourseAgenda";
Expand All @@ -72,5 +73,4 @@ export { default as ModalNote } from "./components/organisms/ModalNote";
//ADVANCED

//SKELETONS
export { CourseCardSkeleton } from "./components/skeletons/CourseCard/index";
export { BannerSkeleton } from "./components/skeletons/Banner/index";
export { CourseCardSkeleton } from "./components/skeletons/CourseCard/CourseCard";

0 comments on commit 5395fd6

Please sign in to comment.