Skip to content

Commit

Permalink
added SidebarCard for promo to v2 and storybook (#3906)
Browse files Browse the repository at this point in the history
Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 21, 2022
1 parent dbf7f84 commit 63bb6b6
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 13 deletions.
1 change: 1 addition & 0 deletions apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@storybook/builder-webpack5": "^6.5.10",
"@storybook/manager-webpack5": "^6.5.10",
"@storybook/react": "^6.5.10",
"@tailwindcss/line-clamp": "^0.4.0",
"@types/node": "16.9.1",
"@types/react": "^18.0.9",
"@types/react-dom": "18.0.4",
Expand Down
26 changes: 26 additions & 0 deletions apps/storybook/stories/Cards.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentMeta } from "@storybook/react";
import { useState } from "react";

import Card, { BaseCardProps } from "@calcom/ui/v2/core/Card";

Expand Down Expand Up @@ -51,3 +52,28 @@ export const AppStore = () => {
</div>
);
};

const sidebarCardProps: BaseCardProps = {
variant: "SidebarCard",
thumbnailUrl: "https://img.youtube.com/vi/60HJt8DOVNo/0.jpg",
mediaLink: "https://www.youtube.com/watch?v=60HJt8DOVNo",
title: "Dynamic boooking links",
description: "Booking link that allows people to quickly schedule meetings.",
learnMore: {
href: "https://cal.com/blog/cal-v-1-9",
text: "Learn more",
},
};

export const SidebarCard = () => {
const [visible, setVisible] = useState(true); // save state in localStorage, cookie or db
return (
<header className="w-full max-w-[225px] bg-gray-100 p-3">
{visible && (
<div>
<Card {...sidebarCardProps} actionButton={{ onClick: () => setVisible(false), child: "Dismiss" }} />
</div>
)}
</header>
);
};
108 changes: 95 additions & 13 deletions packages/ui/v2/core/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
import Link from "next/link";
import { ReactNode } from "react";
import React from "react";

import classNames from "@calcom/lib/classNames";

import { Badge } from "./Badge";
import Button from "./Button";

export type BaseCardProps = {
image: string;
image?: string;
variant: keyof typeof cardTypeByVariant;
imageProps?: JSX.IntrinsicElements["img"];
title: string;
description: string;
description: ReactNode;
containerProps?: JSX.IntrinsicElements["div"];
actionButton?: {
href: string;
href?: string;
child: ReactNode;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
};
learnMore?: {
href: string;
text: string;
};
mediaLink?: string;
thumbnailUrl?: string;
};

const cardTypeByVariant = {
AppStore: {
image: "w-10 h-auto",
card: "p-5 w-64",
title: "text-base",
description: "text-sm leading-[18px] text-gray-500 font-normal",
},
ProfileCard: {
image: "w-9 h-auto rounded-full mb-4s",
card: "w-80 p-4 hover:bg-gray-100",
actionButton: "",
title: "text-base",
description: "text-sm leading-[18px] text-gray-500 font-normal",
},
SidebarCard: {
image: "w-9 h-auto rounded-full mb-4s",
card: "w-full p-3 border border-gray-200",
title: "text-sm font-cal",
description: "text-xs text-gray-600 line-clamp-2",
},
};

