diff --git a/packages/paste-website/src/components/customization-landing-page/WhyPaste.tsx b/packages/paste-website/src/components/customization-landing-page/WhyPaste.tsx
index ffdbedef22..c1e33fe631 100644
--- a/packages/paste-website/src/components/customization-landing-page/WhyPaste.tsx
+++ b/packages/paste-website/src/components/customization-landing-page/WhyPaste.tsx
@@ -7,56 +7,12 @@ import Image from "next/image";
import { DoodleLoopArrowLarge } from "../../assets/illustrations/DoodleLoopArrowLarge";
import WhyPasteMobile from "../../assets/images/customization/why-paste-mobile.png";
import WhyPasteImg from "../../assets/images/customization/why-paste.png";
-import { useDarkModeContext } from "../../context/DarkModeContext";
-import { useSlantedSkew } from "../SlantedBackgroundGradient";
import { A11yIcon } from "../icons/A11yIcon";
import { CustomizableIcon } from "../icons/CustomizableIcon";
import { ThemableIcon } from "../icons/ThemableIcon";
import { LandingPageSection, LandingPageSectionContent } from "./LandingPageLayoutUtils";
import { ReasonBlock } from "./ReasonBlock";
-const WhyPasteTopAngle = (): JSX.Element => {
- const [skewOffset] = useSlantedSkew();
- const { theme } = useDarkModeContext();
-
- return (
-
- );
-};
-
-const WhyPasteBottomAngle = (): JSX.Element => {
- const [skewOffset] = useSlantedSkew(-0.35);
- const { theme } = useDarkModeContext();
-
- return (
-
- );
-};
-
export const WhyPaste = (): JSX.Element => {
return (
{
maxWidth="96%"
marginX="auto"
>
-
-
diff --git a/packages/paste-website/src/components/homepage/Accessibility.tsx b/packages/paste-website/src/components/homepage/Accessibility.tsx
index 8750983fec..6c935d2e9a 100644
--- a/packages/paste-website/src/components/homepage/Accessibility.tsx
+++ b/packages/paste-website/src/components/homepage/Accessibility.tsx
@@ -19,7 +19,7 @@ import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@twilio-paste/tabs";
import { useUID } from "@twilio-paste/uid-library";
import * as React from "react";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
import { BouncyAnchor } from "./BouncyAnchor";
import { SectionSeparator } from "./SectionSeparator";
@@ -32,7 +32,13 @@ const Accessibility: React.FC = (): React.ReactElement => {
return (
-
+
Accessibility
{
Accessibility is more than just color contrast. We believe in designing for each user, not just “all
users”. We build with these considerations in mind:
-
-
- Screen magnification, voice dictation, color blindness, and those who require help with fine motor
- control.
-
- Semantic HTML to better communicate with assistive technologies.
- UI controls that are designed to be instantly recognizable and easy to see.
-
- Keyboard navigation and focus management to allow task completion through a variety of input devices.
-
-
+
+
+ Screen magnification, voice dictation, color blindness, and those who require help with fine motor
+ control.
+
+ Semantic HTML to better communicate with assistive technologies.
+ UI controls that are designed to be instantly recognizable and easy to see.
+
+ Keyboard navigation and focus management to allow task completion through a variety of input devices.
+
+
diff --git a/packages/paste-website/src/components/homepage/CommunityOfBuilders.tsx b/packages/paste-website/src/components/homepage/CommunityOfBuilders.tsx
index 7963069bbb..d1756d8033 100644
--- a/packages/paste-website/src/components/homepage/CommunityOfBuilders.tsx
+++ b/packages/paste-website/src/components/homepage/CommunityOfBuilders.tsx
@@ -5,7 +5,7 @@ import Image from "next/image";
import * as React from "react";
import DesignTool from "../../assets/illustrations/illo_design-tool.svg";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
const StatBox: React.FC<{ stat: string; description: string }> = ({ stat, description }): React.ReactElement => {
return (
@@ -36,7 +36,7 @@ const CommunityOfBuilders: React.FC = (): React.ReactElement => {
display="flex"
flexDirection="column"
width="100%"
- maxWidth={SITE_CONTENT_MAX_WIDTH}
+ maxWidth={HOMEPAGE_SITE_CONTENT_MAX_WIDTH}
backgroundColor="colorBackgroundWarningWeakest"
paddingY="space170"
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
diff --git a/packages/paste-website/src/components/homepage/DesignEfficiency.tsx b/packages/paste-website/src/components/homepage/DesignEfficiency.tsx
index 74cabde30e..29e133c50b 100644
--- a/packages/paste-website/src/components/homepage/DesignEfficiency.tsx
+++ b/packages/paste-website/src/components/homepage/DesignEfficiency.tsx
@@ -7,13 +7,19 @@ import Image from "next/image";
import * as React from "react";
import TokenColors from "../../assets/illustrations/token_colors.svg";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
import { SectionSeparator } from "./SectionSeparator";
const DesignEfficiency: React.FC = (): React.ReactElement => {
return (
-
+
Design efficiency
diff --git a/packages/paste-website/src/components/homepage/NewHomeHero.tsx b/packages/paste-website/src/components/homepage/NewHomeHero.tsx
index 50aa4b7459..89888c30e0 100644
--- a/packages/paste-website/src/components/homepage/NewHomeHero.tsx
+++ b/packages/paste-website/src/components/homepage/NewHomeHero.tsx
@@ -3,13 +3,13 @@ import { DisplayHeading } from "@twilio-paste/display-heading";
import { Text } from "@twilio-paste/text";
import { useTheme } from "@twilio-paste/theme";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
import CircleIcon from "../icons/CircleIcon";
import { BouncyAnchor } from "./BouncyAnchor";
import { SearchBox } from "./SearchBox";
import { ComponentShowcase } from "./component-showcase";
-const NewHomeHero = (): JSX.Element => {
+const HomeHero = (): JSX.Element => {
const theme = useTheme();
return (
@@ -18,10 +18,10 @@ const NewHomeHero = (): JSX.Element => {
position="relative"
display="grid"
gridTemplateColumns="600px min-content"
- maxWidth={SITE_CONTENT_MAX_WIDTH}
+ maxWidth={HOMEPAGE_SITE_CONTENT_MAX_WIDTH}
marginLeft="auto"
marginRight="auto"
- overflow="hidden"
+ overflow="visible"
element="HOME_HERO"
>
{
);
};
-export { NewHomeHero };
+export { HomeHero };
diff --git a/packages/paste-website/src/components/homepage/NewSection.tsx b/packages/paste-website/src/components/homepage/NewSection.tsx
index 4aec90ab8d..dc9266e981 100644
--- a/packages/paste-website/src/components/homepage/NewSection.tsx
+++ b/packages/paste-website/src/components/homepage/NewSection.tsx
@@ -2,7 +2,7 @@ import { Anchor } from "@twilio-paste/anchor";
import { Box } from "@twilio-paste/box";
import * as React from "react";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
import { WhatsNew } from "./WhatsNew";
const NewSection: React.FC = (): React.ReactElement => {
@@ -16,7 +16,7 @@ const NewSection: React.FC = (): React.ReactElement => {
justifyContent="space-between"
paddingY="space200"
width="size120"
- maxWidth={SITE_CONTENT_MAX_WIDTH}
+ maxWidth={HOMEPAGE_SITE_CONTENT_MAX_WIDTH}
>
We're hiring a Product Designer! Apply here
diff --git a/packages/paste-website/src/components/homepage/Templates.tsx b/packages/paste-website/src/components/homepage/Templates.tsx
index a05852fb41..b14f96e8d4 100644
--- a/packages/paste-website/src/components/homepage/Templates.tsx
+++ b/packages/paste-website/src/components/homepage/Templates.tsx
@@ -5,12 +5,12 @@ import { Pagination, PaginationArrow, PaginationItems } from "@twilio-paste/pagi
import { Text } from "@twilio-paste/text";
import * as React from "react";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
const Templates: React.FC = (): React.ReactElement => {
return (
-
+
Explore our templates
diff --git a/packages/paste-website/src/components/homepage/Themeable.tsx b/packages/paste-website/src/components/homepage/Themeable.tsx
index 6000daed60..cfbf838a94 100644
--- a/packages/paste-website/src/components/homepage/Themeable.tsx
+++ b/packages/paste-website/src/components/homepage/Themeable.tsx
@@ -8,7 +8,7 @@ import * as React from "react";
import FigmaLogo from "../../assets/illustrations/figma-logo.svg";
import ReactLogo from "../../assets/illustrations/react-logo.svg";
import TypescriptLogo from "../../assets/illustrations/ts-logo.svg";
-import { SITE_CONTENT_MAX_WIDTH } from "../../constants";
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../constants";
import { SectionSeparator } from "./SectionSeparator";
const Themeable: React.FC = (): React.ReactElement => {
@@ -23,7 +23,7 @@ const Themeable: React.FC = (): React.ReactElement => {
justifyItems="center"
rowGap="space100"
width="100%"
- maxWidth={SITE_CONTENT_MAX_WIDTH}
+ maxWidth={HOMEPAGE_SITE_CONTENT_MAX_WIDTH}
>
Themeable and composable
diff --git a/packages/paste-website/src/components/icons/NewHeartDoodleIcon.tsx b/packages/paste-website/src/components/icons/NewHeartDoodleIcon.tsx
new file mode 100644
index 0000000000..fbf3fc066e
--- /dev/null
+++ b/packages/paste-website/src/components/icons/NewHeartDoodleIcon.tsx
@@ -0,0 +1,36 @@
+import { useUID } from "@twilio-paste/uid-library";
+import * as React from "react";
+
+export interface HeartDoodleIconProps {
+ className?: string;
+ size?: number;
+ color?: string;
+ title?: string;
+ decorative?: boolean;
+}
+
+const HeartDoodleIcon = React.memo(
+ ({ title = "Heart Doodle Icon", decorative = true, className, color, size }: HeartDoodleIconProps) => {
+ const titleId = useUID();
+ return (
+
+
+
+ );
+ },
+);
+
+export default HeartDoodleIcon;
diff --git a/packages/paste-website/src/components/site-wrapper-new/SiteBody.tsx b/packages/paste-website/src/components/site-wrapper-new/SiteBody.tsx
new file mode 100644
index 0000000000..cba90c6e04
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/SiteBody.tsx
@@ -0,0 +1,140 @@
+import { Box } from "@twilio-paste/box";
+import { LogoPasteIcon } from "@twilio-paste/icons/esm/LogoPasteIcon";
+import {
+ Sidebar,
+ SidebarBody,
+ SidebarCollapseButton,
+ SidebarFooter,
+ SidebarHeader,
+ SidebarHeaderIconButton,
+ SidebarHeaderLabel,
+ SidebarPushContentWrapper,
+} from "@twilio-paste/sidebar";
+import { type CSSObject, StylingGlobals } from "@twilio-paste/styling-library";
+import { useWindowSize } from "@twilio-paste/utils";
+import { useRouter } from "next/router";
+import * as React from "react";
+
+import {
+ PASTE_DOCS_CONTENT_AREA,
+ PASTE_DOCS_SIDEBAR_NAV,
+ PASTE_DOCS_TOPBAR,
+ SITE_TOPBAR_HEIGHT,
+ TOKEN_LIST_PAGE_REGEX,
+ TOKEN_STICKY_FILTER_HEIGHT,
+} from "../../constants";
+import { SiteMain } from "./SiteMain";
+import { SidebarNavigation } from "./sidebar/SidebarNavigation";
+import { SiteFooter } from "./site-footer";
+import { SiteHeader } from "./site-header";
+
+// height of the topbar plus a little extra whitespace
+const defaultScrollOffset = `calc(${SITE_TOPBAR_HEIGHT}px + 24px)`;
+
+const GlobalScrollBehaviourStyles = (scrollOffset = defaultScrollOffset): CSSObject => ({
+ html: {
+ scrollBehavior: "smooth",
+ // compensate for the sticky topbar, this offset allows for the jump to links to keep the headings in view when you jump to the section
+ scrollPaddingTop: scrollOffset,
+ },
+});
+
+export const SiteBody: React.FC = ({ children }) => {
+ const { breakpointIndex } = useWindowSize();
+ const router = useRouter();
+ // sidebar is not collapsed by default, most common use case for desktop viewing
+ const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
+ const [mounted, setMounted] = React.useState(false);
+ const mobileSidebarCloseButton = React.useRef(null);
+
+ /**
+ * Handle responsive sidebar collapse state for small screen sizes and initial render for SSR
+ */
+ React.useEffect(() => {
+ // no breakpoints? No Javascript, do nothing, this is initial render
+ if (breakpointIndex === undefined) {
+ return;
+ }
+ // if the screen is small, collapse the sidebar on mount
+ if (breakpointIndex === 0) {
+ setSidebarCollapsed(true);
+ } else {
+ setSidebarCollapsed(false);
+ }
+ // track mounted state to help prevent flash of content on SSR
+ setMounted(true);
+ }, [breakpointIndex]);
+
+ /**
+ * Handle user focus on mobile sidebar open, place them in the overlaid Sidebar
+ */
+ React.useEffect(() => {
+ if (!sidebarCollapsed && mobileSidebarCloseButton.current) {
+ mobileSidebarCloseButton.current.focus();
+ }
+ }, [sidebarCollapsed]);
+
+ /**
+ * The tokens list page an extra sticky filter bar so the jump to scroll offset needs an extra offset.
+ * its the masthead height + the sticky filter height
+ */
+
+ let scrollOffset = defaultScrollOffset;
+
+ if (breakpointIndex !== undefined && TOKEN_LIST_PAGE_REGEX.test(router.pathname)) {
+ scrollOffset = `calc(${TOKEN_STICKY_FILTER_HEIGHT[breakpointIndex]}px + ${defaultScrollOffset})`;
+ }
+
+ return (
+ <>
+
+ {/**
+ * No judgement zone
+ * To successfully handle a single sidebar that is responsive but also adaptive in it's state handling
+ * We need to handle initial render for both mobile and desktop views because the sidebar has inverse initial state
+ * - On mobile, the sidebar should start collapsed
+ * - On desktop, the sidebar should start expanded
+ * We then do a little trickery with opocity and responsive values to stop you seeing the state change flash
+ * when it's not mounted we use a transparent sidebar on small screens, after it's mounted we switch to visible,
+ * but transition it and delay the transistion start. For desktop, we start visible and never transition.
+ */}
+
+
+
+
+
+
+ Twilio Paste
+
+
+
+
+ {breakpointIndex === 0 && (
+
+ setSidebarCollapsed(!sidebarCollapsed)}
+ />
+
+ )}
+
+
+
+
+ {children}
+
+
+ >
+ );
+};
diff --git a/packages/paste-website/src/components/site-wrapper-new/SiteMain.tsx b/packages/paste-website/src/components/site-wrapper-new/SiteMain.tsx
new file mode 100644
index 0000000000..957b9f6d30
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/SiteMain.tsx
@@ -0,0 +1,11 @@
+import { Box } from "@twilio-paste/box";
+import type { HTMLPasteProps } from "@twilio-paste/types";
+import * as React from "react";
+
+export const SiteMain: React.FC>> = ({ children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/paste-website/src/components/site-wrapper-new/index.tsx b/packages/paste-website/src/components/site-wrapper-new/index.tsx
new file mode 100644
index 0000000000..2ec867930d
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/index.tsx
@@ -0,0 +1,23 @@
+import * as React from "react";
+
+import { NavigationContext } from "../../context/NavigationContext";
+import type { NavigationQuery } from "../../context/NavigationContext";
+import { SiteBody } from "./SiteBody";
+
+export interface SiteWrapperProps {
+ navigationData: NavigationQuery;
+}
+
+const SiteWrapper: React.FC> = ({ children, navigationData }) => {
+ return (
+ /*
+ * TODO: move to server components and app directory to remove this provider.
+ * Removes the need for page level getNavigationData() in getStaticProps. Do it once for the whole app, not everypage
+ */
+
+ {children}
+
+ );
+};
+
+export { SiteWrapper };
diff --git a/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarAnchor.tsx b/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarAnchor.tsx
new file mode 100644
index 0000000000..68cf171a1d
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarAnchor.tsx
@@ -0,0 +1,35 @@
+import { SidebarNavigationItem } from "@twilio-paste/sidebar";
+import Link from "next/link";
+import * as React from "react";
+
+import { useLocationPathname } from "../../../utils/RouteUtils";
+
+interface SidebarAnchorProps {
+ children: NonNullable;
+ selected?: boolean;
+ href: string;
+ onClick?: () => void;
+}
+
+const SidebarAnchor: React.FC> = ({
+ children,
+ href,
+ onClick,
+ selected,
+ ...props
+}) => {
+ const pathname = useLocationPathname();
+ const pathnameWithoutTrailingSlash = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
+ const itemSelected = selected || pathnameWithoutTrailingSlash === href;
+
+ return (
+
+ {/* @ts-expect-error using nextjs passHref means that we can't satisfy the nav item href required prop */}
+
+ {children}
+
+
+ );
+};
+
+export { SidebarAnchor };
diff --git a/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarNavigation.tsx b/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarNavigation.tsx
new file mode 100644
index 0000000000..733db4d37a
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/sidebar/SidebarNavigation.tsx
@@ -0,0 +1,482 @@
+import { LinkExternalIcon } from "@twilio-paste/icons/esm/LinkExternalIcon";
+import {
+ SidebarNavigation,
+ SidebarNavigationDisclosure,
+ SidebarNavigationDisclosureContent,
+ type SidebarNavigationDisclosureContentProps,
+ SidebarNavigationDisclosureHeading,
+ type SidebarNavigationDisclosureHeadingProps,
+ SidebarNavigationDisclosureHeadingWrapper,
+ SidebarNavigationSeparator,
+ useSidebarNavigationDisclosureState,
+} from "@twilio-paste/sidebar";
+import kebabCase from "lodash/kebabCase";
+import * as React from "react";
+
+import { SidebarCategoryRoutes } from "../../../constants";
+import { useNavigationContext } from "../../../context/NavigationContext";
+import { event } from "../../../lib/gtag";
+import { getNormalizedNavigationData } from "../../../utils/DataUtils";
+import { useLocationPathname } from "../../../utils/RouteUtils";
+import { alphabetizeComponents } from "../../../utils/componentFilters";
+import { SidebarAnchor } from "./SidebarAnchor";
+
+const CY_BASE = "sidebar-disclosure";
+
+const NavigationDisclosure: React.FC<
+ React.PropsWithChildren<{
+ children: SidebarNavigationDisclosureContentProps["children"];
+ categoryRoute: typeof SidebarCategoryRoutes[keyof typeof SidebarCategoryRoutes];
+ buttonText: string;
+ onClick?: SidebarNavigationDisclosureHeadingProps["onClick"];
+ }>
+> = ({ children, categoryRoute, buttonText, onClick }) => {
+ const pathname = useLocationPathname();
+ const disclosure = useSidebarNavigationDisclosureState({
+ visible: pathname.startsWith(categoryRoute),
+ });
+ const buttonAttribute = `${CY_BASE}-button-${kebabCase(buttonText)}`;
+ const contentAttribute = `${CY_BASE}-content-${kebabCase(buttonText)}`;
+
+ return (
+
+
+
+ {buttonText}
+
+
+ {children}
+
+ );
+};
+
+const SiteSidebarNavigation = (): JSX.Element => {
+ // TODO: move to a server component in the App directory
+ const navigationData = useNavigationContext();
+ const pathname = useLocationPathname();
+
+ // take airtable feature data and mutate it into navigation data
+ const { allPasteComponent, allPasteLayout, allPastePrimitive, allPastePattern, allPastePageTemplate } =
+ getNormalizedNavigationData(navigationData);
+
+ const allComponentSidebarItems = [...allPasteComponent, ...allPasteLayout];
+ const filteredComponentSidebarItems = allComponentSidebarItems.sort(alphabetizeComponents);
+
+ const filteredPrimitives = allPastePrimitive?.sort(alphabetizeComponents);
+
+ return (
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-introduction",
+ label: "Introduction",
+ })
+ }
+ >
+ About Paste
+
+
+ Design guidelines
+
+
+
+ Quick start
+
+ Manual installation
+
+
+
+ Components
+ Icons
+ Patterns
+
+ Working with us
+
+ Accessibility
+
+
+ Colors
+
+ event({
+ category: "Left Navigation",
+ action: "click-content",
+ label: "Content",
+ })
+ }
+ >
+ Overview
+ Content checklist
+ Voice and tone
+
+ Product style guide
+
+ Word list
+
+
+ Data visualization
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-illustrations",
+ label: "Illustrations",
+ })
+ }
+ >
+ Illustrations
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-localization",
+ label: "Localization",
+ })
+ }
+ >
+ Localization
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-spacing-and-layout",
+ label: "Spacing and layout",
+ })
+ }
+ >
+ Spacing and layout
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-typography",
+ label: "Typography",
+ })
+ }
+ >
+ Typography
+
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-patterns",
+ label: "Patterns",
+ })
+ }
+ >
+ Overview
+ {allPastePattern.map(({ name, slug }: { [key: string]: string }) => (
+
+ {name}
+
+ ))}
+
+
+ Overview
+ {allPastePageTemplate.map(({ name, slug }: { [key: string]: string }) => (
+
+ {name}
+
+ ))}
+
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-components",
+ label: "Components",
+ })
+ }
+ >
+ Overview
+ {filteredComponentSidebarItems.map(({ name, slug }: { [key: string]: string }) => {
+ const categoryRoute = `${SidebarCategoryRoutes.COMPONENTS}/${slug}`;
+ const selected = pathname.endsWith(categoryRoute) || pathname.includes(`${categoryRoute}/`);
+ if (name === "Icon") {
+ return (
+
+ event({
+ category: "Left Navigation",
+ action: `click-${name}`,
+ label: name,
+ })
+ }
+ >
+ {name} list
+
+ Usage
+
+
+ );
+ }
+ if (name === "Combobox") {
+ return (
+
+ event({
+ category: "Left Navigation",
+ action: `click-${name}`,
+ label: name,
+ })
+ }
+ >
+
+ Singleselect
+
+
+ Multiselect
+
+
+ );
+ }
+ if (name === "Multiselect Combobox") {
+ return null;
+ }
+ if (name === "Status") {
+ return (
+
+ event({
+ category: "Left Navigation",
+ action: `click-${name}`,
+ label: name,
+ })
+ }
+ >
+
+ Status Badge
+
+
+ Status Menu
+
+
+ );
+ }
+ if (name === "Sidebar") {
+ return (
+
+ event({
+ category: "Left Navigation",
+ action: `click-${name}`,
+ label: name,
+ })
+ }
+ >
+
+ Sidebar Container
+
+
+ Sidebar Navigation
+
+
+ );
+ }
+ if (name === "Sidebar Navigation") {
+ return null;
+ }
+ return (
+
+ {name}
+
+ );
+ })}
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-primitives",
+ label: "Primitives",
+ })
+ }
+ >
+ Overview
+ {filteredPrimitives.map(({ name, slug }: { [key: string]: string }) => {
+ const selected = pathname.includes(`${SidebarCategoryRoutes.PRIMITIVES}/${slug}`);
+ return (
+
+ {name}
+
+ );
+ })}
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-design-tokens",
+ label: "Design Tokens",
+ })
+ }
+ >
+ Overview
+ Token list
+
+ Design tokens package
+
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-core",
+ label: "Core",
+ })
+ }
+ >
+ Paste core
+ Core changelog
+ Upgrade guide
+
+ Overview
+ Code Editor
+ Codemods
+
+ Data visualization
+
+
+ Clipboard Copy
+
+
+
+ SVG-to-React
+
+
+ UID library
+
+ VS Code Plugin
+
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-theme",
+ label: "Theme",
+ })
+ }
+ >
+ Overview
+ Dark theme
+ Changing the theme
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-customization",
+ label: "Customization",
+ })
+ }
+ >
+ Overview
+
+ Customization Provider
+
+
+ Customizing themes
+
+
+ Customizing components
+
+
+ Composing custom UI with tokens
+
+
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-blog",
+ label: "Blog",
+ })
+ }
+ >
+ Blog
+
+
+ event({
+ category: "Left Navigation",
+ action: "click-roadmap",
+ label: "Roadmap",
+ })
+ }
+ >
+ Roadmap
+
+
+ );
+};
+
+export { SiteSidebarNavigation as SidebarNavigation };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterHeader.tsx b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterHeader.tsx
new file mode 100644
index 0000000000..94a4b84b62
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterHeader.tsx
@@ -0,0 +1,25 @@
+import { Box } from "@twilio-paste/box";
+import { useTheme } from "@twilio-paste/theme";
+
+import HeartDoodleIcon from "../../icons/NewHeartDoodleIcon";
+
+const SiteFooterHeader = (): JSX.Element => {
+ const theme = useTheme();
+ return (
+
+
+
+ );
+};
+
+export { SiteFooterHeader };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterIllustration.tsx b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterIllustration.tsx
new file mode 100644
index 0000000000..e99820202d
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterIllustration.tsx
@@ -0,0 +1,14 @@
+import { Box } from "@twilio-paste/box";
+import Image from "next/image";
+
+import FooterImg from "../../../assets/illustrations/footer_img.svg";
+
+const SiteFooterIllustration = (): JSX.Element => {
+ return (
+
+
+
+ );
+};
+
+export { SiteFooterIllustration };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterNav.tsx b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterNav.tsx
new file mode 100644
index 0000000000..87dbe5ece6
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-footer/SiteFooterNav.tsx
@@ -0,0 +1,207 @@
+import { Anchor } from "@twilio-paste/anchor";
+import { Box } from "@twilio-paste/box";
+import { Stack } from "@twilio-paste/stack";
+import { Text } from "@twilio-paste/text";
+import { useTheme } from "@twilio-paste/theme";
+
+import { FIGMA_PROFILE_URL, REMIX_DOMAIN, STORYBOOK_DOMAIN } from "../../../constants";
+import { event } from "../../../lib/gtag";
+import TwilioLogo from "../../icons/TwilioLogo";
+import { SiteFooterHeader } from "./SiteFooterHeader";
+
+const SiteFooterNav = (): JSX.Element => {
+ const theme = useTheme();
+ return (
+
+
+
+
+
+
+ Learn more
+
+
+ event({
+ category: "Footer",
+ action: "click-about",
+ label: "About",
+ })
+ }
+ >
+ About
+
+
+ event({
+ category: "Footer",
+ action: "click-design-guidelines",
+ label: "Design guidelines",
+ })
+ }
+ >
+ Design guidelines
+
+
+ event({
+ category: "Footer",
+ action: "click-quickstart",
+ label: "Quick Start for engineers",
+ })
+ }
+ >
+ Quick start for engineers
+
+
+ event({
+ category: "Footer",
+ action: "click-our-roadmap",
+ label: "Roadmap",
+ })
+ }
+ >
+ Roadmap
+
+
+
+
+
+
+ Get help
+
+
+ event({
+ category: "Footer",
+ action: "click-github-discussions",
+ label: "Github discussions",
+ })
+ }
+ >
+ Ask a question
+
+
+ event({
+ category: "Footer",
+ action: "click-report-a-bug",
+ label: "Report a bug",
+ })
+ }
+ >
+ Report a bug
+
+
+ event({
+ category: "Footer",
+ action: "click-how-we-work",
+ label: "How we work",
+ })
+ }
+ >
+ How we work
+
+
+
+
+
+
+ Build with Paste
+
+
+ event({
+ category: "Footer",
+ action: "click-github",
+ label: "Github",
+ })
+ }
+ >
+ Github
+
+
+ event({
+ category: "Footer",
+ action: "click-figma",
+ label: "Figma",
+ })
+ }
+ >
+ Figma
+
+
+ event({
+ category: "Footer",
+ action: "click-storybook",
+ label: "Storybook",
+ })
+ }
+ >
+ Storybook
+
+
+ event({
+ category: "Footer",
+ action: "click-remix",
+ label: "Theme Designer",
+ })
+ }
+ >
+ Paste Remix
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export { SiteFooterNav };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-footer/index.tsx b/packages/paste-website/src/components/site-wrapper-new/site-footer/index.tsx
new file mode 100644
index 0000000000..356c2f3bab
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-footer/index.tsx
@@ -0,0 +1,32 @@
+import { Box } from "@twilio-paste/box";
+
+import { HOMEPAGE_SITE_CONTENT_MAX_WIDTH } from "../../../constants";
+import { SiteFooterIllustration } from "./SiteFooterIllustration";
+import { SiteFooterNav } from "./SiteFooterNav";
+
+const SiteFooter: React.FC = () => {
+ return (
+
+
+
+
+
+
+ );
+};
+export { SiteFooter };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/DarkModeToggle.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/DarkModeToggle.tsx
new file mode 100644
index 0000000000..b14275ff7b
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/DarkModeToggle.tsx
@@ -0,0 +1,68 @@
+import { DarkModeIcon } from "@twilio-paste/icons/esm/DarkModeIcon";
+import { LightModeIcon } from "@twilio-paste/icons/esm/LightModeIcon";
+import { Menu, MenuButton, MenuItemRadio, useMenuState } from "@twilio-paste/menu";
+import { ScreenReaderOnly } from "@twilio-paste/screen-reader-only";
+
+import { useDarkModeContext } from "../../../context/DarkModeContext";
+import { event } from "../../../lib/gtag";
+
+export const DarkModeToggle = (): JSX.Element => {
+ const { theme, toggleMode } = useDarkModeContext();
+ const menu = useMenuState();
+
+ const handleClick = (category: string, action: string, label: string): void => {
+ menu.hide();
+ toggleMode();
+ event({
+ category,
+ action,
+ label,
+ });
+ };
+
+ return (
+ <>
+
+ event({
+ category: "Top Navigation",
+ action: "click-dark-mode",
+ label: "Switch",
+ })
+ }
+ >
+ {theme === "twilio" ? (
+
+ ) : (
+
+ )}
+ Switch the site theme
+
+
+ >
+ );
+};
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/FigmaButton.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/FigmaButton.tsx
new file mode 100644
index 0000000000..0bd23b7053
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/FigmaButton.tsx
@@ -0,0 +1,43 @@
+import { secureExternalLink } from "@twilio-paste/anchor";
+import { Box } from "@twilio-paste/box";
+import React from "react";
+
+import { FIGMA_PROFILE_URL } from "../../../constants";
+import { FigmaIcon } from "../../icons/FigmaIcon";
+
+// eslint-disable-next-line no-empty-pattern
+const FigmaButton = React.forwardRef(({}, ref) => {
+ return (
+
+
+ Figma
+
+ );
+});
+
+FigmaButton.displayName = "FigmaButton";
+
+export { FigmaButton };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/HamburgerToggle.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/HamburgerToggle.tsx
new file mode 100644
index 0000000000..7503fc7146
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/HamburgerToggle.tsx
@@ -0,0 +1,69 @@
+import { Box } from "@twilio-paste/box";
+import type { BoxProps } from "@twilio-paste/box";
+import type { TextColorOptions } from "@twilio-paste/style-props";
+import { styled, themeGet } from "@twilio-paste/styling-library";
+import * as React from "react";
+
+interface HamburgerToggleProps {
+ toggled: boolean;
+ color: TextColorOptions;
+}
+
+interface StyledPattieProps extends Pick {
+ color: TextColorOptions;
+}
+
+const StyledPattie = styled.span`
+ position: absolute;
+ width: ${themeGet("space.space50")};
+ height: 3px;
+ border-radius: ${themeGet("radii.borderRadius30")};
+ transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out;
+ transform-origin: left center;
+ background-color: ${({ color }) => themeGet(`textColors.${color}`)};
+ top: ${({ top }) => top};
+ left: ${({ left }) => left};
+ transform: ${({ transform }) => transform};
+ opacity: ${({ opacity }) => opacity};
+`;
+
+const HamburgerToggle = React.forwardRef(({ toggled, color }, ref) => {
+ return (
+
+
+
+
+
+
+ );
+});
+
+export { HamburgerToggle };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderLogo.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderLogo.tsx
new file mode 100644
index 0000000000..cc5df23ff1
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderLogo.tsx
@@ -0,0 +1,100 @@
+import { Box } from "@twilio-paste/box";
+import { MediaBody, MediaFigure, MediaObject } from "@twilio-paste/media-object";
+import { styled, themeGet } from "@twilio-paste/styling-library";
+import { Text } from "@twilio-paste/text";
+import { useTheme } from "@twilio-paste/theme";
+import Link from "next/link";
+import * as React from "react";
+
+import { event } from "../../../lib/gtag";
+import { PasteIcon } from "../../icons/PasteIcon";
+import { PasteIconPride } from "../../icons/PasteIconPride";
+
+const LogoLink = styled(Link)`
+ position: relative;
+ text-decoration: none;
+ color: ${themeGet("textColors.colorTextInverse")};
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ &:focus {
+ outline: none;
+ box-shadow: ${themeGet("shadows.shadowFocus")};
+ border-radius: ${themeGet("radii.borderRadius10")};
+ }
+`;
+
+interface SiteHeaderLogoProps {
+ title?: string;
+ subtitle?: string;
+}
+
+/*
+ * Note: 'subtitle' isn't passed for the mobile view, so we use that fact
+ * to render different sizes and spacing in mobile
+ */
+const SiteHeaderLogo: React.FC> = ({ title, subtitle }) => {
+ const theme = useTheme();
+ const [logoOpacity, setLogoOpacity] = React.useState(1);
+ const [hoverOpacity, setHoverOpacity] = React.useState(0);
+ const logoTransition = "ease-out 350ms";
+
+ return (
+
+
+ event({
+ category: "Top Navigation",
+ action: "click-paste-logo",
+ label: "Paste logo",
+ })
+ }
+ onMouseEnter={() => {
+ setLogoOpacity(0);
+ setHoverOpacity(1);
+ }}
+ onMouseLeave={() => {
+ setLogoOpacity(1);
+ setHoverOpacity(0);
+ }}
+ >
+
+
+
+
+
+
+
+ {title}
+
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+
+
+
+
+ );
+};
+
+export { SiteHeaderLogo };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderSearch.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderSearch.tsx
new file mode 100644
index 0000000000..a9022fba50
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/SiteHeaderSearch.tsx
@@ -0,0 +1,74 @@
+import { Box } from "@twilio-paste/box";
+import { Button } from "@twilio-paste/button";
+import { SearchIcon } from "@twilio-paste/icons/esm/SearchIcon";
+import { InlineCode } from "@twilio-paste/inline-code";
+import { ScreenReaderOnly } from "@twilio-paste/screen-reader-only";
+import { Text } from "@twilio-paste/text";
+import * as React from "react";
+import { useHotkeys } from "react-hotkeys-hook";
+
+import { SiteSearch } from "../../site-search";
+
+const SiteHeaderSearch: React.FC = () => {
+ const [isOpen, setIsOpen] = React.useState(false);
+
+ const onOpen = (): void => {
+ setIsOpen(true);
+ };
+
+ const onClose = (): void => {
+ setIsOpen(false);
+ };
+
+ useHotkeys("mod+k", onOpen);
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export { SiteHeaderSearch };
diff --git a/packages/paste-website/src/components/site-wrapper-new/site-header/index.tsx b/packages/paste-website/src/components/site-wrapper-new/site-header/index.tsx
new file mode 100644
index 0000000000..bc6b1ba4a5
--- /dev/null
+++ b/packages/paste-website/src/components/site-wrapper-new/site-header/index.tsx
@@ -0,0 +1,55 @@
+import { Box } from "@twilio-paste/box";
+import { Button } from "@twilio-paste/button";
+import { Topbar, TopbarActions } from "@twilio-paste/topbar";
+import { useWindowSize } from "@twilio-paste/utils";
+import * as React from "react";
+import GitHubButton from "react-github-button";
+import "react-github-button/assets/style.css";
+
+import { PASTE_DOCS_TOPBAR } from "../../../constants";
+import { ContactUsMenu } from "../../ContactUsMenu";
+import { DarkModeToggle } from "./DarkModeToggle";
+import { FigmaButton } from "./FigmaButton";
+import { HamburgerToggle } from "./HamburgerToggle";
+import { SiteHeaderLogo } from "./SiteHeaderLogo";
+import { SiteHeaderSearch } from "./SiteHeaderSearch";
+
+export const SiteHeader: React.FC<{
+ sidebarMobileCollapsed: boolean;
+ setSidebarMobileCollapsed: (collapsed: boolean) => void;
+}> = ({ sidebarMobileCollapsed, setSidebarMobileCollapsed }): JSX.Element => {
+ const { breakpointIndex } = useWindowSize();
+ return (
+
+
+
+
+ {breakpointIndex === 0 ? (
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
diff --git a/packages/paste-website/src/constants.ts b/packages/paste-website/src/constants.ts
index b4fa9d872c..faf02fb74f 100644
--- a/packages/paste-website/src/constants.ts
+++ b/packages/paste-website/src/constants.ts
@@ -5,6 +5,7 @@ export const PASTE_THEME_ALERT_HEIGHT = 54;
export const SITE_TOPBAR_HEIGHT = 77;
export const SITE_BREAKPOINTS = ["768px", "1024px", "1220px", "1880px"];
export const SITE_CONTENT_MAX_WIDTH = "1440px";
+export const HOMEPAGE_SITE_CONTENT_MAX_WIDTH = "1232px";
// Used to 'bleed' sections together. GettingStarted+Popular
export const HOMEPAGE_SECTION_OVERFLOW_OFFSET = 150;
diff --git a/packages/paste-website/src/pages/new/index.tsx b/packages/paste-website/src/pages/new/index.tsx
index b60f1781d4..549f13e1ec 100644
--- a/packages/paste-website/src/pages/new/index.tsx
+++ b/packages/paste-website/src/pages/new/index.tsx
@@ -8,12 +8,12 @@ import { Accessibility } from "../../components/homepage/Accessibility";
import { CommunityOfBuilders } from "../../components/homepage/CommunityOfBuilders";
import { DesignEfficiency } from "../../components/homepage/DesignEfficiency";
import { ForTwilioCustomers } from "../../components/homepage/ForTwilioCustomers";
-import { NewHomeHero } from "../../components/homepage/NewHomeHero";
+import { HomeHero } from "../../components/homepage/NewHomeHero";
import { NewSection } from "../../components/homepage/NewSection";
import { Templates } from "../../components/homepage/Templates";
import { Themeable } from "../../components/homepage/Themeable";
import { WeDoTheThinking } from "../../components/homepage/WeDoTheThinking";
-import { SiteWrapper } from "../../components/site-wrapper";
+import { SiteWrapper } from "../../components/site-wrapper-new";
import { SiteMetaDefaults } from "../../constants";
import { getNavigationData } from "../../utils/api";
import type { Feature } from "../../utils/api";
@@ -33,10 +33,10 @@ const NewHomePage = ({ navigationData }: InferGetStaticPropsType
{SiteMetaDefaults.TITLE}
-
-
+
+
-
+
@@ -60,5 +60,4 @@ export const getStaticProps: GetStaticProps<{ navigationData: Feature[] }> = asy
},
};
};
-
export default NewHomePage;