Skip to content

Commit

Permalink
Merge branch 'main' into aswani-pipelines-s3-typo
Browse files Browse the repository at this point in the history
  • Loading branch information
myronkaifung authored Dec 19, 2024
2 parents 292f856 + a35c913 commit ec6d88c
Show file tree
Hide file tree
Showing 93 changed files with 3,985 additions and 1,459 deletions.
213 changes: 213 additions & 0 deletions components/ChangelogIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import React, { useState, useEffect } from "react";
import { getPagesUnderRoute } from "nextra/context";
import { ImageFrame } from "./ImageFrame";
import { VideoButtonWithModal } from "./VideoButtonWithModal";
import Link from "next/link";
import { MdxFile } from "nextra";

enum PostFilterOptions {
All = `all`,
Announcements = `announcements`,
Updates = `updates`,
}

const renderImage = (page) => {
return (
<ImageFrame
src={page.frontMatter.thumbnail}
alt={page.frontMatter.title}
isAnnouncement={page.frontMatter?.isAnnouncement}
/>
);
};

const getVideoEmbedURL = (videoURL) => {
try {
const parsedURL = new URL(videoURL);
const host = parsedURL.host;
if (host === "www.youtube.com" || host === "youtube.com" || host === "youtu.be") {
const videoId = parsedURL.searchParams.get("v") || parsedURL.pathname.split("/").pop();
return `https://www.youtube.com/embed/${videoId}`;
} else if (host === "www.loom.com" || host === "loom.com") {
const videoId = parsedURL.pathname.split("/").pop();
return `https://www.loom.com/embed/${videoId}?hideEmbedTopBar=true`;
}
} catch (e) {
console.error("Invalid URL:", e);
}
};

const renderVideo = (videoURL) => {
const embedURL = getVideoEmbedURL(videoURL);

return (
<iframe
src={embedURL}
style={{
width: "100%",
aspectRatio: 16 / 9,
height: "auto",
borderRadius: "16px",
marginBottom: "16px",
}}
allow="clipboard-write; encrypted-media; picture-in-picture"
allowFullScreen
title="Video"
></iframe>
);
};

const renderMedia: (page: MdxFile) => React.JSX.Element = (page) => {
if (page.frontMatter?.thumbnail) {
return renderImage(page);
} else if (page.frontMatter?.video) {
return renderVideo(page.frontMatter?.video);
}
return null;
};

export default function ChangelogIndex({ more = "Learn More" }) {
// naturally sorts pages from a-z rather than z-a
const allPages = getPagesUnderRoute("/changelogs").reverse();
const itemsPerPage = 10;
const [displayedPages, setDisplayedPages] = useState([]);
const [pageIndex, setPageIndex] = useState(0);
const [filter, setFilter] = useState(PostFilterOptions.All);

const getAllFilteredPages = () => {
return allPages.filter((page) => {
switch (filter) {
case PostFilterOptions.Updates:
// @ts-ignore:next-line
return !page.frontMatter?.isAnnouncement;
case PostFilterOptions.Announcements:
// @ts-ignore:next-line
return page.frontMatter?.isAnnouncement === true;
default:
return true;
}
});
};

// Load initial or additional pages
useEffect(() => {
const morePages = getAllFilteredPages().slice(0, pageIndex + itemsPerPage);

setDisplayedPages(morePages);
}, [pageIndex, filter]);

const loadMore = () => {
setPageIndex((prev) => prev + itemsPerPage);
};

const filterButton = (id: PostFilterOptions, label: string) => {
let className = "changelogFilterButton";
if (filter === id) {
className += " active";
}
return (
<button
className={className}
onClick={() => {
setFilter(id);
setPageIndex(0);
}}
>
{label}
</button>
);
};

const filterOptions = [
{ id: PostFilterOptions.All, label: "All Posts" },
{ id: PostFilterOptions.Announcements, label: "Announcements" },
{ id: PostFilterOptions.Updates, label: "Updates" },
];

return (
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: "column",
}}
className="changelogIndexContainer"
>
<div className="changelogIndexFilterBorder" />
<div className="changelogIndexFilterBar">
{filterOptions.map((filter) => filterButton(filter.id, filter.label))}
</div>
<div className="changelogIndexFilterBorder changelogIndexFilterBorderBottom" />

