Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(review-requests): review overview on dashboard #1085

Merged
merged 16 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/components/MenuDropdownButton/MenuDropdownButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ interface MenuDropdownButtonProps extends ButtonProps {
mainButtonText: string
}

// NOTE: For icon buttons where the bg is clear, icon.default won't clash with the background
// However, for solid background, return icon.inverse so that the chevron is clear.
const computeIconFill = (variant: ButtonProps["variant"]): string => {
if (variant === "solid") {
return "icon.inverse"
}

return "icon.default"
}

/**
* The button props and mainButtonText props are passed to the main button.
* The children props are passed to the context menu and should be a list of
Expand Down Expand Up @@ -55,11 +65,12 @@ export const MenuDropdownButton = forwardRef<MenuDropdownButtonProps, "button">(
borderLeftRadius={0}
aria-label="Select options"
variant={buttonVariant}
colorScheme={props.colorScheme}
icon={
<Icon
as={isOpen ? BiChevronUp : BiChevronDown}
fontSize="1rem"
fill="icon.default"
fill={computeIconFill(buttonVariant)}
/>
}
/>
Expand Down
27 changes: 27 additions & 0 deletions src/layouts/ReviewRequest/Dashboard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ComponentMeta, Story } from "@storybook/react"

import { MOCK_ITEMS } from "mocks/constants"

import {
ReviewRequestDashboard,
ReviewRequestDashboardProps,
} from "./Dashboard"

const dashboardMeta = {
title: "Components/ReviewRequest/Dashboard",
component: ReviewRequestDashboard,
} as ComponentMeta<typeof ReviewRequestDashboard>

const Template = ReviewRequestDashboard

export const Playground: Story<ReviewRequestDashboardProps> = Template.bind({})
Playground.args = {
reviewRequestedTime: new Date(),
reviewUrl: "Copied to your clipboard kekw",
title: "Update STCCED hyperlink, customs duty",
requestor: "seaerchin",
reviewers: ["nat mae tan", "jiachin er"],
changedItems: MOCK_ITEMS,
}

export default dashboardMeta
177 changes: 177 additions & 0 deletions src/layouts/ReviewRequest/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {
HStack,
VStack,
Text,
Box,
Avatar,
Flex,
Spacer,
useClipboard,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
PopoverArrow,
IconButton,
} from "@chakra-ui/react"
import {
MenuDropdownButton,
MenuDropdownItem,
} from "components/MenuDropdownButton"
import { useState } from "react"
import { BiLink, BiPlus } from "react-icons/bi"

import { extractInitials, getDateTimeFromUnixTime } from "utils"

import { RequestOverview, EditedItemProps } from "./components/RequestOverview"

export interface ReviewRequestDashboardProps {
reviewUrl: string
title: string
requestor: string
reviewers: string[]
reviewRequestedTime: Date
changedItems: EditedItemProps[]
}
export const ReviewRequestDashboard = ({
reviewRequestedTime,
reviewUrl,
title,
requestor,
reviewers,
changedItems,
}: ReviewRequestDashboardProps): JSX.Element => {
const { onCopy, hasCopied } = useClipboard(reviewUrl)

return (
<Box bg="white" w="100%" h="100%">
<VStack
bg="blue.50"
pl="9.25rem"
pr="2rem"
dcshzj marked this conversation as resolved.
Show resolved Hide resolved
pt="2rem"
pb="1.5rem"
spacing="0.75rem"
align="flex-start"
>
<Flex w="100%">
<HStack spacing="0.25rem">
<Text textStyle="h5">{title}</Text>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will truncation be performed for this title?

Also I think the heading does not appear the same way as the one on the Figma (it's smaller and not bold here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the heading one: yea, this is a known issue... this is cos the base design system has wrong textStyles (fact-check me @NatMaeTan)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no truncation - will spill over; no strong opinion, will check with @NatMaeTan

{/* Closes after 1.5s and does not refocus on the button to avoid the outline */}
<Popover returnFocusOnClose={false} isOpen={hasCopied}>
<PopoverTrigger>
<IconButton
icon={<BiLink />}
variant="clear"
aria-label="link to pull request"
onClick={onCopy}
/>
</PopoverTrigger>
<PopoverContent
bg="background.action.alt"
_focus={{
boxShadow: "none",
}}
w="fit-content"
>
<PopoverArrow bg="background.action.alt" />
<PopoverBody>
<Text textStyle="body-2" color="text.inverse">
Link copied!
</Text>
</PopoverBody>
</PopoverContent>
</Popover>
</HStack>
<Spacer />
<ApprovalButton />
</Flex>
<SecondaryDetails
requestor={requestor}
reviewers={reviewers}
reviewRequestedTime={reviewRequestedTime}
/>
</VStack>
<Box pl="9.25rem" pr="2rem">
<RequestOverview items={changedItems} allowEditing />
</Box>
</Box>
)
}

// NOTE: Utility component exists to soothe over state management
const ApprovalButton = (): JSX.Element => {
const [isApproved, setIsApproved] = useState(false)

return (
<MenuDropdownButton
colorScheme={isApproved ? "success" : "primary"}
mainButtonText={isApproved ? "Approved" : "In review"}
variant="solid"
>
<MenuDropdownItem onClick={() => setIsApproved(false)}>
<Text textStyle="body-1" textColor="text.body" w="100%">
In review
</Text>
</MenuDropdownItem>
<MenuDropdownItem onClick={() => setIsApproved(true)}>
<Text textStyle="body-1" textColor="text.success" w="100%">
Approved
</Text>
</MenuDropdownItem>
</MenuDropdownButton>
)
}

