Skip to content

Commit

Permalink
feat: review item component
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlhw committed Jan 21, 2024
1 parent a221863 commit 98dcb0f
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 0 deletions.
41 changes: 41 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 Label, 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 Label[],
};

export default function Components() {
const [isMounted, setIsMounted] = useState(false);
const { theme, setTheme } = useTheme();
Expand Down Expand Up @@ -514,6 +549,12 @@ export default function Components() {
</Tag>
</div>
</div>
<div className="flex flex-col gap-4">
<ReviewItem review={review} />
<ReviewItem review={review} isLocked />
<ReviewItem review={review} variant="subpage" />
<ReviewItem review={review} variant="subpage" isLocked />
</div>
</div>
);
}
71 changes: 71 additions & 0 deletions src/common/components/ReviewItem/ReviewItem.theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { type VariantProps, tv } from "tailwind-variants";

export type ReviewItemVariants = VariantProps<typeof reviewItemTheme>;

export const reviewItemTheme = tv(
{
slots: {
wrapper: [
"flex",
"flex-col",
"p-2",
"items-start",
"w-[45rem]",
"gap-[0.375rem]",
],
headingContainer: [
"flex",
"self-stretch",
"justify-between",
"items-center",
"rounded-none",
"w-full",
"gap-10",
],
schoolContainer: ["flex", "items-center", "gap-2"],
schoolIcon: [],
schoolCourseCode: [
"flex",
"overflow-hidden",
"text-sm",
"text-ellipsis",
"text-text-em-mid",
],
metadataContainer: ["flex", "gap-4", "items-center", "justify-end"],
profileContainer: ["flex", "items-center", "gap-2"],
profileName: [
"overflow-hidden",
"text-sm",
"text-ellipsis",
"text-text-em-mid",
],
profileIcon: [],
likeIcon: [],
timedelta: [
"overflow-hidden",
"text-sm",
"text-ellipsis",
"text-text-em-low",
],
body: [
"relative",
"flex",
"h-16",
"w-full",
"self-stretch",
"overflow-hidden",
"text-sm",
"text-ellipsis",
"text-text-em-high",
],
labels: [
"text-sm",
"text-text-on-secondary",
"flex",
"gap-4",
"items-start",
],
},
},
{ responsiveVariants: true },
);
125 changes: 125 additions & 0 deletions src/common/components/ReviewItem/ReviewItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Button } from "@/common/components/Button";
import { reviewItemTheme, type ReviewItemVariants } from "./ReviewItem.theme";
import { ThumbUpFilledIcon } from "@/common/components/CustomIcon";
import { LockCtaOverlay } from "@/common/components/LockCtaOverlay";
import { Tag } from "@/common/components/Tag";
import { getHumanReadableTimestampDelta } from "@/common/functions";

// TODO: to replace with prisma generated types
export type Label = {
name: string;
typeof: "professor" | "course";
};

export type Review = {
body: string;
courseCode: string;
username: string;
likeCount: number;
labels: Label[];
createdAt: number;
};

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

export const ReviewItem = ({
review,
isLocked,
variant = "home",
}: ReviewItemProps) => {
const {
wrapper,
headingContainer,
schoolContainer,
schoolIcon,
schoolCourseCode,
metadataContainer,
profileContainer,
profileName,
profileIcon,
likeIcon,
timedelta,
body,
labels,
} = reviewItemTheme({ variant });

const HeaderCourse = () => (
<div className={schoolContainer()}>
<div className={schoolIcon()}>
<div className="h-4 w-4 rounded-full bg-red-800"></div>
</div>
<div className={schoolCourseCode()}>{review.courseCode}</div>
</div>
);

const HeaderReviewer = () => (
<div className={profileContainer()}>
<div className={profileIcon()}>
<div className="h-4 w-4 rounded-full bg-cyan-800"></div>
</div>
<div className={profileName()}>{review.username}</div>
</div>
);

const ReviewContent = ({ isDetailed }: { isDetailed: boolean }) => {
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" as="button">
Show more
</Button>
)}
</div>
);
};

return (
<div className={wrapper()}>
<div className={headingContainer()}>
{variant === "home" ? <HeaderCourse /> : <HeaderReviewer />}
<div className={metadataContainer()}>
{variant === "home" ? <HeaderReviewer /> : <HeaderCourse />}
<Tag
variant="secondary"
contentRight={<ThumbUpFilledIcon className={likeIcon()} />}
>
{review.likeCount}
</Tag>
<div className={timedelta()}>
{getHumanReadableTimestampDelta(review.createdAt)}
</div>
</div>
</div>
{isLocked ? (
<div className={body()}>
<LockCtaOverlay size="sm" ctaType="review" variant="border" />
</div>
) : (
<ReviewContent isDetailed={variant === "subpage"} />
)}
</div>
);
};
2 changes: 2 additions & 0 deletions src/common/components/ReviewItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ReviewItem";
export * from "./ReviewItem.theme";

0 comments on commit 98dcb0f

Please sign in to comment.