{displayedPages.map((page) => (
<div key={page.route} className="changelogIndexItem nx-mt-16">
<div className="changelogIndexItemDate">
{page.frontMatter?.date ? (
<p className="changelogDate">
{new Date(page.frontMatter.date).toLocaleDateString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
})}
</p>
) : null}
</div>

<div className="changelogIndexItemBody">
{(page.frontMatter?.thumbnail || page.frontMatter?.video) &&
renderMedia(page)}

<h3
className={
page.frontMatter?.thumbnail && "changelogItemTitleWrapper"
}
>
<Link
href={page.route}
style={{ color: "inherit", textDecoration: "none" }}
className="changelogItemTitle block"
>
{page.meta?.title || page.frontMatter?.title || page.name}
</Link>
</h3>

<p className="opacity-80 mt-6 leading-7">
{page.frontMatter?.description}
</p>
<div className="nx-isolate nx-inline-flex nx-items-center nx-space-x-5 nx-mt-8">
{page.frontMatter?.isAnnouncement && (
<a
href="https://mixpanel.com/contact-us/demo-request/"
className="nx-px-5 nx-py-3 nx-my-4 nx-drop-shadow-sm nx-bg-gradient-to-t nx-from-purple100 nx-to-purple50 nx-rounded-full nx-text-white nx-font-medium nx-text-medium"
>
Request a Demo
</a>
)}
{page.frontMatter?.video && page.frontMatter?.thumbnail && (
<VideoButtonWithModal
src={page.frontMatter.video}
showThumbnail={false}
/>
)}
<Link
target="_blank"
href={page.route}
className="changelogReadMoreLink"
>
{more + " →"}
</Link>
</div>
<div className="changelogDivider nx-mt-16"></div>
</div>
</div>
))}
{pageIndex + itemsPerPage < getAllFilteredPages().length && (
<div className="changelogLoadMoreButtonContainer">
<button onClick={loadMore} className="changelogLoadMoreButton">
Load More
</button>
</div>
)}
</div>
);
}
31 changes: 31 additions & 0 deletions components/ChangelogPostHeader/ChangelogPostHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ImageFrame } from "../ImageFrame";

type ChangelogHeaderProps = {
date?: string;
image?: any;
title?: string;
};

export default function ChangelogPostHeader({
date,
image,
title,
}: ChangelogHeaderProps) {
return (
<div className="changelogPostHeader">
{date && (
<p className="changelogDate">
{new Date(date).toLocaleDateString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
})}
</p>
)}

{title && <h3 className="changelogTitle">{title}</h3>}

{image && <ImageFrame src={image} alt={title} />}
</div>
);
}
102 changes: 102 additions & 0 deletions components/ImageFrame/ImageFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useLayoutEffect, useRef, useState } from "react";
import Image from "next/image";
// https://www.tailwind-variants.org/docs
import { tv } from "tailwind-variants";

type ImageFrameProps = {
src: string;
alt?: string;
isAnnouncement?: boolean;
};

const MAX_IMAGE_HEIGHT_WITHOUT_OVERFLOW = 400;

