Skip to content

Commit

Permalink
feat(ui): review item component (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlhw authored Feb 3, 2024
1 parent 9d29362 commit 0e57563
Show file tree
Hide file tree
Showing 21 changed files with 400 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/app/(school)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { cn } from "@/common/functions/cn";
import { cn } from "@/common/functions";
import { type PropsWithChildren } from "react";

export default function SchoolLayout({ children }: PropsWithChildren) {
return (
<div className="">
<header
className={cn(
"z-header sticky top-0",
"sticky top-0 z-header",
"h-16 w-full border-b border-border-default bg-bg-base",
"hidden items-center justify-between sm:flex",
"px-6",
Expand Down
52 changes: 52 additions & 0 deletions src/app/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Combobox } from "@/modules/reviews/Combobox";
import { exampleListCountries } from "./exampleCountryList";
import { Modal } from "@/common/components/Modal";
import Heading from "@/common/components/Heading";
import { type ReviewLabel, ReviewItem } from "@/common/components/ReviewItem";
import { Tag } from "@/common/components/Tag";

const exampleListObj = exampleListCountries.map((el) => ({
Expand Down Expand Up @@ -83,6 +84,40 @@ const CommandContent = () => (
</>
);

const review = {
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor nunc a velit congue, et faucibus sapien iaculis. Quisque id felis non sapien egestas ultricies vulputate posuere quam. Vestibulum scelerisque arcu leo, sit amet interdum enim suscipit ut. Sed dolor turpis, tincidunt sed elementum at, posuere ac justo. Curabitur sem turpis, porttitor at ante sed, laoreet condimentum magna. Suspendisse ex orci, laoreet in cursus nec, rhoncus quis eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam lacinia varius quam, ut blandit quam suscipit nec. Morbi facilisis mauris erat, quis porttitor purus consequat id. Maecenas.",
courseCode: "COR-MGMT1302",
username: "Anonymous",
likeCount: 10,
createdAt: 1705745162,
labels: [
{
name: "Engaging",
typeof: "professor",
},
{
name: "Fair Grading",
typeof: "professor",
},
{
name: "Effective Teaching",
typeof: "professor",
},
{
name: "Interesting",
typeof: "course",
},
{
name: "Practical",
typeof: "course",
},
{
name: "Gained New Skills",
typeof: "course",
},
] as ReviewLabel[],
};

export default function Components() {
const [isMounted, setIsMounted] = useState(false);
const { theme, setTheme } = useTheme();
Expand Down Expand Up @@ -480,6 +515,23 @@ export default function Components() {
</Tag>
</div>
</div>
<div className="flex flex-col gap-4">
<Heading className="mt-6 text-2xl" as="h6">
Home Page
</Heading>
<ReviewItem review={review} />
<ReviewItem review={review} isLocked />
<Heading className="mt-6 text-2xl" as="h6">
Professor Page
</Heading>
<ReviewItem review={review} variant="professor" />
<ReviewItem review={review} variant="professor" isLocked />
<Heading className="mt-6 text-2xl" as="h6">
Course Page
</Heading>
<ReviewItem review={review} variant="course" />
<ReviewItem review={review} variant="course" isLocked />
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions src/common/components/Button/Button.theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ export const buttonTheme = tv(
"[&>*:not(.loading)]:invisible",
],
},
rounded: {
true: [
"inline-flex",
"min-w-14",
"py-[0.125rem]",
"px-3",
"gap-2",
"rounded-[6.1875rem]",
"after:rounded-[6.1875rem]",
"border-solid",
"border-border-default",
],
},
},
compoundVariants: [
{
Expand Down
2 changes: 2 additions & 0 deletions src/common/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonOrLinkProps>(
iconLeft,
iconRight,
isResponsive = false,
rounded,
fullWidth,
disabled,
loading,
Expand Down Expand Up @@ -104,6 +105,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonOrLinkProps>(
iconOnly,
size,
as,
rounded,
fullWidth,
disabled,
loading,
Expand Down
12 changes: 12 additions & 0 deletions src/common/components/CustomIcon/ThumbUpFilledIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CustomIcon, type CustomIconProps } from "./CustomIcon";

export const ThumbUpFilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon fill="none" {...props}>
<path
d="M15 8H17.405C17.991 7.99991 18.5698 8.12857 19.1006 8.37688C19.6314 8.62518 20.1011 8.98708 20.4766 9.43696C20.852 9.88684 21.1241 10.4137 21.2735 10.9803C21.4228 11.547 21.4459 12.1395 21.341 12.716L20.431 17.716C20.2634 18.6373 19.7778 19.4706 19.059 20.0707C18.3401 20.6708 17.4334 20.9997 16.497 21H8V9L9.821 3.212C10.117 2.522 10.881 1.896 11.845 2.082C13.374 2.375 15 3.566 15 5.5V8ZM6 9C5.20435 9 4.44129 9.31607 3.87868 9.87868C3.31607 10.4413 3 11.2043 3 12V18C3 18.7956 3.31607 19.5587 3.87868 20.1213C4.44129 20.6839 5.20435 21 6 21V9Z"
fill="currentColor"
/>
</CustomIcon>
);
};
1 change: 1 addition & 0 deletions src/common/components/CustomIcon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./LockIcon";
export * from "./MinusIcon";
export * from "./SearchIcon";
export * from "./StarLineAltIcon";
export * from "./ThumbUpFilledIcon";
export * from "./WarningCircleIcon";
export * from "./XCloseIcon";
export * from "./SchoolIcon";
38 changes: 27 additions & 11 deletions src/common/components/LockCtaOverlay/LockCtaOverlay.theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,42 @@ export const lockCtaOverlayTheme = tv(
],
wrapper: [
"inline-flex",
"absolute",
"left-0",
"top-0",
"items-center",
"justify-center",
"h-full",
"w-full",
"gap-1",
"text-text-em-low",
"z-10",
],
icon: ["h-6", "w-6"],
ctaTextContainer: [
"flex",
"items-center",
"gap-1",
"text-lg",
"font-medium",
],
icon: [],
ctaTextContainer: ["flex", "items-center", "gap-1", "font-medium"],
},
variants: {
size: {
md: {},
sm: {},
md: {
wrapper: ["gap-2"],
icon: ["h-6", "w-6"],
ctaTextContainer: ["text-lg"],
},
sm: {
wrapper: ["gap-[0.375rem]"],
icon: ["h-4", "w-4"],
ctaTextContainer: ["text-sm"],
},
},
variant: {
border: {
overlay: ["hidden"],
wrapper: [
"rounded-lg",
"border-2",
"border-solid",
"border-border-default",
],
},
},
},
},
Expand Down
32 changes: 26 additions & 6 deletions src/common/components/LockCtaOverlay/LockCtaOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import { Button } from "@/common/components/Button";
import { LockIcon } from "@/common/components/CustomIcon/LockIcon";
import { lockCtaOverlayTheme } from "./LockCtaOverlay.theme";
import {
lockCtaOverlayTheme,
type LockCtaOverlayVariants,
} from "./LockCtaOverlay.theme";

export const LockCtaOverlay = () => {
const { wrapper, overlay, ctaTextContainer, icon } = lockCtaOverlayTheme();
const ctaTextMap = {
rating: "to see rating",
review: "to see review",
} as const;

export type LockCtaOverlayProps = LockCtaOverlayVariants & {
ctaType?: keyof typeof ctaTextMap;
};

export const LockCtaOverlay = ({
variant,
size = "md",
ctaType = "rating",
}: LockCtaOverlayProps) => {
const { wrapper, overlay, ctaTextContainer, icon } = lockCtaOverlayTheme({
variant,
size,
});
return (
<div className={overlay()}>
<>
<div className={overlay()}></div>
<div className={wrapper()}>
<LockIcon className={icon()} />
<div className={ctaTextContainer()}>
<Button variant="link" as="a" href="/account/auth/login">
Login
</Button>
<span>to see rating</span>
<span>{ctaTextMap[ctaType]}</span>
</div>
</div>
</div>
</>
);
};
6 changes: 3 additions & 3 deletions src/common/components/MobileHeader/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Button } from "@/common/components/Button";
import { AfterclassIcon } from "@/common/components/CustomIcon";
import { Sidebar } from "@/common/components/Sidebar";
import { cn } from "@/common/functions/cn";
import { cn } from "@/common/functions";
import { Icon } from "@iconify-icon/react";
import Link from "next/link";
import { useState, type ComponentPropsWithoutRef } from "react";
Expand Down Expand Up @@ -56,7 +56,7 @@ export const MobileHeader = ({ ...props }: MobileHeaderProps) => {
{/* Overlay */}
<div
className={cn(
"z-header-sidebar absolute inset-x-0 bottom-0 top-14 bg-bg-base/90",
"absolute inset-x-0 bottom-0 top-14 z-header-sidebar bg-bg-base/90",
"opacity-0 transition-opacity duration-500 ease-in-out",
isSidebarOpen && "opacity-100",
!isSidebarOpen && "pointer-events-none",
Expand All @@ -67,7 +67,7 @@ export const MobileHeader = ({ ...props }: MobileHeaderProps) => {
{/* Sidebar */}
<div
className={cn(
"z-header-sidebar absolute bottom-0 left-0 top-14",
"absolute bottom-0 left-0 top-14 z-header-sidebar",
"-translate-x-full transition-transform duration-500 ease-in-out",
"border-r border-border-default bg-surface-base",
isSidebarOpen && "translate-x-0",
Expand Down
2 changes: 1 addition & 1 deletion src/common/components/Modal/ModalHeader/ModalHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type HeadingProps,
} from "@/common/components/Heading/Heading";
import { modalTheme } from "@/common/components/Modal/Modal.theme";
import { cn } from "@/common/functions/cn";
import { cn } from "@/common/functions";

export interface ModalHeaderProps extends ComponentPropsWithoutRef<"div"> {
className?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/common/components/RatingSection/RatingSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type RatingSectionVariants,
} from "./RatingSection.theme";
import { HeartIcon } from "@/common/components/CustomIcon";
import { LockCtaOverlay } from "@/common/components/LockCtaOverlay/LockCtaOverlay";
import { LockCtaOverlay } from "@/common/components/LockCtaOverlay";
import { StatItem, type StatItemProps } from "@/common/components/StatItem";

export type RatingSectionProps = RatingSectionVariants & {
Expand Down
17 changes: 17 additions & 0 deletions src/common/components/ReviewItem/ProfileReviewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { reviewItemTheme, type ReviewItemVariants } from "./ReviewItem.theme";

export type ProfileReviewerProps = ReviewItemVariants & {
name: string;
};

export const ProfileReviewer = ({ name }: ProfileReviewerProps) => {
const { profileContainer, profileName, profileIcon } = reviewItemTheme();
return (
<div className={profileContainer()}>
<div className={profileIcon()}>
<div className="h-4 w-4 rounded-full bg-cyan-800"></div>
</div>
<div className={profileName()}>{name}</div>
</div>
);
};
17 changes: 17 additions & 0 deletions src/common/components/ReviewItem/ProfileSchool.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { reviewItemTheme, type ReviewItemVariants } from "./ReviewItem.theme";

export type ProfileSchoolProps = ReviewItemVariants & {
courseCode: string;
};

export const ProfileSchool = ({ courseCode }: ProfileSchoolProps) => {
const { schoolContainer, schoolCourseCode, schoolIcon } = reviewItemTheme();
return (
<div className={schoolContainer()}>
<div className={schoolIcon()}>
<div className="h-4 w-4 rounded-full bg-red-800"></div>
</div>
<div className={schoolCourseCode()}>{courseCode}</div>
</div>
);
};
37 changes: 37 additions & 0 deletions src/common/components/ReviewItem/ReviewBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Button } from "@/common/components/Button";
import { reviewItemTheme, type ReviewItemVariants } from "./ReviewItem.theme";
import { Review } from "./ReviewItem";

export type ReviewBodyProps = ReviewItemVariants & {
review: Review;
isDetailed: boolean;
variant?: "home" | "subpage";
};

export const ReviewBody = ({ review, isDetailed }: ReviewBodyProps) => {
const { body, labels } = reviewItemTheme();
return (
<div className="flex flex-col gap-1">
{isDetailed && (
<div className={labels()}>
{review.labels
.filter((label) => label.typeof === "professor")
.map((label) => (
<span key={label.name}>{label.name}</span>
))}
</div>
)}
{isDetailed && (
<div className={labels()}>
{review.labels
.filter((label) => label.typeof === "course")
.map((label) => (
<span key={label.name}>{label.name}</span>
))}
</div>
)}
<div className={body()}>{review.body}</div>
{isDetailed && <Button variant="link">Show more</Button>}
</div>
);
};
Loading

0 comments on commit 0e57563

Please sign in to comment.