first commit
This commit is contained in:
67
calcom/packages/ui/components/meta/Meta.tsx
Normal file
67
calcom/packages/ui/components/meta/Meta.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import Head from "next/head";
|
||||
import { createContext, useContext, useState, useEffect } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
|
||||
type MetaType = {
|
||||
title: string;
|
||||
description: string;
|
||||
backButton?: boolean;
|
||||
CTA?: ReactNode;
|
||||
borderInShellHeader?: boolean;
|
||||
};
|
||||
|
||||
const initialMeta: MetaType = {
|
||||
title: "",
|
||||
description: "",
|
||||
backButton: false,
|
||||
CTA: null,
|
||||
borderInShellHeader: true,
|
||||
};
|
||||
|
||||
const MetaContext = createContext({
|
||||
meta: initialMeta,
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setMeta: (_newMeta: Partial<MetaType>) => {},
|
||||
});
|
||||
|
||||
export function useMeta() {
|
||||
return useContext(MetaContext);
|
||||
}
|
||||
|
||||
export function MetaProvider({ children }: { children: ReactNode }) {
|
||||
const [value, setValue] = useState(initialMeta);
|
||||
const setMeta = (newMeta: Partial<MetaType>) => {
|
||||
setValue((v) => ({ ...v, ...newMeta }));
|
||||
};
|
||||
|
||||
return <MetaContext.Provider value={{ meta: value, setMeta }}>{children}</MetaContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this component is to simplify title and description handling.
|
||||
* Similarly to `next/head`'s `Head` component this allow us to update the metadata for a page
|
||||
* from any children, also exposes the metadata via the `useMeta` hook in case we need them
|
||||
* elsewhere (ie. on a Heading, Title, Subtitle, etc.)
|
||||
* @example <Meta title="Password" description="Manage settings for your account passwords" />
|
||||
*/
|
||||
export default function Meta({ title, description, backButton, CTA, borderInShellHeader }: MetaType) {
|
||||
const { setMeta, meta } = useMeta();
|
||||
|
||||
/* @TODO: maybe find a way to have this data on first render to prevent flicker */
|
||||
useEffect(() => {
|
||||
if (meta.title !== title || meta.description !== description || meta.CTA !== CTA) {
|
||||
setMeta({ title, description, backButton, CTA, borderInShellHeader });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [title, description, backButton, CTA]);
|
||||
|
||||
const title_ = `${title} | ${APP_NAME}`;
|
||||
return (
|
||||
<Head>
|
||||
<title>{title_}</title>
|
||||
<meta name="description" content={description} />
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
1
calcom/packages/ui/components/meta/index.ts
Normal file
1
calcom/packages/ui/components/meta/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Meta, MetaProvider, useMeta } from "./Meta";
|
||||
54
calcom/packages/ui/components/meta/meta.test.tsx
Normal file
54
calcom/packages/ui/components/meta/meta.test.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import Meta, { useMeta } from "./Meta";
|
||||
|
||||
vi.mock("./Meta", async () => {
|
||||
const actualMeta = (await vi.importActual("./Meta")) as object;
|
||||
const MockMeta = ({ title, description }: { title: string; description: string }) => (
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
type MetaProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
return {
|
||||
...actualMeta,
|
||||
default: MockMeta,
|
||||
useMeta: vi.fn(),
|
||||
MetaProvider: ({ children }: MetaProviderProps) => children,
|
||||
};
|
||||
});
|
||||
|
||||
describe("Meta Component", () => {
|
||||
test("Should render with default metadata", () => {
|
||||
(useMeta as jest.Mock).mockReturnValue({
|
||||
meta: { title: "", description: "", backButton: false, CTA: null },
|
||||
});
|
||||
|
||||
const { getByText } = render(<Meta title="Page Title" description="Page Description" />);
|
||||
|
||||
expect(getByText("Page Title")).toBeInTheDocument();
|
||||
expect(getByText("Page Description")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Should update metadata when props change", () => {
|
||||
(useMeta as jest.Mock).mockReturnValue({
|
||||
meta: { title: "", description: "", backButton: false, CTA: null },
|
||||
});
|
||||
|
||||
const { rerender, getByText } = render(<Meta title="Page Title" description="Page Description" />);
|
||||
|
||||
expect(getByText("Page Title")).toBeInTheDocument();
|
||||
expect(getByText("Page Description")).toBeInTheDocument();
|
||||
|
||||
rerender(<Meta title="New Title" description="New Description" />);
|
||||
|
||||
expect(getByText("New Title")).toBeInTheDocument();
|
||||
expect(getByText("New Description")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user