export default function ImageFrame({
alt = "Thumbnail of screenshot",
isAnnouncement = false,
...props
}: ImageFrameProps) {
const imageRef = useRef<HTMLImageElement>(null);
const lightImageRef = useRef<HTMLImageElement>(null);
const darkImageRef = useRef<HTMLImageElement>(null);

const [height, setHeight] = useState(0);
const [width, setWidth] = useState(0);

useLayoutEffect(() => {
if (isAnnouncement) {
setHeight(imageRef.current.getBoundingClientRect().height);
setWidth(imageRef.current.getBoundingClientRect().width);
} else {
// display: none still renders the HTML, and returns 0 for both height and width
setHeight(
Math.max(
lightImageRef.current?.getBoundingClientRect().height,
darkImageRef.current?.getBoundingClientRect().height
)
);
setWidth(
Math.max(
lightImageRef?.current?.getBoundingClientRect().width,
darkImageRef?.current?.getBoundingClientRect().width
)
);
}
}, [setHeight, setWidth]);

const isTall = height > MAX_IMAGE_HEIGHT_WITHOUT_OVERFLOW;

const createTVConfig = (is_light = true) => {
const lightBase = `nx-aspect-video nx-overflow-hidden nx-nx-mt-8 lg:nx-rounded-3xl nx-rounded-xl nx-mb-8 nx-px-8 sm:nx-px-10 md:nx-px-10 lg:nx-px-14 nx-bg-gradient-to-t nx-from-grey20-opacity-100 nx-to-grey20-opacity-50`;
const darkBase = `nx-aspect-video nx-overflow-hidden nx-nx-mt-8 lg:nx-rounded-3xl nx-rounded-xl nx-mb-8 nx-px-8 sm:nx-px-10 md:nx-px-10 lg:nx-px-14 nx-bg-gradient-to-t nx-from-grey20-opacity-8 nx-to-grey20-opacity-5`;
return {
base: is_light ? lightBase : darkBase,
variants: {
isTall: {
false: "nx-flex nx-justify-center nx-items-center",
},
},
};
};

const darkImageFrame = tv(createTVConfig(false));
const lightImageFrame = tv(createTVConfig(true));

const imageSelf = tv({
base: "nx-w-full max-h-96 h-full nx-shadow-sm",
variants: {
isTall: {
true: "nx-border nx-border-grey20",
false: "nx-rounded-2xl",
},
isAnnouncement: {
true: "lg:nx-rounded-3xl nx-rounded-xl nx-mb-8 nx-bg-transparent nx-border-0",
},
},
});

const renderImageComponent = (refInstance) => (
<Image
ref={refInstance}
src={props.src}
height={height}
width={width}
className={imageSelf({ isTall: isTall, isAnnouncement: isAnnouncement })}
alt={alt}
/>
);

return isAnnouncement ? (
<>{renderImageComponent(imageRef)}</>
) : (
<>
<div className={`lightImageFrame ` + lightImageFrame({ isTall: isTall })}>
{renderImageComponent(lightImageRef)}
</div>

<div className={`darkImageFrame ` + darkImageFrame({ isTall: isTall })}>
{renderImageComponent(darkImageRef)}
</div>
</>
);
}
1 change: 1 addition & 0 deletions components/ImageFrame/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ImageFrame } from "./ImageFrame";
8 changes: 4 additions & 4 deletions components/MainContent/MainContent.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { FeedbackCollector } from "../FeedbackCollector/FeedbackCollector";

interface Props {
children: React.ReactNode
children: React.ReactNode;
}

const MainContent: React.FC<Props> = ({children}): JSX.Element => {
const MainContent: React.FC<Props> = ({ children }): JSX.Element => {
return (
<>
{children}
<FeedbackCollector />
</>
)
}
);
};

export default MainContent;
4 changes: 4 additions & 0 deletions components/Search/Search.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
margin-right: 6px;
}

.searchBar {
background: rgba(0, 0, 0, 0);
}

.searchBar:focus {
box-shadow: none;
}
Expand Down
4 changes: 2 additions & 2 deletions components/SignUpButton/SignUpButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

.signUpButton {
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
background-color: colors.$purple50;
font-weight: 600;
background: linear-gradient(1deg, colors.$purple100 0.97%, colors.$purple50 96.96%);
font-weight: 500;
font-size: 1rem;
line-height: 1.5rem;
color: colors.$white;
Expand Down
Loading

0 comments on commit ec6d88c

Please sign in to comment.