Expand All @@ -37,6 +56,9 @@ export function Card({
actionButton,
containerProps,
imageProps,
mediaLink,
thumbnailUrl,
learnMore,
}: BaseCardProps) {
return (
<div
Expand All @@ -46,21 +68,81 @@ export function Card({
"border-1 rounded-md border-gray-200 bg-white"
)}
{...containerProps}>
<img
src={image}
// Stops eslint complaining - not smart enough to realise it comes from ...imageProps
alt={imageProps?.alt}
className={classNames(imageProps?.className, cardTypeByVariant[variant].image, "mb-4")}
{...imageProps}
/>
<span className="text-base font-bold leading-5 text-gray-900 ">{title}</span>
<p className="pt-1 text-sm font-normal leading-[18px] text-gray-500">{description}</p>
{image && (
<img
src={image}
// Stops eslint complaining - not smart enough to realise it comes from ...imageProps
alt={imageProps?.alt}
className={classNames(imageProps?.className, cardTypeByVariant[variant].image, "mb-4")}
{...imageProps}
/>
)}
<h5
title={title}
className={classNames(
cardTypeByVariant[variant].title,
"line-clamp-1 font-bold leading-5 text-gray-900"
)}>
{title}
</h5>
{description && (
<p
title={description.toString()}
className={classNames(cardTypeByVariant[variant].description, "pt-1")}>
{description}
</p>
)}
{variant === "SidebarCard" && (
<a
target="_blank"
rel="noreferrer"
href={mediaLink}
className="group relative my-3 flex aspect-video items-center overflow-hidden rounded">
<div className="absolute inset-0 bg-black bg-opacity-50 transition-opacity group-hover:bg-opacity-40" />
<svg
className="absolute top-1/2 left-1/2 h-8 w-8 -translate-x-1/2 -translate-y-1/2 transform rounded-full text-white shadow-lg hover:-mt-px"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z"
fill="white"
/>
<path
d="M12.1667 8.5L23.8334 16L12.1667 23.5V8.5Z"
fill="#111827"
stroke="#111827"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<img alt="play feature video" src={thumbnailUrl} />
</a>
)}
{variant === "AppStore" && (
<Button color="secondary" href={actionButton?.href} size="lg" className="mt-10 w-full">
{/* Force it to be centered as this usecase of a button is off - doesnt meet normal sizes */}
<div className="mx-auto">{actionButton?.child}</div>
</Button>
)}
{variant === "SidebarCard" && (
<div className="mt-2 flex items-center justify-between">
{learnMore && (
<Link href={learnMore.href}>
<a target="_blank" rel="noreferrer" className="text-xs font-medium">
{learnMore.text}
</a>
</Link>
)}
<button
className="p-0 text-xs font-normal text-gray-600 hover:text-gray-800"
color="minimal"
onClick={actionButton?.onClick}>
{actionButton?.child}
</button>
</div>
)}
</div>
);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/ui/v2/core/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import pkg from "../../../../apps/web/package.json";
import ErrorBoundary from "../../ErrorBoundary";
import { KBarRoot, KBarContent, KBarTrigger } from "../../Kbar";
import Logo from "../../Logo";
import Tips from "../modules/tips/Tips";
import Card from "./Card";
import HeadSeo from "./head-seo";

/* TODO: Migate this */
Expand Down Expand Up @@ -563,6 +565,8 @@ function SideBarContainer() {
}

function SideBar() {
const [visible, setVisible] = useState(true);
const { t } = useLocale();
return (
<aside className="hidden w-14 flex-col border-r border-gray-100 bg-gray-50 px-2 md:flex lg:w-56 lg:flex-shrink-0 lg:px-4">
<div className="flex h-0 flex-1 flex-col overflow-y-auto pt-3 pb-4 lg:pt-5">
Expand All @@ -584,6 +588,9 @@ function SideBar() {
</Link>
<Navigation />
</div>

<Tips />

<TrialBanner />
<div data-testid="user-dropdown-trigger">
<span className="hidden lg:inline">
Expand Down
108 changes: 108 additions & 0 deletions packages/ui/v2/modules/tips/Tips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import autoAnimate from "@formkit/auto-animate";
import { useEffect, useState, useRef } from "react";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";

import Card from "../../core/Card";

const tips = [
{
id: 1,
thumbnailUrl: "https://img.youtube.com/vi/60HJt8DOVNo/0.jpg",
mediaLink: "https://www.youtube.com/watch?v=60HJt8DOVNo",
title: "Dynamic booking links",
description: "Booking link that allows people to quickly schedule meetings.",
href: "https://cal.com/blog/cal-v-1-9",
},
{
id: 2,
thumbnailUrl: "https://img.youtube.com/vi/EAc46SPL6iA/0.jpg",
mediaLink: "https://youtu.be/EAc46SPL6iA",
title: "How to set up Teams",
description: "Learn how to use round-robin and collective events.",
href: "https://docs.cal.com/deep-dives/event-types",
},
{
id: 3,
thumbnailUrl: "https://img.youtube.com/vi/c7ZKFuLy1fg/0.jpg",
mediaLink: "https://youtu.be/c7ZKFuLy1fg",
title: "Routing Forms, Workflows",
description: "Ask screening questions of potential bookers to connect them with the right person",
href: "https://cal.com/blog/cal-v-1-8",
},
];

export default function Tips() {
const animationRef = useRef(null);

useEffect(() => {
animationRef.current && autoAnimate(animationRef.current, { duration: 250, easing: "ease-out" });
}, [animationRef]);

const { t } = useLocale();

const [list, setList] = useState<typeof tips>([]);

const handleRemoveItem = (id: number) => {
setList((currentItems) => {
const items = localStorage.getItem("removedTipsIds") || "";
const itemToRemoveIndex = currentItems.findIndex((item) => item.id === id);

localStorage.setItem(
"removedTipsIds",
`${currentItems[itemToRemoveIndex].id.toString()}${items.length > 0 ? `,${items.split(",")}` : ""}`
);
currentItems.splice(itemToRemoveIndex, 1);
return [...currentItems];
});
};

useEffect(() => {
const reversedTips = tips.slice(0).reverse();

const removedTipsString = localStorage.getItem("removedTipsIds") || "";
const removedTipsIds = removedTipsString.split(",").map((id) => parseInt(id, 10));
const filteredTips = reversedTips.filter((tip) => removedTipsIds.indexOf(tip.id) === -1);
setList(() => [...filteredTips]);
}, []);
const baseOriginalList = list.slice(0).reverse();
return (
<div
className="mb-4 hidden lg:grid"
ref={animationRef}
style={{
gridTemplateColumns: "1fr",
}}>
{list.map((tip, index) => {
return (
<div
className="relative"
style={{
gridRowStart: 1,
gridColumnStart: 1,
}}
key={tip.id}>
<div
className="relative"
style={{
transform: `scale(${1 - baseOriginalList.indexOf(tip) / 20})`,
top: -baseOriginalList.indexOf(tip) * 10,
opacity: `${1 - baseOriginalList.indexOf(tip) / 7}`,
}}>
<Card
variant="SidebarCard"
thumbnailUrl={tip.thumbnailUrl}
mediaLink={tip.mediaLink}
title={tip.title}
description={tip.description}
learnMore={{ href: tip.href, text: t("learn_more") }}
actionButton={{ onClick: () => handleRemoveItem(tip.id), child: t("dismiss") }}
/>
</div>
</div>
);
})}
</div>
);
}
1 change: 1 addition & 0 deletions packages/ui/v2/modules/tips/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Tips } from "./Tips";

0 comments on commit 63bb6b6

Please sign in to comment.