interface SecondaryDetailsProps {
requestor: string
reviewers: string[]
reviewRequestedTime: Date
}
const SecondaryDetails = ({
requestor,
reviewers,
reviewRequestedTime,
}: SecondaryDetailsProps) => {
const { date, time } = getDateTimeFromUnixTime(reviewRequestedTime.getTime())
return (
<VStack spacing="0.5rem" align="flex-start">
<Text textStyle="caption-2" textColor="text.helper">
{`Review requested by ${requestor} on ${date} ${time}`}
</Text>
<HStack spacing="0.75rem">
<Text textStyle="caption-2" textColor="text.helper">
Reviewers
</Text>
<Box>
{reviewers.map((name, index) => {
const initials = extractInitials(name)
return (
<Avatar
zIndex={reviewers.length - index}
border="1px solid white"
ml="-0.25rem"
bg="primary.100"
name={initials}
textStyle="caption-1"
textColor="primary.400"
size="sm"
/>
)
})}
{/* NOTE: Not using design system IconButton as we require sm size */}
<IconButton
icon={<BiPlus />}
aria-label="Add Reviewer"
variant="outline"
borderRadius="50%"
fontSize="1rem"
size="sm"
ml="-0.25rem"
bg="blue.50"
/>
</Box>
</HStack>
</VStack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MOCK_ITEMS } from "mocks/constants"
import { RequestOverview, RequestOverviewProps } from "./RequestOverview"

const overviewMeta = {
title: "Components/ReviewRequestModal/Overview",
title: "Components/ReviewRequest/Modal Overview",
component: RequestOverview,
} as ComponentMeta<typeof RequestOverview>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
BiChevronDown,
BiCompass,
BiCheck,
BiEditAlt,
} from "react-icons/bi"
import { TableVirtuoso } from "react-virtuoso"

Expand Down Expand Up @@ -164,10 +165,15 @@ const LastEditedMeta = ({

export interface RequestOverviewProps {
items: EditedItemProps[]
// NOTE: An alternative way to do this would be to allow consumers to pass in the header
// and let it have the same shape as react-table's header.
// However, this component seems limited in scope so the easier way out was chosen.
allowEditing?: boolean
}

export const RequestOverview = ({
items,
allowEditing,
}: RequestOverviewProps): JSX.Element => {
const {
isExpanded,
Expand Down Expand Up @@ -335,6 +341,15 @@ export const RequestOverview = ({
header: () => null,
cell: ({ row }) => (
<HStack spacing="0.25rem">
{allowEditing && (
<Link href="www.google.com">
<IconButton
icon={<BiEditAlt />}
aria-label="edit file"
variant="link"
/>
</Link>
)}
<Link href={row.original.url}>
<IconButton
icon={<BiGitCompare />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MOCK_ADMINS } from "mocks/constants"
import { ReviewRequestForm, ReviewRequestFormProps } from "./ReviewRequestForm"

const formMeta = {
title: "Components/ReviewRequestModal/Form",
title: "Components/ReviewRequest/Modal Form",
component: ReviewRequestForm,
} as ComponentMeta<typeof ReviewRequestForm>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { Story, ComponentMeta } from "@storybook/react"

import { MOCK_ITEMS, MOCK_ADMINS } from "mocks/constants"

import { EditedItemProps } from "./RequestOverview"
import {
ReviewRequestModal,
ReviewRequestModalProps,
} from "./ReviewRequestModal"

const modalMeta = {
title: "Components/ReviewRequestModal/Modal",
title: "Components/ReviewRequest/Modal",
component: ReviewRequestModal,
} as ComponentMeta<typeof ReviewRequestModal>
type TemplateProps = Pick<ReviewRequestModalProps, "admins" | "items">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
} from "@chakra-ui/react"
import { Button, ModalCloseButton, Tab } from "@opengovsg/design-system-react"

import { EditedItemProps, RequestOverview } from "./RequestOverview"
import { ReviewRequestForm, ReviewRequestFormProps } from "./ReviewRequestForm"
import { EditedItemProps, RequestOverview } from "../RequestOverview"
import { ReviewRequestForm, ReviewRequestFormProps } from "../ReviewRequestForm"

export type ReviewRequestModalProps = ModalProps &
ReviewRequestFormProps & { items: EditedItemProps[] }
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/mocks/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EditedItemProps } from "layouts/Dashboard/components/ReviewRequestModal/RequestOverview"
import { EditedItemProps } from "layouts/ReviewRequest/components/RequestOverview"

import {
DirectoryData,
Expand Down
5 changes: 5 additions & 0 deletions src/theme/foundations/colours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const colours: { [k in IsomerColorScheme]: NestedRecord } = {
helper: "#848484",
placeholder: "#A0A0A0",
description: "#474747",
success: "#00774E",
inverse: "#FFFFFF",
title: {
brand: "#2164DA",
brandSecondary: "#3C4764",
Expand All @@ -62,7 +64,10 @@ export const colours: { [k in IsomerColorScheme]: NestedRecord } = {
},
background: {
action: {
default: "#2164DA",
success: "#00774E",
defaultInverse: "#FFFFFF",
alt: "#5D6785",
altInverse: "#F8F9FA",
infoInverse: "#F7F9FE",
},
Expand Down