forked from gitcoinco/gitcoin-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PAR-606: ReviewDropdown (gitcoinco#41)
* ReviewSummary * wip * deisgn updates * clean code * questionmark * fix padding * refactor classes * add list * introduce isOpen * minor fix * fix breaking build --------- Co-authored-by: Aditya Anand M C <aditya.anandmc@gmail.com>
- Loading branch information
1 parent
6d0d989
commit ed0eb7b
Showing
19 changed files
with
582 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions
21
src/features/checker/components/ReviewDropdown/ReviewDropdown.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Meta, Stories, Controls, Story } from "@storybook/blocks"; | ||
import * as ReviewDropdownStories from './ReviewDropdown.stories'; | ||
import ReviewDropdown from './ReviewDropdown'; | ||
|
||
<Meta of={ReviewDropdownStories}/> | ||
|
||
# Review Summary | ||
|
||
The `ReviewDropdown` component is a simple UI element designed to provide a title or summary for a review section. | ||
|
||
## Example | ||
|
||
<Story name="Default" of={ReviewDropdownStories.Default} /> | ||
|
||
## Controls | ||
|
||
<Controls /> | ||
|
||
## Other Variations | ||
|
||
<Stories /> |
25 changes: 25 additions & 0 deletions
25
src/features/checker/components/ReviewDropdown/ReviewDropdown.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
|
||
import ReviewDropdown from "./ReviewDropdown"; | ||
import { mockData } from "./mockData"; | ||
|
||
const meta: Meta<typeof ReviewDropdown> = { | ||
component: ReviewDropdown, | ||
title: "Features/Checker/Components/ReviewDropdown", // Adjust the path as per your Storybook organization | ||
} satisfies Meta<typeof ReviewDropdown>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof ReviewDropdown>; | ||
|
||
export const Default: Story = { | ||
args: { evaluation: { ...mockData[0] }, index: 1 }, | ||
}; | ||
|
||
export const Rejected: Story = { | ||
args: { evaluation: { ...mockData[1] }, index: 2 }, | ||
}; | ||
|
||
export const LlmGpt3: Story = { | ||
args: { evaluation: { ...mockData[2] }, index: 3 }, | ||
}; |
195 changes: 195 additions & 0 deletions
195
src/features/checker/components/ReviewDropdown/ReviewDropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import * as React from "react"; | ||
|
||
import { tv, type VariantProps } from "tailwind-variants"; | ||
|
||
import { cn, formatLocalDate } from "@/lib/utils"; | ||
import { Accordion } from "@/primitives/Accordion"; | ||
import { Badge } from "@/primitives/Badge/Badge"; | ||
import { Icon, IconType } from "@/primitives/Icon"; | ||
|
||
import { EvaluationSummaryProps } from "./types"; | ||
|
||
const ReviewDropdownVariants = tv({ | ||
slots: { | ||
header: "flex w-full items-center justify-between gap-4 py-8 pr-2", | ||
headerLeft: "flex flex-1 items-center gap-4", | ||
headerRight: "flex items-center justify-end gap-4", | ||
content: "flex w-full flex-col gap-6 p-8", | ||
textRow: "space-x-1 text-left", | ||
status: "text-gray-600", | ||
reviewTitle: "font-sans text-xl text-black", | ||
evaluatorTitle: "absolute my-1 font-sans text-sm font-normal text-gray-900", | ||
reviewDate: "text-base font-normal text-black", | ||
}, | ||
defaultVariants: {}, | ||
}); | ||
|
||
const evaluationSummaryVariants = tv({ | ||
base: "flex items-center gap-2 self-stretch rounded-lg p-4", | ||
variants: { | ||
background: { | ||
default: "bg-gray-50", | ||
light: "bg-white", | ||
}, | ||
}, | ||
defaultVariants: { | ||
background: "default", | ||
}, | ||
}); | ||
|
||
export type ReviewDropdownVariants = VariantProps<typeof ReviewDropdownVariants>; | ||
export type EvaluationSummaryVariants = VariantProps<typeof evaluationSummaryVariants>; | ||
|
||
interface ReviewDropdownContentProps { | ||
evaluation: EvaluationSummaryProps; | ||
index?: number; | ||
isOpen?: boolean; | ||
} | ||
|
||
// Main Component | ||
const ReviewDropdown: React.FC<ReviewDropdownContentProps> = ({ | ||
evaluation, | ||
index, | ||
isOpen = true, | ||
}) => { | ||
const accordionVariant = evaluation.evaluatorType === "human" ? "light" : "blue"; | ||
return ( | ||
<Accordion | ||
border="md" | ||
padding="md" | ||
variant={accordionVariant} | ||
header={<ReviewDropdownHeader evaluation={evaluation} index={index} />} | ||
content={<ReviewDropdownContent evaluation={evaluation} />} | ||
isOpen={isOpen} | ||
/> | ||
); | ||
}; | ||
|
||
// Header Component | ||
const ReviewDropdownHeader: React.FC<ReviewDropdownContentProps> = ({ evaluation, index }) => { | ||
let reviewTitle = ""; | ||
let evaluatorTitle = ""; | ||
let evaluatorIconType; | ||
|
||
if (evaluation.evaluatorType === "human") { | ||
reviewTitle = `Review ${index ?? ""}`; | ||
evaluatorIconType = IconType.USER; | ||
evaluatorTitle = `by ${evaluation.evaluator.slice(0, 4)}...${evaluation.evaluator.slice(-4)}`; | ||
} else { | ||
reviewTitle = "AI Powered"; | ||
evaluatorIconType = IconType.SHINE; | ||
evaluatorTitle = "BETA"; | ||
} | ||
|
||
const rating = evaluation.evaluation.filter((answer) => answer.answer === "YES").length; | ||
|
||
const reviewStatusBadgeVariant = | ||
evaluation.evaluationStatus === "approved" | ||
? "success-strong" | ||
: evaluation.evaluationStatus === "rejected" | ||
? "error-strong" | ||
: "info-strong"; | ||
|
||
const { | ||
header, | ||
headerLeft, | ||
headerRight, | ||
textRow, | ||
status, | ||
reviewTitle: reviewTitleClass, | ||
evaluatorTitle: evaluatorTitleClass, | ||
reviewDate, | ||
} = ReviewDropdownVariants({ | ||
variant: "default", | ||
}); | ||
|
||
return ( | ||
<div className={cn(header())}> | ||
<div className={cn(headerLeft())}> | ||
<Icon type={evaluatorIconType} /> | ||
<div> | ||
<p className={cn(textRow())}> | ||
<span className={cn(reviewTitleClass())}>{reviewTitle}</span> | ||
<span className={cn(evaluatorTitleClass())}> {evaluatorTitle}</span> | ||
</p> | ||
<p className={cn(textRow(), reviewDate())}> | ||
Reviewed on {formatLocalDate(evaluation.lastUpdatedAt)} | ||
</p> | ||
</div> | ||
</div> | ||
<div className={cn(headerRight())}> | ||
<div className="flex gap-2"> | ||
{getIcon(evaluation.evaluationStatus)} | ||
<p className={cn(status())}> | ||
{rating}/{evaluation.evaluation.length} | ||
</p> | ||
</div> | ||
<Badge variant={reviewStatusBadgeVariant}> | ||
{evaluation.evaluationStatus.charAt(0).toUpperCase() + | ||
evaluation.evaluationStatus.slice(1)} | ||
</Badge> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
// Content Component | ||
const ReviewDropdownContent: React.FC<ReviewDropdownContentProps> = ({ evaluation }) => { | ||
const { content } = ReviewDropdownVariants({ variant: "default" }); | ||
|
||
return ( | ||
<div className={cn(content())}> | ||
<EvaluationSummary evaluation={evaluation} /> | ||
<EvaluationAnswers evaluation={evaluation} /> | ||
</div> | ||
); | ||
}; | ||
|
||
// Evaluation Summary Component | ||
const EvaluationSummary: React.FC<ReviewDropdownContentProps> = ({ evaluation }) => { | ||
const backgroundClass = evaluationSummaryVariants({ | ||
background: evaluation.evaluatorType === "human" ? "default" : "light", | ||
}); | ||
|
||
return ( | ||
<div className={cn(backgroundClass)}> | ||
<p>{evaluation.summary}</p> | ||
</div> | ||
); | ||
}; | ||
|
||
// Evaluation Answers Component | ||
const EvaluationAnswers: React.FC<ReviewDropdownContentProps> = ({ evaluation }) => { | ||
return ( | ||
<div className="flex flex-col gap-6"> | ||
{evaluation.evaluation.map((evaluation, index) => ( | ||
<div | ||
key={index} | ||
className="flex items-start gap-2 font-sans text-base font-normal leading-7 text-black" | ||
> | ||
<span className="mt-1 shrink-0">{getIcon(evaluation.answer)}</span> | ||
<p className="grow">{evaluation.question}</p> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default ReviewDropdown; | ||
|
||
const getIcon = (value: string) => { | ||
const iconMap = { | ||
approved: IconType.SOLID_CHECK, | ||
rejected: IconType.SOLID_X, | ||
uncertain: IconType.SOLID_QUESTION_MARK_CIRCLE, | ||
YES: IconType.SOLID_CHECK, | ||
NO: IconType.SOLID_X, | ||
UNCERTAIN: IconType.SOLID_QUESTION_MARK_CIRCLE, | ||
}; | ||
|
||
const iconType = Object.keys(iconMap).includes(value) | ||
? iconMap[value as keyof typeof iconMap] | ||
: IconType.SOLID_QUESTION_MARK_CIRCLE; | ||
|
||
return <Icon className="my-0.5" type={iconType} />; | ||
}; |
73 changes: 73 additions & 0 deletions
73
src/features/checker/components/ReviewDropdown/mockData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { EvaluationSummaryProps } from "./types"; | ||
|
||
export const mockData: EvaluationSummaryProps[] = [ | ||
{ | ||
evaluator: "0x1234567890123456789012345678901234567890", | ||
evaluationStatus: "approved", | ||
evaluatorType: "human", | ||
evaluation: [ | ||
{ | ||
question: "How would you rate the quality of the content?", | ||
answer: "YES", | ||
}, | ||
{ | ||
question: "Was the content easy to understand?", | ||
answer: "NO", | ||
}, | ||
{ | ||
question: | ||
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", | ||
answer: "UNCERTAIN", | ||
}, | ||
], | ||
summary: | ||
"The content was well-researched, engaging, and easy to understand. The overall experience was very positive.", | ||
lastUpdatedAt: "2024-11-20T13:04:36.042449", | ||
}, | ||
{ | ||
evaluator: "0x1234567890123456789012345678901234567890", | ||
evaluationStatus: "rejected", | ||
evaluatorType: "human", | ||
evaluation: [ | ||
{ | ||
question: "How would you rate the quality of the content?", | ||
answer: "YES", | ||
}, | ||
{ | ||
question: "Was the content easy to understand?", | ||
answer: "YES", | ||
}, | ||
{ | ||
question: | ||
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", | ||
answer: "UNCERTAIN", | ||
}, | ||
], | ||
summary: | ||
"The content was well-researched, engaging, and easy to understand. The overall experience was very positive.", | ||
lastUpdatedAt: "2024-11-20T13:04:36.042449", | ||
}, | ||
{ | ||
evaluator: "0x1234567890123456789012345678901234567890", | ||
evaluationStatus: "uncertain", | ||
evaluatorType: "llm_gpt3", | ||
evaluation: [ | ||
{ | ||
question: "How would you rate the quality of the content?", | ||
answer: "YES", | ||
}, | ||
{ | ||
question: "Was the content easy to understand?", | ||
answer: "NO", | ||
}, | ||
{ | ||
question: | ||
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. ", | ||
answer: "UNCERTAIN", | ||
}, | ||
], | ||
summary: | ||
"The content was well-researched, engaging, and easy to understand. The overall experience was very positive.", | ||
lastUpdatedAt: "2024-11-20T13:04:36.042449", | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export interface EvaluationQuestionProps { | ||
question: string; | ||
answer: "YES" | "NO" | "UNCERTAIN"; | ||
} | ||
|
||
export interface EvaluationSummaryProps { | ||
evaluator: string; | ||
evaluationStatus: "approved" | "rejected" | "pending" | "uncertain"; | ||
evaluatorType: "human" | "llm_gpt3"; | ||
evaluation: EvaluationQuestionProps[]; | ||
summary: string; | ||
lastUpdatedAt: string; | ||
} |
21 changes: 21 additions & 0 deletions
21
src/features/checker/components/ReviewDropdownList/ReviewDropdownList.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Meta, Stories, Controls, Story } from "@storybook/blocks"; | ||
import * as ReviewDropdownListStories from './ReviewDropdownList.stories'; | ||
import ReviewDropdownList from './ReviewDropdownList'; | ||
|
||
<Meta of={ReviewDropdownListStories}/> | ||
|
||
# Review Summary | ||
|
||
The `ReviewDropdownList` component renders a list of ReviewDropdown. | ||
|
||
## Example | ||
|
||
<Story name="Default" of={ReviewDropdownListStories.Default} /> | ||
|
||
## Controls | ||
|
||
<Controls /> | ||
|
||
## Other Variations | ||
|
||
<Stories /> |
Oops, something went wrong.