Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c26793f5 | ||
|
|
956f81fab3 | ||
|
|
1dcd54aab7 | ||
|
|
68d3d9d964 | ||
|
|
717a26f223 | ||
|
|
6818806c5e | ||
|
|
dd7f108f08 | ||
|
|
7e4d9e26c6 | ||
|
|
a7352cf587 | ||
|
|
5f8db0557c |
@@ -1,6 +1,5 @@
|
||||
/**/package.json @calcom/Foundation
|
||||
/apps/api/**/* @calcom/API
|
||||
/apps/ui-playground/**/* @calcom/Consumer @calcom/Foundation
|
||||
/apps/web/**/layout.tsx @calcom/Consumer @calcom/Foundation
|
||||
/apps/web/lib/csp.ts @calcom/Foundation
|
||||
/apps/web/lib/buildNonce.ts @calcom/Foundation
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
@@ -95,6 +95,8 @@ describe("Me Endpoints", () => {
|
||||
expect(responseBody.data.id).toEqual(user.id);
|
||||
expect(responseBody.data.email).toEqual(user.email);
|
||||
expect(responseBody.data.name).toEqual(user.name);
|
||||
expect(responseBody.data.avatarUrl).toEqual(user.avatarUrl);
|
||||
expect(responseBody.data.bio).toEqual(user.bio);
|
||||
expect(responseBody.data.timeFormat).toEqual(user.timeFormat);
|
||||
expect(responseBody.data.defaultScheduleId).toEqual(user.defaultScheduleId);
|
||||
expect(responseBody.data.weekStart).toEqual(user.weekStart);
|
||||
@@ -117,6 +119,8 @@ describe("Me Endpoints", () => {
|
||||
|
||||
expect(responseBody.data.id).toEqual(user.id);
|
||||
expect(responseBody.data.email).toEqual(user.email);
|
||||
expect(responseBody.data.avatarUrl).toEqual(user.avatarUrl);
|
||||
expect(responseBody.data.bio).toEqual(user.bio);
|
||||
expect(responseBody.data.timeFormat).toEqual(user.timeFormat);
|
||||
expect(responseBody.data.defaultScheduleId).toEqual(user.defaultScheduleId);
|
||||
expect(responseBody.data.weekStart).toEqual(user.weekStart);
|
||||
@@ -151,13 +155,13 @@ describe("Me Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should not update user associated with access token given invalid time format", async () => {
|
||||
const bodyWithIncorrectTimeFormat: UpdateManagedUserInput = { timeFormat: 100 as any };
|
||||
const bodyWithIncorrectTimeFormat = { timeFormat: 100 };
|
||||
|
||||
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectTimeFormat).expect(400);
|
||||
});
|
||||
|
||||
it("should not update user associated with access token given invalid week start", async () => {
|
||||
const bodyWithIncorrectWeekStart: UpdateManagedUserInput = { weekStart: "waba luba dub dub" as any };
|
||||
const bodyWithIncorrectWeekStart = { weekStart: "waba luba dub dub" };
|
||||
|
||||
return request(app.getHttpServer()).patch("/v2/me").send(bodyWithIncorrectWeekStart).expect(400);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,14 @@ export class MeOutput {
|
||||
@DocsProperty({ type: String, nullable: true })
|
||||
name!: string | null;
|
||||
|
||||
@IsString()
|
||||
@DocsProperty({ type: String, nullable: true })
|
||||
avatarUrl!: string | null;
|
||||
|
||||
@IsString()
|
||||
@DocsProperty({ type: String, nullable: true })
|
||||
bio!: string | null;
|
||||
|
||||
@IsInt()
|
||||
@DocsProperty()
|
||||
timeFormat!: number;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# Source maps
|
||||
*.source/*
|
||||
@@ -1,36 +0,0 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
@@ -1,9 +0,0 @@
|
||||
import { HomeLayout } from "fumadocs-ui/layouts/home";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { baseOptions } from "../layout.config";
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
// @ts-expect-error weird type check
|
||||
return <HomeLayout {...baseOptions}>{children}</HomeLayout>;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="font-cal -mt-16 flex min-h-screen w-screen items-center justify-center border pt-16 text-center ">
|
||||
<div>
|
||||
<h1 className="text-7xl leading-none md:text-8xl">
|
||||
Open UI for absolutely
|
||||
<br /> everyone
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { componentSource } from "@/app/source";
|
||||
import { createSearchAPI } from "fumadocs-core/search/server";
|
||||
|
||||
const indexes = [componentSource].flatMap((src) =>
|
||||
src.getPages().map((page) => ({
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
structuredData: page.data.structuredData,
|
||||
id: page.url,
|
||||
url: page.url,
|
||||
}))
|
||||
);
|
||||
|
||||
export const { GET } = createSearchAPI("advanced", {
|
||||
indexes,
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
};
|
||||
export const Icon: React.FC<PropsWithChildren<Props>> = (props) => {
|
||||
return (
|
||||
<div className="text-gray-12 flex flex-col items-center justify-center gap-4">
|
||||
<div className="size-12 border-gray-5 bg-gray-3 flex aspect-square items-center justify-center rounded-lg border ">
|
||||
{props.children}
|
||||
</div>
|
||||
<span className="font-mono text-sm">
|
||||
{"<"}
|
||||
{props.name}
|
||||
{"/>"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Icon.displayName = "Icon";
|
||||
@@ -1,57 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { type PropsWithChildren, useState } from "react";
|
||||
|
||||
import className from "@calcom/ui/classNames";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
type Props = {
|
||||
customCodeSnippet?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const RenderComponentWithSnippet: React.FC<PropsWithChildren<Props>> = (props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
// const snippet =
|
||||
// props.customCodeSnippet ??
|
||||
// reactElementToJSXString(props.children, {
|
||||
// showFunctions: true,
|
||||
// useBooleanShorthandSyntax: true,
|
||||
|
||||
// displayName: (node) => {
|
||||
// // @ts-ignore
|
||||
// return node?.type.displayName ?? node?.type ?? "Unknown";
|
||||
// },
|
||||
// });
|
||||
return (
|
||||
<div className="border-gray-6 bg-default rounded-lg border">
|
||||
<div className={className("not-prose p-8 xl:p-12", props.className)}>{props.children}</div>
|
||||
|
||||
<div className="bg-gray-3 border-gray-6 flex items-center justify-start border-b border-t p-2">
|
||||
<Button
|
||||
color="minimal"
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}>
|
||||
Code (WIP)
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={className(
|
||||
"bg-gray-2 max-h-96 w-full overflow-y-scroll transition-all",
|
||||
open ? "block" : "hidden"
|
||||
)}>
|
||||
<div className="flex items-start">
|
||||
{/* <pre className="py-0 text-gray-8 text-right">
|
||||
{snippet
|
||||
.split("\n")
|
||||
.map((_line, i) => i + 1)
|
||||
.join("\n")}
|
||||
</pre>
|
||||
<pre className="py-0 text-gray-12 w-full">{snippet}</pre> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export const Row: React.FC<PropsWithChildren> = (props) => {
|
||||
return <div className="flex w-full items-center justify-between gap-8">{props.children}</div>;
|
||||
};
|
||||
|
||||
Row.displayName = "Row";
|
||||
@@ -1,56 +0,0 @@
|
||||
import { componentSource } from "@/app/source";
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { createTypeTable } from "fumadocs-typescript/ui";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
||||
const params = await props.params;
|
||||
|
||||
const page = componentSource.getPage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
const { AutoTypeTable } = createTypeTable();
|
||||
|
||||
const MDX = page.data.body;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* @ts-expect-error idk why fumadocs doersnt like this */}
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<Toaster />
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
|
||||
<DocsBody>
|
||||
<TooltipProvider>
|
||||
{/* @ts-expect-error idk why fumadocs doersnt like this */}
|
||||
<MDX components={{ ...defaultMdxComponents, AutoTypeTable }} />
|
||||
</TooltipProvider>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return componentSource.generateParams();
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ slug?: string[] }> }) {
|
||||
const page = componentSource.getPage((await params).slug);
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
} satisfies Metadata;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { componentSource } from "@/app/source";
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { DocsLayout } from "fumadocs-ui/layouts/notebook";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { IconSprites } from "@calcom/ui/components/icon";
|
||||
|
||||
import { baseOptions } from "../layout.config";
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout tree={componentSource.pageTree} {...baseOptions}>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
<IconSprites />
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
import type { HomeLayoutProps } from "fumadocs-ui/layouts/home";
|
||||
|
||||
/**
|
||||
* Shared layout configurations
|
||||
*
|
||||
* you can configure layouts individually from:
|
||||
* Home Layout: app/(home)/layout.tsx
|
||||
* Docs Layout: app/docs/layout.tsx
|
||||
*/
|
||||
export const baseOptions: HomeLayoutProps = {
|
||||
nav: {
|
||||
title: "Cal.com",
|
||||
},
|
||||
githubUrl: "https://github.com/calcom/cal.com",
|
||||
links: [
|
||||
{
|
||||
text: "Design",
|
||||
url: "/design",
|
||||
active: "nested-url",
|
||||
},
|
||||
{
|
||||
text: "GitHub",
|
||||
url: "https://github.com/calcom/cal.com",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { RootProvider } from "fumadocs-ui/provider";
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
import "./global.css";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>{`
|
||||
:root {
|
||||
--font-inter: ${inter.style.fontFamily.replace(/\'/g, "")};
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body className={`${inter.variable} antialiased`}>
|
||||
<RootProvider>{children}</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { components, docs, meta } from "@/.source";
|
||||
import { loader } from "fumadocs-core/source";
|
||||
import { createMDXSource } from "fumadocs-mdx";
|
||||
|
||||
export const source = loader({
|
||||
baseUrl: "/docs",
|
||||
source: createMDXSource(docs, meta),
|
||||
});
|
||||
|
||||
export const componentSource = loader({
|
||||
baseUrl: "/design",
|
||||
source: createMDXSource(components, []),
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { Status } from "./Status";
|
||||
|
||||
export function MDXLayout({ children, frontmatter }: { children: React.ReactNode; frontmatter: any }) {
|
||||
return (
|
||||
<div className="prose max-w-none">
|
||||
<Toaster />
|
||||
{frontmatter.airtableId && (
|
||||
<Status
|
||||
airtableId={frontmatter.airtableId}
|
||||
designStatus={frontmatter.designStatus}
|
||||
devStatus={frontmatter.devStatus}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
interface BorderRadiusToken {
|
||||
name: string;
|
||||
value: string;
|
||||
className: string;
|
||||
}
|
||||
|
||||
interface BorderRadiusTableProps {
|
||||
tokens: BorderRadiusToken[];
|
||||
}
|
||||
|
||||
const tokens: BorderRadiusToken[] = [
|
||||
{ name: "None", value: "0px", className: "rounded-none" },
|
||||
{ name: "Small", value: "0.125rem", className: "rounded-sm" },
|
||||
{ name: "Default", value: "0.25rem", className: "rounded" },
|
||||
{ name: "Medium", value: "0.375rem", className: "rounded-md" },
|
||||
{ name: "Large", value: "0.5rem", className: "rounded-lg" },
|
||||
{ name: "XLarge", value: "0.75rem", className: "rounded-xl" },
|
||||
{ name: "2XLarge", value: "1rem", className: "rounded-2xl" },
|
||||
{ name: "3XLarge", value: "1.5rem", className: "rounded-3xl" },
|
||||
{ name: "Full", value: "9999px", className: "rounded-full" },
|
||||
];
|
||||
|
||||
export const BorderRadiusTable: React.FC<BorderRadiusTableProps> = () => {
|
||||
const handleCopy = async (value: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast(`Copied ${value}`, "success");
|
||||
} catch (error) {
|
||||
console.error("Failed to copy:", error);
|
||||
showToast("Failed to copy", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{tokens.map((token) => (
|
||||
<div
|
||||
key={token.name}
|
||||
onClick={() => handleCopy(token.className)}
|
||||
className="border-subtle bg-default hover:bg-subtle group relative cursor-pointer overflow-hidden rounded-lg border p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-emphasis text-sm font-medium">{token.name}</p>
|
||||
<p className="text-subtle mt-1 text-xs">{token.className}</p>
|
||||
</div>
|
||||
<p className="text-subtle text-sm">{token.value}</p>
|
||||
</div>
|
||||
<div className={`bg-emphasis h-16 w-full ${token.className}`} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,372 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
type IconName =
|
||||
| "activity"
|
||||
| "arrow-down"
|
||||
| "arrow-left"
|
||||
| "arrow-right"
|
||||
| "arrow-up-right"
|
||||
| "arrow-up"
|
||||
| "asterisk"
|
||||
| "at-sign"
|
||||
| "atom"
|
||||
| "badge-check"
|
||||
| "ban"
|
||||
| "bell"
|
||||
| "binary"
|
||||
| "blocks"
|
||||
| "bold"
|
||||
| "book"
|
||||
| "book-open-check"
|
||||
| "book-open"
|
||||
| "book-user"
|
||||
| "building"
|
||||
| "calendar-check-2"
|
||||
| "calendar-days"
|
||||
| "calendar-heart"
|
||||
| "calendar-range"
|
||||
| "calendar-search"
|
||||
| "calendar-x-2"
|
||||
| "calendar"
|
||||
| "chart-bar"
|
||||
| "chart-line"
|
||||
| "check-check"
|
||||
| "check"
|
||||
| "chevron-down"
|
||||
| "chevron-left"
|
||||
| "chevron-right"
|
||||
| "chevron-up"
|
||||
| "chevrons-down-up"
|
||||
| "chevrons-left"
|
||||
| "chevrons-right"
|
||||
| "chevrons-up-down"
|
||||
| "circle-alert"
|
||||
| "circle-arrow-up"
|
||||
| "circle-check-big"
|
||||
| "circle-check"
|
||||
| "circle-help"
|
||||
| "circle-x"
|
||||
| "circle"
|
||||
| "clipboard-check"
|
||||
| "clipboard"
|
||||
| "clock"
|
||||
| "code"
|
||||
| "columns-3"
|
||||
| "command"
|
||||
| "contact"
|
||||
| "copy"
|
||||
| "corner-down-left"
|
||||
| "corner-down-right"
|
||||
| "credit-card"
|
||||
| "disc"
|
||||
| "dot"
|
||||
| "download"
|
||||
| "ellipsis-vertical"
|
||||
| "ellipsis"
|
||||
| "external-link"
|
||||
| "eye-off"
|
||||
| "eye"
|
||||
| "file-down"
|
||||
| "file-text"
|
||||
| "file"
|
||||
| "filter"
|
||||
| "fingerprint"
|
||||
| "flag"
|
||||
| "folder"
|
||||
| "gift"
|
||||
| "git-merge"
|
||||
| "github"
|
||||
| "globe"
|
||||
| "grid-3x3"
|
||||
| "handshake"
|
||||
| "info"
|
||||
| "italic"
|
||||
| "key"
|
||||
| "layers"
|
||||
| "layout-dashboard"
|
||||
| "link-2"
|
||||
| "link"
|
||||
| "loader"
|
||||
| "lock-open"
|
||||
| "lock"
|
||||
| "log-out"
|
||||
| "mail-open"
|
||||
| "mail"
|
||||
| "map-pin"
|
||||
| "map"
|
||||
| "menu"
|
||||
| "message-circle"
|
||||
| "messages-square"
|
||||
| "moon"
|
||||
| "paintbrush"
|
||||
| "paperclip"
|
||||
| "pencil"
|
||||
| "phone-call"
|
||||
| "phone"
|
||||
| "plus"
|
||||
| "refresh-ccw"
|
||||
| "refresh-cw"
|
||||
| "repeat"
|
||||
| "rocket"
|
||||
| "rotate-ccw"
|
||||
| "rotate-cw"
|
||||
| "search"
|
||||
| "send"
|
||||
| "settings"
|
||||
| "share-2"
|
||||
| "shield-check"
|
||||
| "shield"
|
||||
| "shuffle"
|
||||
| "sliders-horizontal"
|
||||
| "sliders-vertical"
|
||||
| "smartphone"
|
||||
| "sparkles"
|
||||
| "square-check"
|
||||
| "star"
|
||||
| "sun"
|
||||
| "sunrise"
|
||||
| "sunset"
|
||||
| "tags"
|
||||
| "terminal"
|
||||
| "trash-2"
|
||||
| "trash"
|
||||
| "trello"
|
||||
| "triangle-alert"
|
||||
| "upload"
|
||||
| "user-check"
|
||||
| "user-plus"
|
||||
| "user-x"
|
||||
| "user"
|
||||
| "users"
|
||||
| "venetian-mask"
|
||||
| "video"
|
||||
| "waypoints"
|
||||
| "webhook"
|
||||
| "x"
|
||||
| "zap";
|
||||
|
||||
interface IconGridProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface CopyMenuProps {
|
||||
name: IconName;
|
||||
onCopy: (format: string) => void;
|
||||
}
|
||||
|
||||
const CopyMenu: React.FC<CopyMenuProps> = ({ name, onCopy }) => {
|
||||
return (
|
||||
<div className="absolute inset-2 flex items-center justify-center bg-black/10 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="bg-default space-y-1 rounded-md p-2 shadow-lg">
|
||||
<button
|
||||
onClick={() => onCopy(`<Icon name="${name}" className="h-4 w-4" />`)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy Component
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const IconGrid: React.FC<IconGridProps> = ({ className }) => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const handleCopy = async (value: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast(`Copied ${value}`, "success");
|
||||
} catch (error) {
|
||||
console.error("Failed to copy:", error);
|
||||
showToast("Failed to copy", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const icons: IconName[] = [
|
||||
"activity",
|
||||
"arrow-down",
|
||||
"arrow-left",
|
||||
"arrow-right",
|
||||
"arrow-up-right",
|
||||
"arrow-up",
|
||||
"asterisk",
|
||||
"at-sign",
|
||||
"atom",
|
||||
"badge-check",
|
||||
"ban",
|
||||
"bell",
|
||||
"binary",
|
||||
"blocks",
|
||||
"bold",
|
||||
"book-open-check",
|
||||
"book-open",
|
||||
"book-user",
|
||||
"building",
|
||||
"calendar-check-2",
|
||||
"calendar-days",
|
||||
"calendar-heart",
|
||||
"calendar-range",
|
||||
"calendar-search",
|
||||
"calendar-x-2",
|
||||
"calendar",
|
||||
"chart-bar",
|
||||
"chart-line",
|
||||
"check-check",
|
||||
"check",
|
||||
"chevron-down",
|
||||
"chevron-left",
|
||||
"chevron-right",
|
||||
"chevron-up",
|
||||
"chevrons-down-up",
|
||||
"chevrons-left",
|
||||
"chevrons-right",
|
||||
"chevrons-up-down",
|
||||
"circle-alert",
|
||||
"circle-arrow-up",
|
||||
"circle-check-big",
|
||||
"circle-check",
|
||||
"circle-help",
|
||||
"circle-x",
|
||||
"circle",
|
||||
"clipboard-check",
|
||||
"clipboard",
|
||||
"clock",
|
||||
"code",
|
||||
"columns-3",
|
||||
"command",
|
||||
"contact",
|
||||
"copy",
|
||||
"corner-down-left",
|
||||
"corner-down-right",
|
||||
"credit-card",
|
||||
"disc",
|
||||
"dot",
|
||||
"download",
|
||||
"ellipsis-vertical",
|
||||
"ellipsis",
|
||||
"external-link",
|
||||
"eye-off",
|
||||
"eye",
|
||||
"file-down",
|
||||
"file-text",
|
||||
"file",
|
||||
"filter",
|
||||
"fingerprint",
|
||||
"flag",
|
||||
"folder",
|
||||
"gift",
|
||||
"git-merge",
|
||||
"github",
|
||||
"globe",
|
||||
"grid-3x3",
|
||||
"handshake",
|
||||
"info",
|
||||
"italic",
|
||||
"key",
|
||||
"layers",
|
||||
"layout-dashboard",
|
||||
"link-2",
|
||||
"link",
|
||||
"loader",
|
||||
"lock-open",
|
||||
"lock",
|
||||
"log-out",
|
||||
"mail-open",
|
||||
"mail",
|
||||
"map-pin",
|
||||
"map",
|
||||
"menu",
|
||||
"message-circle",
|
||||
"messages-square",
|
||||
"moon",
|
||||
"paintbrush",
|
||||
"paperclip",
|
||||
"pencil",
|
||||
"phone-call",
|
||||
"phone",
|
||||
"plus",
|
||||
"refresh-ccw",
|
||||
"refresh-cw",
|
||||
"repeat",
|
||||
"rocket",
|
||||
"rotate-ccw",
|
||||
"rotate-cw",
|
||||
"search",
|
||||
"send",
|
||||
"settings",
|
||||
"share-2",
|
||||
"shield-check",
|
||||
"shield",
|
||||
"shuffle",
|
||||
"sliders-horizontal",
|
||||
"sliders-vertical",
|
||||
"smartphone",
|
||||
"sparkles",
|
||||
"square-check",
|
||||
"star",
|
||||
"sun",
|
||||
"sunrise",
|
||||
"sunset",
|
||||
"tags",
|
||||
"terminal",
|
||||
"trash-2",
|
||||
"trash",
|
||||
"trello",
|
||||
"triangle-alert",
|
||||
"upload",
|
||||
"user-check",
|
||||
"user-plus",
|
||||
"user-x",
|
||||
"user",
|
||||
"users",
|
||||
"venetian-mask",
|
||||
"video",
|
||||
"waypoints",
|
||||
"webhook",
|
||||
"x",
|
||||
"zap",
|
||||
];
|
||||
|
||||
const filteredIcons = useMemo(() => {
|
||||
if (!searchQuery) return icons;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return icons.filter((icon) => icon.toLowerCase().includes(query));
|
||||
}, [searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className={className}>
|
||||
<div className="mb-8">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search icons..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="border-subtle bg-default text-emphasis focus:ring-emphasis placeholder:text-subtle w-full rounded-md border px-4 py-2 focus:outline-none focus:ring-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
|
||||
{filteredIcons.map((iconName) => (
|
||||
<div
|
||||
key={iconName}
|
||||
className="border-subtle bg-default group relative overflow-hidden rounded-lg border p-4">
|
||||
<div className="flex flex-col items-center space-y-2">
|
||||
<div className="bg-subtle flex h-12 w-12 items-center justify-center rounded-md">
|
||||
<Icon name={iconName} className="text-emphasis h-6 w-6" />
|
||||
</div>
|
||||
<p className="text-emphasis text-center text-sm">{iconName}</p>
|
||||
</div>
|
||||
<CopyMenu name={iconName} onCopy={handleCopy} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,126 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
interface ShadowToken {
|
||||
name: string;
|
||||
className: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ShadowTableProps {
|
||||
tokens: ShadowToken[];
|
||||
}
|
||||
|
||||
const tokens: ShadowToken[] = [
|
||||
{
|
||||
name: "Dropdown",
|
||||
className: "shadow-dropdown",
|
||||
description: "Used for dropdown menus and popovers",
|
||||
},
|
||||
{
|
||||
name: "Solid Gray Rested",
|
||||
className: "shadow-solid-gray-rested",
|
||||
description: "Default state for solid gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Solid Gray Hover",
|
||||
className: "shadow-solid-gray-hover",
|
||||
description: "Hover state for solid gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Solid Gray Active",
|
||||
className: "shadow-solid-gray-active",
|
||||
description: "Active/pressed state for solid gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Gray Rested",
|
||||
className: "shadow-outline-gray-rested",
|
||||
description: "Default state for outline gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Gray Hover",
|
||||
className: "shadow-outline-gray-hover",
|
||||
description: "Hover state for outline gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Gray Active",
|
||||
className: "shadow-outline-gray-active",
|
||||
description: "Active/pressed state for outline gray buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Red Rested",
|
||||
className: "shadow-outline-red-rested",
|
||||
description: "Default state for outline red buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Red Hover",
|
||||
className: "shadow-outline-red-hover",
|
||||
description: "Hover state for outline red buttons",
|
||||
},
|
||||
{
|
||||
name: "Outline Red Active",
|
||||
className: "shadow-outline-red-active",
|
||||
description: "Active/pressed state for outline red buttons",
|
||||
},
|
||||
{
|
||||
name: "Elevation Low",
|
||||
className: "shadow-elevation-low",
|
||||
description: "Subtle elevation for UI elements",
|
||||
},
|
||||
{
|
||||
name: "Button Solid Brand Default",
|
||||
className: "shadow-button-solid-brand-default",
|
||||
description: "Default state for solid brand buttons",
|
||||
},
|
||||
{
|
||||
name: "Button Solid Brand Hover",
|
||||
className: "shadow-button-solid-brand-hover",
|
||||
description: "Hover state for solid brand buttons",
|
||||
},
|
||||
{
|
||||
name: "Button Solid Brand Active",
|
||||
className: "shadow-button-solid-brand-active",
|
||||
description: "Active/pressed state for solid brand buttons",
|
||||
},
|
||||
];
|
||||
|
||||
export const ShadowTable: React.FC<ShadowTableProps> = () => {
|
||||
const handleCopy = async (value: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast(`Copied ${value}`, "success");
|
||||
} catch (error) {
|
||||
console.error("Failed to copy:", error);
|
||||
showToast("Failed to copy", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{tokens.map((token) => (
|
||||
<div
|
||||
key={token.name}
|
||||
onClick={() => handleCopy(token.className)}
|
||||
className="group relative cursor-pointer">
|
||||
<div
|
||||
className={`border-subtle bg-default hover:bg-subtle relative overflow-hidden rounded-lg border p-4 transition-colors ${token.className}`}>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<p className="text-emphasis text-sm font-medium">{token.name}</p>
|
||||
<p className="text-subtle mt-1 text-xs">{token.className}</p>
|
||||
</div>
|
||||
<p className="text-subtle text-sm">{token.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
interface SpacingToken {
|
||||
name: string;
|
||||
value: string;
|
||||
pixels: string;
|
||||
}
|
||||
|
||||
interface SpacingTableProps {
|
||||
tokens: SpacingToken[];
|
||||
}
|
||||
|
||||
export const SpacingTable: React.FC<SpacingTableProps> = ({ tokens }) => {
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{tokens.map((token, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border-subtle bg-default group relative overflow-hidden rounded-lg border">
|
||||
<div className="flex items-center gap-4 p-4">
|
||||
<div className="w-[100px] space-y-1">
|
||||
<p className="text-emphasis text-sm font-medium">
|
||||
{token.name === "px" ? "px" : `${token.name} (${token.pixels})`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-subtle relative h-8 rounded">
|
||||
<div
|
||||
className="absolute left-0 top-1/2 h-2 -translate-y-1/2 bg-red-500"
|
||||
style={{ width: token.pixels }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { getColorFormats, copyToClipboard } from "@/lib/colorUtils";
|
||||
import React from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
interface ColorSwatch {
|
||||
name: string;
|
||||
value: string;
|
||||
className: string;
|
||||
}
|
||||
|
||||
interface ColorSection {
|
||||
title: string;
|
||||
description?: string;
|
||||
colors: ColorSwatch[];
|
||||
}
|
||||
|
||||
interface TypeTableProps {
|
||||
sections: ColorSection[];
|
||||
}
|
||||
|
||||
interface CopyMenuProps {
|
||||
color: string;
|
||||
className: string;
|
||||
onCopy: (format: string) => void;
|
||||
}
|
||||
|
||||
const CopyMenu: React.FC<CopyMenuProps> = ({ color, className, onCopy }) => {
|
||||
const formats = getColorFormats(color, className);
|
||||
if (!formats) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-2 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="bg-default space-y-1 rounded-md p-2 shadow-lg">
|
||||
<button
|
||||
onClick={() => onCopy(formats.hex)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy as HEX
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onCopy(formats.rgb)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy as RGB
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onCopy(formats.hsl)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy as HSL
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onCopy(formats.tailwind)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy Tailwind Class
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TypeTable: React.FC<TypeTableProps> = ({ sections }) => {
|
||||
const handleCopy = async (colorValue: string) => {
|
||||
try {
|
||||
await copyToClipboard(colorValue);
|
||||
showToast(`Copied ${colorValue}`, "success");
|
||||
} catch (error) {
|
||||
console.error("Failed to copy color:", error);
|
||||
showToast("Failed to copy color", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="space-y-8">
|
||||
{sections.map((section, sectionIndex) => (
|
||||
<div key={sectionIndex} className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-emphasis text-lg font-semibold">{section.title}</h3>
|
||||
{section.description && <p className="text-subtle text-sm">{section.description}</p>}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{section.colors.map((color, colorIndex) => (
|
||||
<div key={colorIndex} className="border-subtle bg-default overflow-hidden rounded-lg border">
|
||||
<div className="group relative">
|
||||
<div className={`h-36 w-full ${color.className}`} title={color.value} />
|
||||
<CopyMenu color={color.value} className={color.className} onCopy={handleCopy} />
|
||||
</div>
|
||||
<div className="space-y-1 p-2">
|
||||
<p className="text-emphasis text-sm font-medium">{color.name}</p>
|
||||
<p className="text-subtle text-xs">{color.value}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
interface TypographyStyle {
|
||||
name: string;
|
||||
className: string;
|
||||
specs: {
|
||||
fontFamily: string;
|
||||
fontSize: string;
|
||||
lineHeight: string;
|
||||
weight: string;
|
||||
};
|
||||
category?: string;
|
||||
}
|
||||
|
||||
interface TypographySection {
|
||||
title: string;
|
||||
description?: string;
|
||||
styles: TypographyStyle[];
|
||||
}
|
||||
|
||||
interface TypographyTableProps {
|
||||
sections: TypographySection[];
|
||||
}
|
||||
|
||||
interface CopyMenuProps {
|
||||
style: TypographyStyle;
|
||||
onCopy: (format: string) => void;
|
||||
}
|
||||
|
||||
const CopyMenu: React.FC<CopyMenuProps> = ({ style, onCopy }) => {
|
||||
return (
|
||||
<div className="absolute inset-2 flex items-center justify-center bg-black/10 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="bg-default space-y-1 rounded-md p-2 shadow-lg">
|
||||
<button
|
||||
onClick={() => onCopy(style.className)}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy Tailwind Class
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
onCopy(`font-family: ${style.specs.fontFamily};
|
||||
font-size: ${style.specs.fontSize}px;
|
||||
line-height: ${style.specs.lineHeight};
|
||||
font-weight: ${style.specs.weight};`)
|
||||
}
|
||||
className="hover:bg-subtle w-full rounded px-3 py-1 text-left text-sm">
|
||||
Copy CSS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TypographyTable: React.FC<TypographyTableProps> = ({ sections }) => {
|
||||
const handleCopy = async (value: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
showToast(`Copied to clipboard`, "success");
|
||||
} catch (error) {
|
||||
console.error("Failed to copy:", error);
|
||||
showToast("Failed to copy", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div className="space-y-12">
|
||||
{sections.map((section, sectionIndex) => (
|
||||
<div key={sectionIndex} className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-emphasis text-2xl font-semibold">{section.title}</h3>
|
||||
{section.description && <p className="text-subtle mt-2 text-base">{section.description}</p>}
|
||||
</div>
|
||||
<div className="grid gap-6">
|
||||
{section.styles.map((style, styleIndex) => (
|
||||
<div
|
||||
key={styleIndex}
|
||||
className="border-subtle bg-default group relative overflow-hidden rounded-lg border p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-emphasis text-sm font-medium">{style.name}</p>
|
||||
<p className="text-subtle mt-1 text-xs">{style.className}</p>
|
||||
</div>
|
||||
<div className={style.className}>The quick brown fox jumps over the lazy dog</div>
|
||||
</div>
|
||||
<div className="text-subtle space-y-2 text-sm">
|
||||
<p>Font: {style.specs.fontFamily}</p>
|
||||
<p>Size: {style.specs.fontSize}px</p>
|
||||
<p>Line Height: {style.specs.lineHeight}</p>
|
||||
<p>Weight: {style.specs.weight}</p>
|
||||
</div>
|
||||
</div>
|
||||
<CopyMenu style={style} onCopy={handleCopy} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: Border Radius
|
||||
description: Cal.com's border radius system
|
||||
---
|
||||
|
||||
import { BorderRadiusTable } from '@/components/ui/BorderRadiusTable';
|
||||
|
||||
# Border Radius
|
||||
|
||||
Our design system uses a consistent set of border radius values to create a cohesive and modern interface. The border radius scale ranges from none (sharp corners) to full (completely rounded).
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
// No border radius
|
||||
<div className="rounded-none">Sharp corners</div>
|
||||
|
||||
// Default radius
|
||||
<div className="rounded">Default rounded corners</div>
|
||||
|
||||
// Larger radius
|
||||
<div className="rounded-lg">Larger rounded corners</div>
|
||||
|
||||
// Fully rounded (useful for pills, badges)
|
||||
<div className="rounded-full">Completely rounded</div>
|
||||
```
|
||||
|
||||
## Border Radius Scale
|
||||
|
||||
Click on any radius to copy its Tailwind class name. Each preview shows how the radius appears when applied.
|
||||
|
||||
<BorderRadiusTable />
|
||||
@@ -1,144 +0,0 @@
|
||||
---
|
||||
title: Colors
|
||||
description: Cal.com Color System Documentation
|
||||
---
|
||||
|
||||
import { TypeTable } from '@/components/ui/TypeTable';
|
||||
|
||||
export const backgroundTokens = {
|
||||
title: 'Background Tokens',
|
||||
colors: [
|
||||
{ name: 'emphasis', value: 'var(--cal-bg-emphasis)', className: 'bg-emphasis' },
|
||||
{ name: 'default', value: 'var(--cal-bg, white)', className: 'bg-default' },
|
||||
{ name: 'subtle', value: 'var(--cal-bg-subtle)', className: 'bg-subtle' },
|
||||
{ name: 'muted', value: 'var(--cal-bg-muted)', className: 'bg-muted' },
|
||||
{ name: 'inverted', value: 'var(--cal-bg-inverted)', className: 'bg-inverted' },
|
||||
],
|
||||
};
|
||||
|
||||
export const primaryBackgroundTokens = {
|
||||
title: 'Primary Background Tokens',
|
||||
colors: [
|
||||
{ name: 'primary.default', value: 'var(--cal-bg-primary)', className: 'bg-primary-default' },
|
||||
{ name: 'primary.emphasis', value: 'var(--cal-bg-primary-emphasis)', className: 'bg-primary-emphasis' },
|
||||
{ name: 'primary.muted', value: 'var(--cal-bg-primary-muted)', className: 'bg-primary-muted' },
|
||||
],
|
||||
};
|
||||
|
||||
export const brandTokens = {
|
||||
title: 'Brand Colors',
|
||||
colors: [
|
||||
{ name: 'brand.default', value: 'var(--cal-brand,#111827)', className: 'bg-brand-default' },
|
||||
{ name: 'brand.emphasis', value: 'var(--cal-brand-emphasis,#101010)', className: 'bg-brand-emphasis' },
|
||||
{ name: 'brand.subtle', value: 'var(--cal-brand-subtle,#9CA3AF)', className: 'bg-brand-subtle' },
|
||||
{ name: 'brand.accent', value: 'var(--cal-brand-accent,white)', className: 'bg-brand-accent' },
|
||||
],
|
||||
};
|
||||
|
||||
export const semanticBackgroundTokens = {
|
||||
title: 'Semantic Background Tokens',
|
||||
colors: [
|
||||
{ name: 'semantic.info.subtle', value: 'var(--cal-bg-semantic-info-subtle)', className: 'bg-semantic-info-subtle' },
|
||||
{ name: 'semantic.info.emphasis', value: 'var(--cal-bg-semantic-info-emphasis)', className: 'bg-semantic-info-emphasis' },
|
||||
{ name: 'semantic.success.subtle', value: 'var(--cal-bg-semantic-success-subtle)', className: 'bg-semantic-success-subtle' },
|
||||
{ name: 'semantic.success.emphasis', value: 'var(--cal-bg-semantic-success-emphasis)', className: 'bg-semantic-success-emphasis' },
|
||||
{ name: 'semantic.attention.subtle', value: 'var(--cal-bg-semantic-attention-subtle)', className: 'bg-semantic-attention-subtle' },
|
||||
{ name: 'semantic.attention.emphasis', value: 'var(--cal-bg-semantic-attention-emphasis)', className: 'bg-semantic-attention-emphasis' },
|
||||
{ name: 'semantic.error.subtle', value: 'var(--cal-bg-semantic-error-subtle)', className: 'bg-semantic-error-subtle' },
|
||||
{ name: 'semantic.error.emphasis', value: 'var(--cal-bg-semantic-error-emphasis)', className: 'bg-semantic-error-emphasis' },
|
||||
],
|
||||
};
|
||||
|
||||
export const borderTokens = {
|
||||
title: 'Border Tokens',
|
||||
colors: [
|
||||
{ name: 'border.emphasis', value: 'var(--cal-border-emphasis)', className: 'border-emphasis' },
|
||||
{ name: 'border.default', value: 'var(--cal-border)', className: 'border-default' },
|
||||
{ name: 'border.subtle', value: 'var(--cal-border-subtle)', className: 'border-subtle' },
|
||||
{ name: 'border.muted', value: 'var(--cal-border-muted)', className: 'border-muted' },
|
||||
{ name: 'border.semantic.error', value: 'var(--cal-border-semantic-error)', className: 'border-semantic-error' },
|
||||
],
|
||||
};
|
||||
|
||||
export const textTokens = {
|
||||
title: 'Text Tokens',
|
||||
description: 'Semantic text colors for various purposes',
|
||||
colors: [
|
||||
{ name: 'text.emphasis', value: 'var(--cal-text-emphasis)', className: 'text-emphasis' },
|
||||
{ name: 'text.default', value: 'var(--cal-text)', className: 'text-default' },
|
||||
{ name: 'text.subtle', value: 'var(--cal-text-subtle)', className: 'text-subtle' },
|
||||
{ name: 'text.muted', value: 'var(--cal-text-muted)', className: 'text-muted' },
|
||||
{ name: 'text.inverted', value: 'var(--cal-text-inverted)', className: 'text-inverted' },
|
||||
],
|
||||
};
|
||||
|
||||
# Color System
|
||||
|
||||
Our color system is built on semantic tokens that provide consistent theming across light and dark modes. The system is organized into five main categories:
|
||||
|
||||
1. Background Colors
|
||||
2. Primary Colors
|
||||
3. Brand Colors
|
||||
4. Semantic Colors
|
||||
5. Text & Border Colors
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
1. **Use Semantic Tokens**: Always prefer semantic tokens over direct color values to maintain consistency and theme support.
|
||||
2. **Dark Mode Support**: Our semantic tokens automatically handle dark mode transitions.
|
||||
3. **Component Design**: When creating new components, follow the semantic token system:
|
||||
- Use `bg-*` tokens for backgrounds
|
||||
- Use `text-*` tokens for text content
|
||||
- Use `border-*` tokens for borders
|
||||
4. **Semantic States**: Use semantic color variations to convey meaning:
|
||||
- Info: For informational UI elements
|
||||
- Success: For successful actions and positive states
|
||||
- Attention: For warnings and important notices
|
||||
- Error: For error states and destructive actions
|
||||
5. **Brand Colors**: Use brand colors sparingly and primarily for:
|
||||
- Primary CTAs
|
||||
- Brand identity elements
|
||||
- Key interactive components
|
||||
|
||||
|
||||
## Using Semantic Tokens
|
||||
|
||||
Our color tokens are CSS variables that automatically handle light and dark mode transitions. Here's how to use them:
|
||||
|
||||
### Background Colors
|
||||
```tsx
|
||||
// Basic backgrounds
|
||||
<div className="bg-default">Default background</div>
|
||||
<div className="bg-subtle">Subtle background</div>
|
||||
<div className="bg-emphasis">Emphasis background</div>
|
||||
|
||||
// Primary backgrounds
|
||||
<div className="bg-primary-default">Primary background</div>
|
||||
<div className="bg-primary-emphasis">Primary emphasis</div>
|
||||
```
|
||||
|
||||
### Semantic Colors
|
||||
```tsx
|
||||
// Info states
|
||||
<div className="bg-semantic-info-subtle">Info subtle</div>
|
||||
<div className="bg-semantic-info-emphasis">Info emphasis</div>
|
||||
|
||||
// Success states
|
||||
<div className="bg-semantic-success-subtle">Success subtle</div>
|
||||
<div className="bg-semantic-success-emphasis">Success emphasis</div>
|
||||
|
||||
// Error states
|
||||
<div className="bg-semantic-error-subtle">Error subtle</div>
|
||||
<div className="bg-semantic-error-emphasis">Error emphasis</div>
|
||||
```
|
||||
|
||||
### Text Colors
|
||||
```tsx
|
||||
<p className="text-emphasis">High emphasis text</p>
|
||||
<p className="text-default">Default text</p>
|
||||
<p className="text-subtle">Subtle text</p>
|
||||
<p className="text-muted">Muted text</p>
|
||||
```
|
||||
|
||||
<TypeTable sections={[backgroundTokens, primaryBackgroundTokens, brandTokens, semanticBackgroundTokens, borderTokens, textTokens]} />
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
title: MultiOptionInput
|
||||
---
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { StatesExample } from "./multiInputField.states"
|
||||
import { PasteExample } from "./multiInputField.paste"
|
||||
import { CustomizationExample } from "./multiInputField.customization"
|
||||
|
||||
## Overview
|
||||
|
||||
The MultiOptionInput component is a flexible form input that allows users to manage a list of options with features like:
|
||||
- Adding and removing options
|
||||
- Reordering options with move up/down buttons
|
||||
- Pasting multiple options with configurable delimiters
|
||||
- Minimum options requirement
|
||||
- Customizable placeholders and labels
|
||||
- Key-value pair mode for environment variables, configuration settings, etc.
|
||||
|
||||
## States
|
||||
|
||||
MultiOptionInput in different states: default, disabled, and with minimum options.
|
||||
|
||||
<StatesExample/>
|
||||
|
||||
## Paste Support
|
||||
|
||||
Demonstrates the paste functionality with different delimiters. Users can paste comma-separated values, newline-separated values, or use custom delimiters.
|
||||
|
||||
In key-value mode, the component can also parse key-value pairs from pasted text. For example:
|
||||
- `KEY=value`
|
||||
- `NODE_ENV:production`
|
||||
|
||||
<PasteExample/>
|
||||
|
||||
## Customization
|
||||
|
||||
Examples of different customization options like placeholders, move buttons, remove buttons, and key-value pairs.
|
||||
|
||||
<CustomizationExample/>
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Options
|
||||
|
||||
```tsx
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form/inputs/MultiOptionInput";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
type FormValues = {
|
||||
options: Array<{ label: string; id: string }>;
|
||||
};
|
||||
|
||||
const MyForm = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="options"
|
||||
optionPlaceholders={["Option 1", "Option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
pasteDelimiters={["\n", ","]} // Optional
|
||||
showMoveButtons={true} // Optional
|
||||
minOptions={1} // Optional
|
||||
addOptionLabel="Add another option" // Optional
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Key-Value Pairs
|
||||
|
||||
```tsx
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form/inputs/MultiOptionInput";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
type FormValues = {
|
||||
envVars: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
const EnvVarsForm = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="envVars"
|
||||
keyValueMode={true}
|
||||
keyLabel="Environment Variable"
|
||||
valueLabel="Value"
|
||||
optionPlaceholders={["NODE_ENV", "PORT"]}
|
||||
valuePlaceholders={["production", "3000"]}
|
||||
keyValueDelimiters={[":", "="]} // Optional, defaults to [":", "="]
|
||||
defaultNumberOfOptions={2}
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
|
||||
const severities = ["neutral", "info", "warning", "error"] as const;
|
||||
|
||||
export const ActionsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="no-prose space-y-4">
|
||||
{severities.map((severity) => (
|
||||
<Alert
|
||||
key={severity}
|
||||
severity={severity}
|
||||
title={`${severity.charAt(0).toUpperCase() + severity.slice(1)} Alert with Actions`}
|
||||
message={`This is a ${severity} alert with action buttons.`}
|
||||
actions={
|
||||
<div className="flex space-x-2">
|
||||
<button className="rounded-md px-3 py-2 text-sm font-medium hover:opacity-90">Dismiss</button>
|
||||
<button className="rounded-md px-3 py-2 text-sm font-medium hover:opacity-90">View</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
|
||||
const severities = ["neutral", "info", "warning", "error"] as const;
|
||||
|
||||
export const BasicExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="not-prose space-y-4">
|
||||
{severities.map((severity) => (
|
||||
<Alert
|
||||
key={severity}
|
||||
severity={severity}
|
||||
title={`${severity.charAt(0).toUpperCase() + severity.slice(1)} Alert`}
|
||||
message={`This is a ${severity} alert message to show important information to users.`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
|
||||
const severities = ["neutral", "info", "warning", "error"] as const;
|
||||
|
||||
export const CustomIconColorExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="not-prose space-y-4">
|
||||
{severities.map((severity) => (
|
||||
<Alert
|
||||
key={severity}
|
||||
severity={severity}
|
||||
CustomIcon="bell"
|
||||
customIconColor="text-emphasis"
|
||||
title={`${severity.charAt(0).toUpperCase() + severity.slice(1)} Alert with Custom Icon Color`}
|
||||
message="This alert uses a custom icon color."
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
|
||||
const severities = ["neutral", "info", "warning", "error"] as const;
|
||||
const customIcons = ["info", "alert-triangle", "circle-x", "circle-check", "bell"] as const;
|
||||
|
||||
export const CustomIconsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="not-prose space-y-4">
|
||||
{severities.map((severity, index) => (
|
||||
<Alert
|
||||
key={severity}
|
||||
severity={severity}
|
||||
// @ts-expect-error Didnt type this as IconName for CustomIcon
|
||||
CustomIcon={customIcons[index % customIcons.length]}
|
||||
title={`${severity.charAt(0).toUpperCase() + severity.slice(1)} Alert with Custom Icon`}
|
||||
message="This alert uses a custom icon instead of the default one."
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
title: Alert
|
||||
airtableId: recnvwaXqOucqLo9Y
|
||||
---
|
||||
import { Alert } from "@calcom/ui/components/alert"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./alert.basic"
|
||||
import { ActionsExample } from "./alert.actions"
|
||||
import { CustomIconsExample } from "./alert.customIcons"
|
||||
import { CustomIconColorExample } from "./alert.customIconColor"
|
||||
import { NoTitleExample } from "./alert.noTitle"
|
||||
|
||||
## Basic Alerts
|
||||
|
||||
Alerts come in different severities to convey different levels of importance.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## Alerts with Actions
|
||||
|
||||
Alerts can include action buttons to provide users with immediate options.
|
||||
|
||||
<ActionsExample/>
|
||||
|
||||
## Custom Icons
|
||||
|
||||
You can customize the alert icon to better match your message context.
|
||||
|
||||
<CustomIconsExample/>
|
||||
|
||||
## Custom Icon Colors
|
||||
|
||||
The icon color can be customized independently of the alert severity.
|
||||
|
||||
<CustomIconColorExample/>
|
||||
|
||||
## Without Title
|
||||
|
||||
For simpler messages, alerts can be displayed without a title.
|
||||
|
||||
<NoTitleExample/>
|
||||
@@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
|
||||
const severities = ["neutral", "info", "warning", "error"] as const;
|
||||
|
||||
export const NoTitleExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="not-prose space-y-4">
|
||||
{severities.map((severity) => (
|
||||
<Alert
|
||||
key={severity}
|
||||
severity={severity}
|
||||
message={`This is a ${severity} alert without a title to show a more compact version.`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
const sampleImage = "https://cal.com/stakeholder/peer.jpg";
|
||||
|
||||
export const ImageExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="md" alt="With image" imageSrc={sampleImage} />
|
||||
<span className="text-subtle text-xs">With Image</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="md" alt="Without image" imageSrc="https://cal.com/avatar.svg" />
|
||||
<span className="text-subtle text-xs">Without Image</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
const sampleImage = "https://cal.com/stakeholder/peer.jpg";
|
||||
|
||||
export const IndicatorExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar
|
||||
size="md"
|
||||
alt="With indicator"
|
||||
imageSrc={sampleImage}
|
||||
indicator={
|
||||
<div className="absolute bottom-0 right-0 h-2.5 w-2.5 rounded-full bg-green-500 ring-2 ring-white" />
|
||||
}
|
||||
/>
|
||||
<span className="text-subtle text-xs">With Status Indicator</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
const sampleImage = "https://cal.com/stakeholder/peer.jpg";
|
||||
|
||||
export const LinkExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="md" alt="With link" imageSrc={sampleImage} href="https://cal.com" />
|
||||
<span className="text-subtle text-xs">Clickable</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
title: Avatar
|
||||
airtableId: reciflFrJo5cVgGrS
|
||||
---
|
||||
import { Avatar } from "@calcom/ui/components/avatar"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { SizesExample } from "./avatar.sizes"
|
||||
import { ImageExample } from "./avatar.image"
|
||||
import { TooltipExample } from "./avatar.tooltip"
|
||||
import { LinkExample } from "./avatar.link"
|
||||
import { IndicatorExample } from "./avatar.indicator"
|
||||
import { ShapeExample } from "./avatar.shape"
|
||||
|
||||
## Size Variations
|
||||
|
||||
Avatars come in different sizes to suit various use cases.
|
||||
|
||||
<SizesExample/>
|
||||
|
||||
## Shape Variations
|
||||
|
||||
Avatars can be displayed in either circular or square shapes.
|
||||
|
||||
<ShapeExample/>
|
||||
|
||||
## With and Without Image
|
||||
|
||||
Avatars can display either an image or a fallback placeholder.
|
||||
|
||||
<ImageExample/>
|
||||
|
||||
## With Tooltip
|
||||
|
||||
Avatars can show additional information on hover using tooltips.
|
||||
|
||||
<TooltipExample/>
|
||||
|
||||
## With Link
|
||||
|
||||
Avatars can be made clickable by providing an href.
|
||||
|
||||
<LinkExample/>
|
||||
|
||||
## With Indicator
|
||||
|
||||
Avatars can include status indicators or badges.
|
||||
|
||||
<IndicatorExample/>
|
||||
|
||||
## Usage Example
|
||||
|
||||
Here's a complete example showing how to use the Avatar component with all its props:
|
||||
|
||||
```tsx
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Basic Avatar */}
|
||||
<Avatar
|
||||
alt="User Name"
|
||||
title="User Name"
|
||||
size="md"
|
||||
shape="circle"
|
||||
imageSrc="https://example.com/avatar.jpg"
|
||||
/>
|
||||
|
||||
{/* Avatar with Link */}
|
||||
<Avatar
|
||||
alt="User Name"
|
||||
title="User Name"
|
||||
size="lg"
|
||||
shape="square"
|
||||
imageSrc="https://example.com/avatar.jpg"
|
||||
href="/profile"
|
||||
/>
|
||||
|
||||
{/* Avatar with Tooltip and Indicator */}
|
||||
<Avatar
|
||||
alt="User Name"
|
||||
title="User Name"
|
||||
size="md"
|
||||
shape="circle"
|
||||
imageSrc="https://example.com/avatar.jpg"
|
||||
indicator={<div className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-green-500" />}
|
||||
/>
|
||||
|
||||
{/* Avatar with Fallback */}
|
||||
<Avatar
|
||||
alt="User Name"
|
||||
title="User Name"
|
||||
size="md"
|
||||
shape="circle"
|
||||
fallback={<span>UN</span>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
export function ShapeExample() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar
|
||||
alt="Circle Avatar"
|
||||
title="Circle Avatar"
|
||||
size="lg"
|
||||
shape="circle"
|
||||
imageSrc="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
/>
|
||||
<Avatar
|
||||
alt="Square Avatar"
|
||||
title="Square Avatar"
|
||||
size="lg"
|
||||
shape="square"
|
||||
imageSrc="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
const sizes = ["xs", "xsm", "sm", "md", "mdLg", "lg", "xl"] as const;
|
||||
const sampleImage = "https://cal.com/stakeholder/peer.jpg";
|
||||
|
||||
export const SizesExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex items-center gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Avatar size={size} alt={`${size} avatar`} imageSrc={sampleImage} />
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Avatar size={size} alt={`${size} avatar`} imageSrc={sampleImage} shape="square" />
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
|
||||
const sampleImage = "https://cal.com/stakeholder/peer.jpg";
|
||||
|
||||
export const TooltipExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="md" alt="With tooltip" imageSrc={sampleImage} title="Hover me!" />
|
||||
<span className="text-subtle text-xs">Hover to see tooltip</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
|
||||
const variants = [
|
||||
"default",
|
||||
"warning",
|
||||
"orange",
|
||||
"success",
|
||||
"green",
|
||||
"gray",
|
||||
"blue",
|
||||
"red",
|
||||
"error",
|
||||
"grayWithoutHover",
|
||||
] as const;
|
||||
|
||||
export const DotsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex flex-col items-center gap-2">
|
||||
<Badge variant={variant} withDot>
|
||||
{variant}
|
||||
</Badge>
|
||||
<span className="text-subtle text-xs">Dot</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
|
||||
const variants = [
|
||||
"default",
|
||||
"warning",
|
||||
"orange",
|
||||
"success",
|
||||
"green",
|
||||
"gray",
|
||||
"blue",
|
||||
"red",
|
||||
"error",
|
||||
"grayWithoutHover",
|
||||
] as const;
|
||||
|
||||
export const IconsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex flex-col items-center gap-2">
|
||||
<Badge variant={variant} startIcon="bell">
|
||||
{variant}
|
||||
</Badge>
|
||||
<span className="text-subtle text-xs">Icon</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
|
||||
const variants = [
|
||||
"default",
|
||||
"warning",
|
||||
"orange",
|
||||
"success",
|
||||
"green",
|
||||
"gray",
|
||||
"blue",
|
||||
"red",
|
||||
"error",
|
||||
"grayWithoutHover",
|
||||
] as const;
|
||||
|
||||
export const InteractiveExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex flex-col items-center gap-2">
|
||||
<Badge variant={variant} onClick={() => alert(`${variant} badge clicked!`)}>
|
||||
Click me
|
||||
</Badge>
|
||||
<span className="text-subtle text-xs">Clickable</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
title: Badge
|
||||
airtableId: recso7QMfD2xwI04K
|
||||
---
|
||||
import { Badge } from "@calcom/ui/components/badge"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { VariantsExample } from "./badge.variants"
|
||||
import { IconsExample } from "./badge.icons"
|
||||
import { DotsExample } from "./badge.dots"
|
||||
import { InteractiveExample } from "./badge.interactive"
|
||||
import { RoundedExample } from "./badge.rounded"
|
||||
|
||||
## Variants and Sizes
|
||||
|
||||
Badges come in different variants and sizes to suit various use cases.
|
||||
|
||||
<VariantsExample/>
|
||||
|
||||
## With Icons
|
||||
|
||||
Badges can include icons to provide additional visual context.
|
||||
|
||||
<IconsExample/>
|
||||
|
||||
## With Dots
|
||||
|
||||
Badges can include dots, useful for showing status or notifications.
|
||||
|
||||
<DotsExample/>
|
||||
|
||||
## Interactive
|
||||
|
||||
Badges can be made interactive with click handlers.
|
||||
|
||||
<InteractiveExample/>
|
||||
|
||||
## Rounded
|
||||
|
||||
Badges can be fully rounded, useful for numerical indicators.
|
||||
|
||||
<RoundedExample/>
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
|
||||
const variants = [
|
||||
"default",
|
||||
"warning",
|
||||
"orange",
|
||||
"success",
|
||||
"green",
|
||||
"gray",
|
||||
"blue",
|
||||
"red",
|
||||
"error",
|
||||
"grayWithoutHover",
|
||||
] as const;
|
||||
|
||||
export const RoundedExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex flex-col items-center gap-2">
|
||||
<Badge variant={variant} rounded>
|
||||
1
|
||||
</Badge>
|
||||
<span className="text-subtle text-xs">{variant}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
|
||||
const variants = [
|
||||
"default",
|
||||
"warning",
|
||||
"orange",
|
||||
"success",
|
||||
"green",
|
||||
"gray",
|
||||
"blue",
|
||||
"red",
|
||||
"error",
|
||||
"grayWithoutHover",
|
||||
] as const;
|
||||
|
||||
const sizes = ["sm", "md", "lg"] as const;
|
||||
|
||||
export const VariantsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="space-y-2">
|
||||
<h4 className="text-default text-sm capitalize">{variant}</h4>
|
||||
<div className="flex items-center gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Badge variant={variant} size={size}>
|
||||
{variant}
|
||||
</Badge>
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,63 +0,0 @@
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { ButtonGroup } from "@calcom/ui/components/buttonGroup";
|
||||
|
||||
export const ButtonGroupExample = () => {
|
||||
return (
|
||||
<RenderComponentWithSnippet className="flex flex-col space-y-6">
|
||||
{/* Basic Button Group */}
|
||||
<ButtonGroup>
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
</ButtonGroup>
|
||||
|
||||
{/* Combined Button Group */}
|
||||
<ButtonGroup combined>
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
<Button variant="icon" StartIcon="ellipsis" color="secondary" />
|
||||
</ButtonGroup>
|
||||
|
||||
{/* Button Group with different variants */}
|
||||
<ButtonGroup combined>
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" />
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" />
|
||||
<Button variant="icon" color="destructive" StartIcon="trash" />
|
||||
</ButtonGroup>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonGroupSizes = () => {
|
||||
const sizes = ["xs", "sm", "base", "lg"] as const;
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet className="flex flex-col space-y-6">
|
||||
{sizes.map((size) => (
|
||||
<ButtonGroup key={size}>
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
</ButtonGroup>
|
||||
))}
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonGroupSizesCombined = () => {
|
||||
const sizes = ["xs", "sm", "base", "lg"] as const;
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet className="flex flex-col space-y-6">
|
||||
{sizes.map((size) => (
|
||||
<ButtonGroup key={size} combined>
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
<Button variant="icon" color="secondary" StartIcon="ellipsis" size={size} />
|
||||
</ButtonGroup>
|
||||
))}
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
const colors = ["primary", "secondary", "minimal", "destructive"] as const;
|
||||
|
||||
export const DisabledExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">Disabled State</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} disabled>
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">Disabled</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">Disabled with Icons</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} disabled StartIcon="calendar">
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">With Icon</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
const colors = ["primary", "secondary", "minimal", "destructive"] as const;
|
||||
|
||||
export const IconsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">Start Icon</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} StartIcon="calendar">
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">Start Icon</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">End Icon</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} EndIcon="arrow-right">
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">End Icon</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
const colors = ["primary", "secondary", "minimal", "destructive"] as const;
|
||||
|
||||
export const LoadingExample: React.FC = () => {
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">Loading State</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} loading>
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">Loading</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-default mb-4 text-sm">Loading with Icons</h3>
|
||||
<Row>
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="flex flex-col items-center gap-2">
|
||||
<Button color={color} loading StartIcon="calendar">
|
||||
{color}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">With Icon</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
title: Button
|
||||
airtableId: recEJyzUeMMXCjyTB
|
||||
---
|
||||
import { Button } from "@calcom/ui/components/button"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { OnClickExample } from "./button.onClick"
|
||||
import { VariantExample } from "./button.variant"
|
||||
import { IconsExample } from "./button.icons"
|
||||
import { LoadingExample } from "./button.loading"
|
||||
import { DisabledExample } from "./button.disabled"
|
||||
import { ButtonGroupExample,ButtonGroupSizes,ButtonGroupSizesCombined} from "./button.buttongroup"
|
||||
|
||||
## Variants and Colors
|
||||
|
||||
Buttons come in different variants and colors to suit different purposes. Each variant can be combined with any color and size.
|
||||
|
||||
<VariantExample/>
|
||||
|
||||
## Icons
|
||||
|
||||
Buttons can include icons at the start or end. The icon color and size are automatically handled by the button component.
|
||||
|
||||
<IconsExample/>
|
||||
|
||||
## Loading State
|
||||
|
||||
Buttons can show a loading state to indicate an ongoing action. The loading spinner replaces the button text while maintaining the button's width.
|
||||
|
||||
<LoadingExample/>
|
||||
|
||||
## Disabled State
|
||||
|
||||
Buttons can be disabled to indicate that they are not interactive. The disabled state is visually distinct and prevents any click events.
|
||||
|
||||
<DisabledExample/>
|
||||
|
||||
## onClick Events
|
||||
|
||||
Buttons handle click events like any other button component. You can provide an onClick handler to respond to user interactions.
|
||||
|
||||
<OnClickExample/>
|
||||
|
||||
Providing a loading state will add a spinner on the left side of the button.
|
||||
|
||||
<LoadingExample/>
|
||||
|
||||
## Disabled
|
||||
|
||||
<DisabledExample/>
|
||||
|
||||
## Button Groups
|
||||
|
||||
Buttons can be grouped together using the ButtonGroup component. The ButtonGroup component can be used to create a group of related buttons with consistent spacing and optional combined borders.
|
||||
|
||||
<ButtonGroupExample/>
|
||||
|
||||
### Button Group Sizes
|
||||
|
||||
#### Separated
|
||||
|
||||
<ButtonGroupSizes/>
|
||||
|
||||
#### Combined
|
||||
|
||||
<ButtonGroupSizesCombined/>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
// Primary button (default)
|
||||
<Button>Primary Button</Button>
|
||||
|
||||
// Secondary button
|
||||
<Button color="secondary">Secondary Button</Button>
|
||||
|
||||
// Minimal button
|
||||
<Button color="minimal">Minimal Button</Button>
|
||||
|
||||
// Destructive button
|
||||
<Button color="destructive">Destructive Button</Button>
|
||||
|
||||
// With start icon
|
||||
<Button StartIcon="plus">Add Item</Button>
|
||||
|
||||
// With end icon
|
||||
<Button EndIcon="chevronRight">View More</Button>
|
||||
|
||||
// With link
|
||||
<Button href="/some-path">Navigate</Button>
|
||||
|
||||
// Different sizes
|
||||
<Button size="xs">Extra Small</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="base">Base (Default)</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
export const OnClickExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<Row>
|
||||
<Button onClick={() => alert("hello")}>Trigger Alert</Button>
|
||||
</Row>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
|
||||
const variants = ["button", "icon"] as const;
|
||||
const colors = ["primary", "secondary", "minimal", "destructive"] as const;
|
||||
const sizes = ["xs", "sm", "base", "lg"] as const;
|
||||
|
||||
export const VariantExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="space-y-6">
|
||||
<h3 className="text-default text-sm capitalize">{variant === "button" ? "Default" : variant}</h3>
|
||||
<div className="space-y-6">
|
||||
{colors.map((color) => (
|
||||
<div key={color} className="space-y-2">
|
||||
<h4 className="text-default text-sm capitalize">{color}</h4>
|
||||
<Row>
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Button
|
||||
variant={variant}
|
||||
color={color}
|
||||
size={size}
|
||||
StartIcon={variant === "icon" ? "plus" : undefined}>
|
||||
{variant !== "icon" && `Label`}
|
||||
</Button>
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Checkbox } from "@calcom/ui/components/form";
|
||||
|
||||
export const BasicExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<Checkbox id="basic-unchecked" />
|
||||
<span className="text-subtle text-xs">Unchecked</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<Checkbox id="basic-checked" defaultChecked />
|
||||
<span className="text-subtle text-xs">Checked</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<Checkbox id="basic-disabled" disabled />
|
||||
<span className="text-subtle text-xs">Disabled</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<Checkbox id="basic-disabled-checked" disabled defaultChecked />
|
||||
<span className="text-subtle text-xs">Disabled Checked</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { CheckboxField } from "@calcom/ui/components/form";
|
||||
|
||||
export const DescriptionExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-4">
|
||||
<CheckboxField description="This is a regular description" id="desc-normal" />
|
||||
<CheckboxField description="This description acts as the label" descriptionAsLabel id="desc-as-label" />
|
||||
<CheckboxField id="desc-long" label="Label and description" description="Label and description" />
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,16 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { CheckboxField } from "@calcom/ui/components/form";
|
||||
|
||||
export const InfoIconExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<CheckboxField
|
||||
description="Checkbox with additional information"
|
||||
id="info-icon"
|
||||
label="Information Icon"
|
||||
informationIconText="This is additional information that appears in a tooltip"
|
||||
/>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { CheckboxField } from "@calcom/ui/components/form";
|
||||
|
||||
export const LabelPositionExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-4">
|
||||
<CheckboxField
|
||||
description="Description with label above (default on mobile)"
|
||||
id="label-above"
|
||||
label="Label Above"
|
||||
/>
|
||||
<div className="sm:min-w-[400px]">
|
||||
<CheckboxField
|
||||
description="Description with label to the side (on larger screens)"
|
||||
id="label-side"
|
||||
label="Label to the Side"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Checkbox
|
||||
airtableId: recXGPNAfDJVm8NHo
|
||||
---
|
||||
import { Checkbox } from "@calcom/ui/components/form"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./checkbox.basic"
|
||||
import { DescriptionExample } from "./checkbox.description"
|
||||
import { LabelPositionExample } from "./checkbox.labelPosition"
|
||||
import { InfoIconExample } from "./checkbox.infoIcon"
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Checkboxes in their various states - unchecked, checked, disabled, and disabled checked.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## With Description
|
||||
|
||||
Checkboxes can include descriptions to provide additional context.
|
||||
|
||||
<DescriptionExample/>
|
||||
|
||||
## Label Positions
|
||||
|
||||
Labels can be positioned above or to the side of the checkbox.
|
||||
|
||||
<LabelPositionExample/>
|
||||
|
||||
## With Information Icon
|
||||
|
||||
Checkboxes can include an information icon with a tooltip for additional details.
|
||||
|
||||
<InfoIconExample/>
|
||||
@@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
|
||||
|
||||
export const BasicExample: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-2">
|
||||
<Button onClick={() => setOpen(true)}>Open Basic Dialog</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent
|
||||
title="Basic Dialog"
|
||||
description="This is a basic dialog with a title and description.">
|
||||
<p className="text-default text-sm">
|
||||
Dialogs are used to display content that requires user attention or interaction. They appear
|
||||
above the page content and must be closed before interacting with the page again.
|
||||
</p>
|
||||
<DialogFooter>
|
||||
<DialogClose />
|
||||
<Button>Action</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
|
||||
|
||||
export const ConfirmationExample: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-2">
|
||||
<Button color="destructive" onClick={() => setOpen(true)}>
|
||||
Delete Item
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent
|
||||
title="Confirm Deletion"
|
||||
description="Are you sure you want to delete this item? This action cannot be undone.">
|
||||
<DialogFooter>
|
||||
<DialogClose />
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
|
||||
import { TextAreaField } from "@calcom/ui/components/form";
|
||||
|
||||
export const FormExample: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formInput, setFormInput] = useState("");
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-2">
|
||||
<Button color="secondary" onClick={() => setOpen(true)}>
|
||||
Open Form Dialog
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent title="Feedback Form" description="Please provide your feedback below">
|
||||
<div>
|
||||
<TextAreaField
|
||||
name="feedback"
|
||||
label={
|
||||
<>
|
||||
Your Feedback
|
||||
<span className="text-subtle font-normal"> (Optional)</span>
|
||||
</>
|
||||
}
|
||||
value={formInput}
|
||||
onChange={(e) => setFormInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose />
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setFormInput("");
|
||||
}}>
|
||||
Submit Feedback
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div className="mt-6">
|
||||
<h4 className="text-emphasis text-sm font-medium">Current Form Input:</h4>
|
||||
<pre className="text-default bg-subtle mt-2 rounded-md p-4 text-sm">
|
||||
{formInput || "No input yet"}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@calcom/ui/components/dialog";
|
||||
|
||||
export const LargeContentExample: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-2">
|
||||
<Button color="secondary" onClick={() => setOpen(true)}>
|
||||
Open Large Content Dialog
|
||||
</Button>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent
|
||||
title="Terms and Conditions"
|
||||
description="Please review our terms and conditions carefully"
|
||||
enableOverflow>
|
||||
<div className="space-y-6">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div key={i} className="space-y-4">
|
||||
<h3 className="text-emphasis font-semibold">Section {i + 1}</h3>
|
||||
<p className="text-default text-sm">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris. Vivamus
|
||||
hendrerit arcu sed erat molestie vehicula. Sed auctor neque eu tellus rhoncus ut eleifend
|
||||
nibh porttitor. Ut in nulla enim. Phasellus molestie magna non est bibendum non venenatis
|
||||
nisl tempor.
|
||||
</p>
|
||||
<p className="text-default text-sm">
|
||||
Suspendisse in orci enim. Integer vel sapien at risus ultrices ornare nec sit amet nibh.
|
||||
Duis blandit lectus ac odio rhoncus non congue diam bibendum. Aliquam erat volutpat.
|
||||
</p>
|
||||
{i === 2 && (
|
||||
<div className="border-subtle rounded-lg border p-4 shadow-sm">
|
||||
<h4 className="text-emphasis mb-2 font-medium">Important Notice</h4>
|
||||
<p className="text-default text-sm">
|
||||
This is a highlighted section within the content to demonstrate how different UI
|
||||
elements appear within a scrollable dialog.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose>Decline</DialogClose>
|
||||
<Button>Accept Terms</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Dialog
|
||||
airtableId: rechCoaoEMZLnLChR
|
||||
---
|
||||
import { Dialog } from "@calcom/ui/components/dialog"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./dialog.basic"
|
||||
import { LargeContentExample } from "./dialog.largeContent"
|
||||
import { FormExample } from "./dialog.form"
|
||||
import { ConfirmationExample } from "./dialog.confirmation"
|
||||
|
||||
## Basic Dialog
|
||||
|
||||
A simple dialog with a title, description, and action buttons.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## Large Content Dialog
|
||||
|
||||
A dialog that can handle scrollable content, such as terms and conditions.
|
||||
|
||||
<LargeContentExample/>
|
||||
|
||||
## Form Dialog
|
||||
|
||||
A dialog containing a form for user input.
|
||||
|
||||
<FormExample/>
|
||||
|
||||
## Confirmation Dialog
|
||||
|
||||
A dialog for confirming destructive actions.
|
||||
|
||||
<ConfirmationExample/>
|
||||
@@ -1,38 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
|
||||
export const AvatarTriggerExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-8">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="cursor-pointer">
|
||||
<Avatar size="sm" imageSrc="https://cal.com/stakeholder/peer.jpg" alt="Avatar" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Profile</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownItem>View Profile</DropdownItem>
|
||||
<DropdownItem>Settings</DropdownItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownItem className="text-error">Sign out</DropdownItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">Avatar Menu</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,84 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import type { IconName } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownItem,
|
||||
DropdownMenuLabel,
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
|
||||
const menuItems = [
|
||||
{ label: "View", icon: "eye", kbd: "V" },
|
||||
{ label: "Edit", icon: "edit", kbd: "E" },
|
||||
{ label: "Share", icon: "share", kbd: "⌘S" },
|
||||
{ label: "Delete", icon: "trash", destructive: true, kbd: "⌘⌫" },
|
||||
] as {
|
||||
label: string;
|
||||
icon: IconName;
|
||||
destructive?: boolean;
|
||||
kbd?: string;
|
||||
}[];
|
||||
|
||||
export const ButtonTriggerExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-8">
|
||||
{/* Default Button */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button>Menu</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
{menuItems.map((item) => (
|
||||
<DropdownItem color={item.destructive ? "destructive" : "secondary"} key={item.label}>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">Default</span>
|
||||
</div>
|
||||
|
||||
{/* Button with Icon */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button>More Actions</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Quick Actions</DropdownMenuLabel>
|
||||
{menuItems.map((item) => (
|
||||
<DropdownItem key={item.label} color={item.destructive ? "destructive" : undefined}>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">With Icon</span>
|
||||
</div>
|
||||
|
||||
{/* Icon Button */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="icon" StartIcon="calendar" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{menuItems.map((item) => (
|
||||
<DropdownItem key={item.label} color={item.destructive ? "destructive" : undefined}>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">Icon Only</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,86 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
|
||||
export const ComplexExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="flex flex-wrap items-center gap-8">
|
||||
{/* Example 1: Complex Menu Structure */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button color="secondary">Open</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>My account</DropdownMenuLabel>
|
||||
<DropdownItem>Profile</DropdownItem>
|
||||
<DropdownItem>Billing</DropdownItem>
|
||||
<DropdownItem>Settings</DropdownItem>
|
||||
<DropdownItem>Keyboard Shortcuts</DropdownItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Team</DropdownMenuLabel>
|
||||
<DropdownItem>Invite users</DropdownItem>
|
||||
<DropdownItem>New team</DropdownItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownItem>Github</DropdownItem>
|
||||
<DropdownItem>Support</DropdownItem>
|
||||
<DropdownItem>API</DropdownItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">Complex Menu</span>
|
||||
</div>
|
||||
|
||||
{/* Example 2: Avatar with Icons */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="cursor-pointer">
|
||||
<Avatar size="md" imageSrc="https://cal.com/stakeholder/peer.jpg" alt="Avatar" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>peer@cal.com</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownItem StartIcon="user">My account</DropdownItem>
|
||||
<DropdownItem StartIcon="map">Plan</DropdownItem>
|
||||
<DropdownItem StartIcon="credit-card">Billing</DropdownItem>
|
||||
<DropdownItem StartIcon="link">Integrations</DropdownItem>
|
||||
<DropdownItem StartIcon="upload">Module</DropdownItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownItem className="text-error" StartIcon="log-out">
|
||||
Log out
|
||||
</DropdownItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">With Icons</span>
|
||||
</div>
|
||||
|
||||
{/* Example 3: Simple Actions */}
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="icon" color="secondary" StartIcon="calendar" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownItem>Rename</DropdownItem>
|
||||
<DropdownItem>Duplicate</DropdownItem>
|
||||
<DropdownItem>Move</DropdownItem>
|
||||
<DropdownItem color="destructive">Delete</DropdownItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<span className="text-subtle text-xs">Simple Actions</span>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
title: Dropdown
|
||||
airtableId: recMePWqulsKOCa59
|
||||
---
|
||||
import { Dropdown } from "@calcom/ui/components/dropdown"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { ButtonTriggerExample } from "./dropdown.buttonTrigger"
|
||||
import { AvatarTriggerExample } from "./dropdown.avatarTrigger"
|
||||
import { ComplexExample } from "./dropdown.complex"
|
||||
|
||||
## Button Trigger
|
||||
|
||||
Dropdowns can be triggered by various types of buttons.
|
||||
|
||||
<ButtonTriggerExample/>
|
||||
|
||||
## Avatar Trigger
|
||||
|
||||
Dropdowns can be triggered by avatar components, commonly used for profile menus.
|
||||
|
||||
<AvatarTriggerExample/>
|
||||
|
||||
## Complex Examples
|
||||
|
||||
More complex dropdown examples with various combinations of labels, icons, and separators.
|
||||
|
||||
<ComplexExample/>
|
||||
@@ -1,17 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
export const BasicExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<EmptyScreen
|
||||
Icon="calendar"
|
||||
headline="No upcoming meetings"
|
||||
description="Create a meeting to get started"
|
||||
buttonText="Create Meeting"
|
||||
buttonOnClick={() => alert("Create Meeting clicked")}
|
||||
/>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
export const BorderExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-8">
|
||||
{/* Without Border */}
|
||||
<div>
|
||||
<h4 className="text-emphasis mb-4 text-sm font-medium">Without Border</h4>
|
||||
<EmptyScreen
|
||||
Icon="grid-3x3"
|
||||
headline="No apps installed"
|
||||
description="Browse available apps in the marketplace"
|
||||
buttonText="Browse Apps"
|
||||
buttonOnClick={() => alert("Browse Apps clicked")}
|
||||
border={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* With Solid Border */}
|
||||
<div>
|
||||
<h4 className="text-emphasis mb-4 text-sm font-medium">With Solid Border</h4>
|
||||
<EmptyScreen
|
||||
Icon="mail"
|
||||
headline="No messages"
|
||||
description="Your inbox is empty"
|
||||
buttonText="Compose"
|
||||
buttonOnClick={() => alert("Compose clicked")}
|
||||
dashedBorder={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
export const CustomIconExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<EmptyScreen
|
||||
Icon="user"
|
||||
iconClassName="text-emphasis h-12 w-12"
|
||||
headline="No team members"
|
||||
description="Add team members to collaborate"
|
||||
buttonText="Add Members"
|
||||
buttonOnClick={() => alert("Add Members clicked")}
|
||||
/>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
export const HtmlContentExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<EmptyScreen
|
||||
Icon="info"
|
||||
headline={
|
||||
<span className="text-emphasis">
|
||||
Custom Headline with <strong>HTML</strong>
|
||||
</span>
|
||||
}
|
||||
description={
|
||||
<div className="text-subtle space-y-2">
|
||||
<p>This is a custom description with multiple paragraphs.</p>
|
||||
<p>
|
||||
You can include <em>any HTML content</em> here.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
buttonText="Learn More"
|
||||
buttonOnClick={() => alert("Learn More clicked")}
|
||||
/>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
title: Empty Screen
|
||||
---
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./emptyScreen.basic"
|
||||
import { CustomIconExample } from "./emptyScreen.customIcon"
|
||||
import { RawButtonExample } from "./emptyScreen.rawButton"
|
||||
import { BorderExample } from "./emptyScreen.border"
|
||||
import { HtmlContentExample } from "./emptyScreen.htmlContent"
|
||||
|
||||
## Basic Usage
|
||||
|
||||
A simple empty screen with an icon, headline, description, and action button.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## Custom Icon
|
||||
|
||||
Empty screen with a custom icon and styling.
|
||||
|
||||
<CustomIconExample/>
|
||||
|
||||
## Raw Button
|
||||
|
||||
Empty screen with custom button layout.
|
||||
|
||||
<RawButtonExample/>
|
||||
|
||||
## Border Variations
|
||||
|
||||
Empty screen with different border styles.
|
||||
|
||||
<BorderExample/>
|
||||
|
||||
## HTML Content
|
||||
|
||||
Empty screen with custom HTML content in headline and description.
|
||||
|
||||
<HtmlContentExample/>
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
|
||||
|
||||
export const RawButtonExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<EmptyScreen
|
||||
Icon="link"
|
||||
headline="No links found"
|
||||
description="Create a new link to share"
|
||||
buttonRaw={
|
||||
<div className="flex space-x-2">
|
||||
<Button color="primary" onClick={() => alert("Create Link clicked")}>
|
||||
Create Link
|
||||
</Button>
|
||||
<Button color="secondary" onClick={() => alert("Import Links clicked")}>
|
||||
Import Links
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,67 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { InputField } from "@calcom/ui/components/form";
|
||||
import { Icon } from "@calcom/ui/components/icon";
|
||||
|
||||
type FormValues = {
|
||||
website: string;
|
||||
githubUsername: string;
|
||||
customDomain: string;
|
||||
};
|
||||
|
||||
export const AddOnsExample: React.FC = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: {
|
||||
website: "",
|
||||
githubUsername: "",
|
||||
customDomain: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
console.log("Form submitted:", data);
|
||||
};
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<InputField
|
||||
label="Website"
|
||||
addOnLeading="https://"
|
||||
{...form.register("website")}
|
||||
error={form.formState.errors.website?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="Website"
|
||||
addOnLeading="apple.cal.com/team/seeded-team/"
|
||||
{...form.register("website")}
|
||||
error={form.formState.errors.website?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="GitHub Username"
|
||||
addOnLeading={<Icon name="github" className="text-subtle h-4 w-4" />}
|
||||
{...form.register("githubUsername")}
|
||||
error={form.formState.errors.githubUsername?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="Custom Domain"
|
||||
addOnSuffix=".cal.com"
|
||||
{...form.register("customDomain")}
|
||||
error={form.formState.errors.customDomain?.message}
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button color="minimal" type="button" onClick={() => form.reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { InputField } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export const BasicExample: React.FC = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
console.log("Form submitted:", data);
|
||||
};
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<InputField
|
||||
label="Username"
|
||||
{...form.register("username")}
|
||||
error={form.formState.errors.username?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="Email"
|
||||
type="email"
|
||||
{...form.register("email")}
|
||||
error={form.formState.errors.email?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="Password"
|
||||
type="password"
|
||||
{...form.register("password")}
|
||||
error={form.formState.errors.password?.message}
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button color="minimal" type="button" onClick={() => form.reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: Form
|
||||
---
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./form.basic"
|
||||
import { AddOnsExample } from "./form.addOns"
|
||||
import { SpecialFieldsExample } from "./form.specialFields"
|
||||
|
||||
## Basic Fields
|
||||
|
||||
Basic form fields with validation using React Hook Form and Zod.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## Fields with Add-ons
|
||||
|
||||
Form fields with leading and trailing add-ons.
|
||||
|
||||
<AddOnsExample/>
|
||||
|
||||
## Number and Special Fields
|
||||
|
||||
Form fields for numbers and special input types.
|
||||
|
||||
<SpecialFieldsExample/>
|
||||
@@ -1,53 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { InputField } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
age?: number;
|
||||
phone?: string;
|
||||
};
|
||||
|
||||
export const SpecialFieldsExample: React.FC = () => {
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: {
|
||||
age: undefined,
|
||||
phone: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
console.log("Form submitted:", data);
|
||||
};
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<InputField
|
||||
label="Age"
|
||||
type="number"
|
||||
{...form.register("age", { valueAsNumber: true })}
|
||||
error={form.formState.errors.age?.message}
|
||||
/>
|
||||
<InputField
|
||||
label="Phone"
|
||||
type="tel"
|
||||
addOnLeading="+"
|
||||
{...form.register("phone")}
|
||||
error={form.formState.errors.phone?.message}
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button color="minimal" type="button" onClick={() => form.reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Input
|
||||
airtableId: rec2yv97ak5hv3kIL
|
||||
---
|
||||
import { Input } from "@calcom/ui/components/form"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { StatesExample } from "./input.states"
|
||||
import { TypesExample } from "./input.types"
|
||||
import { WidthsExample } from "./input.widths"
|
||||
import { ValuesExample } from "./input.values"
|
||||
|
||||
## States
|
||||
|
||||
Input fields in different states: default, disabled, and readonly.
|
||||
|
||||
<StatesExample/>
|
||||
|
||||
## Types
|
||||
|
||||
Different types of input fields for various data types.
|
||||
|
||||
<TypesExample/>
|
||||
|
||||
## Widths
|
||||
|
||||
Input fields with different width configurations.
|
||||
|
||||
<WidthsExample/>
|
||||
|
||||
## With Values
|
||||
|
||||
Input fields with default values and placeholders.
|
||||
|
||||
<ValuesExample/>
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Input } from "@calcom/ui/components/form";
|
||||
|
||||
const states = ["default", "disabled", "readonly"] as const;
|
||||
const sizes = ["sm", "md"] as const;
|
||||
|
||||
export const StatesExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{states.map((state) => (
|
||||
<div key={state} className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm capitalize">{state}</h3>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={`${state} input`}
|
||||
disabled={state === "disabled"}
|
||||
readOnly={state === "readonly"}
|
||||
size={size}
|
||||
isFullWidth={false}
|
||||
/>
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,46 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Input } from "@calcom/ui/components/form";
|
||||
|
||||
const types = ["text", "email", "password", "number", "tel", "url", "search"] as const;
|
||||
const sizes = ["sm", "md"] as const;
|
||||
|
||||
export const TypesExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{types.map((type) => (
|
||||
<div key={type} className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm capitalize">{type}</h3>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<Input
|
||||
type={type}
|
||||
placeholder={`Enter ${type}...`}
|
||||
defaultValue={
|
||||
type === "email"
|
||||
? "example@cal.com"
|
||||
: type === "url"
|
||||
? "https://cal.com"
|
||||
: type === "number"
|
||||
? "42"
|
||||
: type === "tel"
|
||||
? "+1234567890"
|
||||
: ""
|
||||
}
|
||||
size={size}
|
||||
isFullWidth={false}
|
||||
/>
|
||||
<span className="text-subtle text-xs">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Input } from "@calcom/ui/components/form";
|
||||
|
||||
const sizes = ["sm", "md"] as const;
|
||||
|
||||
export const ValuesExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Size: {size}</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-subtle text-xs">Default Value</h4>
|
||||
<Input type="text" defaultValue="Default text value" size={size} />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-subtle text-xs">Placeholder</h4>
|
||||
<Input type="text" placeholder="Placeholder text" size={size} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Input } from "@calcom/ui/components/form";
|
||||
|
||||
const sizes = ["sm", "md"] as const;
|
||||
|
||||
export const WidthsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Size: {size}</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-subtle text-xs">Full Width</h4>
|
||||
<Input type="text" placeholder="Full width input" size={size} isFullWidth />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-subtle text-xs">Auto Width</h4>
|
||||
<Input type="text" placeholder="Auto width input" size={size} isFullWidth={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
customPlaceholders: Array<{ label: string; id: string }>;
|
||||
noMoveButtons: Array<{ label: string; id: string }>;
|
||||
customLabel: Array<{ label: string; id: string }>;
|
||||
keyValuePairs: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
export const CustomizationExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Key-Value Pairs</h3>
|
||||
<p className="text-subtle text-xs">Allows inputting keys and values</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="keyValuePairs"
|
||||
keyValueMode
|
||||
keyLabel="Environment Variable"
|
||||
valueLabel="Value"
|
||||
optionPlaceholders={["NODE_ENV", "PORT", "DATABASE_URL"]}
|
||||
valuePlaceholders={["production", "3000", "postgres://..."]}
|
||||
defaultNumberOfOptions={3}
|
||||
keyValueDelimiters={[":", "="]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Placeholders</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customPlaceholders"
|
||||
optionPlaceholders={["Enter your name", "Enter your email", "Enter your phone"]}
|
||||
defaultNumberOfOptions={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Without Move Buttons</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="noMoveButtons"
|
||||
optionPlaceholders={["Static option 1", "Static option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
showMoveButtons={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Add Button Label</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customLabel"
|
||||
optionPlaceholders={["Social media link"]}
|
||||
defaultNumberOfOptions={1}
|
||||
addOptionLabel="Add another social media link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
newlineOptions: Array<{ label: string; id: string }>;
|
||||
commaOptions: Array<{ label: string; id: string }>;
|
||||
customOptions: Array<{ label: string; id: string }>;
|
||||
keyValueOptions: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
export const PasteExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Default Delimiters (Newline and Comma)</h3>
|
||||
<p className="text-subtle text-xs">
|
||||
Try pasting: “Option 1, Option 2” or multiple lines
|
||||
</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="newlineOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Comma Only Delimiter</h3>
|
||||
<p className="text-subtle text-xs">Try pasting: “First, Second, Third”</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="commaOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
pasteDelimiters={[","]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Key-Value Pair Paste Support</h3>
|
||||
<p className="text-subtle text-xs">
|
||||
Try pasting: “NODE_ENV=production” or “KEY1:value1, KEY2:value2”
|
||||
</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="keyValueOptions"
|
||||
keyValueMode
|
||||
optionPlaceholders={["Key..."]}
|
||||
valuePlaceholders={["Value..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
keyValueDelimiters={[":", "="]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Delimiters (Semicolon and Pipe)</h3>
|
||||
<p className="text-subtle text-xs">Try pasting: “One;Two|Three”</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
pasteDelimiters={[";", "|"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
defaultOptions: Array<{ label: string; id: string }>;
|
||||
disabledOptions: Array<{ label: string; id: string }>;
|
||||
minOptions: Array<{ label: string; id: string }>;
|
||||
};
|
||||
|
||||
export const StatesExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Default State</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="defaultOptions"
|
||||
optionPlaceholders={["First option", "Second option", "Third option"]}
|
||||
defaultNumberOfOptions={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Disabled State</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="disabledOptions"
|
||||
optionPlaceholders={["Disabled option 1", "Disabled option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Minimum Options (2)</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="minOptions"
|
||||
optionPlaceholders={["Required option 1", "Required option 2", "Optional option"]}
|
||||
defaultNumberOfOptions={3}
|
||||
minOptions={2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { NavigationItem } from "@calcom/ui/components/navigation";
|
||||
|
||||
export const ItemsExample: React.FC = () => (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-4 md:w-64">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-emphasis mb-2 text-sm font-medium">Basic Items</h3>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Event Types",
|
||||
href: "#event-types",
|
||||
icon: "link",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Availability",
|
||||
href: "#availability",
|
||||
icon: "clock",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-emphasis mb-2 text-sm font-medium">With Badge</h3>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Bookings",
|
||||
href: "#bookings",
|
||||
icon: "calendar",
|
||||
badge: <Badge variant="blue">3</Badge>,
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Teams",
|
||||
href: "#teams",
|
||||
icon: "users",
|
||||
badge: <Badge variant="gray">New</Badge>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-emphasis mb-2 text-sm font-medium">Active State</h3>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Event Types",
|
||||
href: "#event-types",
|
||||
icon: "link",
|
||||
isCurrent: true,
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Availability",
|
||||
href: "#availability",
|
||||
icon: "clock",
|
||||
badge: <Badge variant="blue">3</Badge>,
|
||||
isCurrent: true,
|
||||
}}
|
||||
/>
|
||||
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Apps",
|
||||
href: "#apps",
|
||||
icon: "grid-3x3",
|
||||
isCurrent: true,
|
||||
isExpanded: true,
|
||||
child: [
|
||||
{
|
||||
name: "App Store",
|
||||
href: "#apps/store",
|
||||
isCurrent: true,
|
||||
},
|
||||
{
|
||||
name: "Installed Apps",
|
||||
href: "#apps/installed",
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
title: Navigation
|
||||
airtableId: recwHio1ktKQOdXGL
|
||||
---
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
// import { SidebarExample } from "./navigation.sidebar.tsx"
|
||||
import { ItemsExample } from "./navigation.items.tsx"
|
||||
|
||||
{/* ## Sidebar Navigation
|
||||
|
||||
Full sidebar navigation with logo, sections, and footer.
|
||||
|
||||
<SidebarExample/> */}
|
||||
|
||||
## Navigation Items
|
||||
|
||||
Individual navigation items with different states and variants.
|
||||
|
||||
<ItemsExample/>
|
||||
@@ -1,204 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { NavigationItem } from "@calcom/ui";
|
||||
|
||||
export const SidebarExample: React.FC = () => {
|
||||
const [isDeelExpanded, setIsDeelExpanded] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="bg-muted border-muted flex w-60 flex-col border p-3">
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Event types",
|
||||
href: "#event-types",
|
||||
icon: "link",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Bookings",
|
||||
href: "#bookings",
|
||||
icon: "users",
|
||||
isCurrent: true,
|
||||
isExpanded: true,
|
||||
child: [
|
||||
{ name: "Label", href: "#label-1" },
|
||||
{ name: "Label", href: "#label-2" },
|
||||
{ name: "Label", href: "#label-3" },
|
||||
{ name: "Label", href: "#label-4" },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Insights",
|
||||
href: "#insights",
|
||||
icon: "chart-line",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Routing forms",
|
||||
href: "#routing-forms",
|
||||
icon: "shuffle",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Workflows",
|
||||
href: "#workflows",
|
||||
icon: "zap",
|
||||
}}
|
||||
/>
|
||||
<div className="text-subtle mt-4 px-2 text-sm">Manage</div>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Members",
|
||||
href: "#members",
|
||||
icon: "users",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Apps",
|
||||
href: "#apps",
|
||||
icon: "grid-3x3",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Availability",
|
||||
href: "#availability",
|
||||
icon: "clock",
|
||||
}}
|
||||
/>
|
||||
<div className="mt-auto" />
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Help",
|
||||
href: "#help",
|
||||
icon: "circle-help",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Settings",
|
||||
href: "#settings",
|
||||
icon: "settings",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-muted border-muted flex w-60 flex-col border p-3">
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Back",
|
||||
href: "#back",
|
||||
icon: "chevron-left",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Appearance",
|
||||
href: "#appearance",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Out of office",
|
||||
href: "#out-of-office",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Security",
|
||||
icon: "shield",
|
||||
isExpanded: true,
|
||||
child: [
|
||||
{
|
||||
name: "Password",
|
||||
href: "#password",
|
||||
},
|
||||
{
|
||||
name: "Impersonation",
|
||||
href: "#impersonation",
|
||||
},
|
||||
{
|
||||
name: "Two factor auth",
|
||||
href: "#2fa",
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Billing",
|
||||
icon: "credit-card",
|
||||
isExpanded: true,
|
||||
child: [
|
||||
{
|
||||
name: "Manage billing",
|
||||
href: "#manage-billing",
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Developer",
|
||||
icon: "code",
|
||||
isExpanded: true,
|
||||
child: [
|
||||
{
|
||||
name: "Webhooks",
|
||||
href: "#webhooks",
|
||||
},
|
||||
{
|
||||
name: "API Keys",
|
||||
href: "#api-keys",
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Teams",
|
||||
icon: "users",
|
||||
}}
|
||||
/>
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Deel",
|
||||
icon: isDeelExpanded ? "chevron-down" : "chevron-right",
|
||||
isExpanded: isDeelExpanded,
|
||||
onToggle: () => setIsDeelExpanded(!isDeelExpanded),
|
||||
child: [
|
||||
{ name: "Profile", href: "#profile" },
|
||||
{ name: "Members", href: "#members" },
|
||||
{ name: "Event Types", href: "#event-types" },
|
||||
{ name: "Appearance", href: "#appearance" },
|
||||
{ name: "Billing", href: "#billing" },
|
||||
{ name: "Booking Limits", href: "#booking-limits" },
|
||||
{ name: "Attributes", href: "#attributes" },
|
||||
{ name: "Segments", href: "#segments", isCurrent: true },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
<NavigationItem
|
||||
item={{
|
||||
name: "Add team",
|
||||
href: "#add-team",
|
||||
icon: "plus",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
title: Pagination
|
||||
---
|
||||
|
||||
import { BasicExample, CustomPageSizesExample, LargeDatasetExample, WithCallbacksExample } from "./pagination";
|
||||
|
||||
# Pagination
|
||||
|
||||
The Pagination component provides a consistent way to handle data pagination across the application. It includes rows per page selection and navigation controls.
|
||||
|
||||
## Installation
|
||||
|
||||
```ts
|
||||
import { Pagination } from "@calcom/ui";
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
A basic pagination component with default settings.
|
||||
|
||||
<BasicExample />
|
||||
|
||||
### Custom Page Sizes
|
||||
|
||||
Customize the available page size options.
|
||||
|
||||
<CustomPageSizesExample />
|
||||
|
||||
### With Navigation Callbacks
|
||||
|
||||
Use `onNext` and `onPrevious` callbacks to perform additional actions when navigating pages.
|
||||
|
||||
<WithCallbacksExample />
|
||||
|
||||
### Large Dataset
|
||||
|
||||
The pagination component handles large datasets efficiently.
|
||||
|
||||
<LargeDatasetExample />
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Pagination } from "@calcom/ui/components/pagination";
|
||||
|
||||
export const BasicExample = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(25);
|
||||
const totalItems = 61;
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="w-full">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomPageSizesExample = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const totalItems = 25;
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="w-full">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
pageSizeOptions={[5, 10, 15, 25]}
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCallbacksExample = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(25);
|
||||
const totalItems = 100;
|
||||
|
||||
const handleNext = () => {
|
||||
console.log("Next page clicked");
|
||||
};
|
||||
|
||||
const handlePrevious = () => {
|
||||
console.log("Previous page clicked");
|
||||
};
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="w-full">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onNext={handleNext}
|
||||
onPrevious={handlePrevious}
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const LargeDatasetExample = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(25);
|
||||
const totalItems = 1000;
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="w-full">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalItems={totalItems}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { RangeSlider } from "@calcom/ui/components/form";
|
||||
|
||||
export const BasicExample: React.FC = () => {
|
||||
const [singleValue, setSingleValue] = useState([50]);
|
||||
const [rangeValue, setRangeValue] = useState([20, 80]);
|
||||
const [steppedValue, setSteppedValue] = useState([25]);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6 md:w-80">
|
||||
<div className="w-full max-w-[300px] space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-default text-sm">Single Value: {singleValue[0]}%</span>
|
||||
</div>
|
||||
<RangeSlider
|
||||
value={singleValue}
|
||||
onValueChange={setSingleValue}
|
||||
max={100}
|
||||
step={1}
|
||||
aria-label="Single value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-[300px] space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-default text-sm">
|
||||
Range: {rangeValue[0]}% - {rangeValue[1]}%
|
||||
</span>
|
||||
</div>
|
||||
<RangeSlider
|
||||
value={rangeValue}
|
||||
onValueChange={setRangeValue}
|
||||
max={100}
|
||||
step={1}
|
||||
aria-label="Range"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-[300px] space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-default text-sm">Step Size 25: {steppedValue[0]}%</span>
|
||||
</div>
|
||||
<RangeSlider
|
||||
value={steppedValue}
|
||||
onValueChange={setSteppedValue}
|
||||
max={100}
|
||||
step={25}
|
||||
aria-label="Stepped"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-[300px] space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-default text-sm">Disabled: 50%</span>
|
||||
</div>
|
||||
<RangeSlider value={[50]} max={100} step={1} disabled aria-label="Disabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
title: Range Slider
|
||||
airtableId: recPe8JPnGCVmjROA
|
||||
---
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { Row } from "@/app/components/row";
|
||||
|
||||
import { BasicExample } from "./rangeslider.basic.tsx";
|
||||
import { PopoverExample } from "./rangeslider.popover";
|
||||
|
||||
## Basic
|
||||
|
||||
Basic range slider for selecting numeric values.
|
||||
|
||||
<BasicExample />
|
||||
|
||||
## With Popover
|
||||
|
||||
Range slider with a popover showing the current value.
|
||||
|
||||
<PopoverExample />
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { RangeSliderPopover } from "@calcom/ui/components/form";
|
||||
|
||||
export const PopoverExample: React.FC = () => {
|
||||
const [defaultRange, setDefaultRange] = useState([15, 30]);
|
||||
const [customRange, setCustomRange] = useState([5, 20]);
|
||||
const [largeRange, setLargeRange] = useState([0, 100]);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-6 md:w-80">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-emphasis text-sm font-medium">Default Range (15-30 mins)</h3>
|
||||
<RangeSliderPopover
|
||||
triggerText="Total time"
|
||||
value={defaultRange}
|
||||
onChange={setDefaultRange}
|
||||
min={0}
|
||||
max={48}
|
||||
badgeVariant="default"
|
||||
inputSuffix="mins"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-emphasis text-sm font-medium">Custom Range with Success Badge (5-20 mins)</h3>
|
||||
<RangeSliderPopover
|
||||
triggerText="Meeting duration"
|
||||
value={customRange}
|
||||
onChange={setCustomRange}
|
||||
min={5}
|
||||
max={20}
|
||||
badgeVariant="success"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-emphasis text-sm font-medium">Large Range with Warning Badge (0-100 mins)</h3>
|
||||
<RangeSliderPopover
|
||||
triggerText="Extended duration"
|
||||
value={largeRange}
|
||||
onChange={setLargeRange}
|
||||
min={0}
|
||||
max={100}
|
||||
step={5}
|
||||
badgeVariant="warning"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<h4 className="text-default text-sm font-medium">Selected Values:</h4>
|
||||
<pre className="text-emphasis bg-subtle mt-2 rounded-md p-4 text-sm">
|
||||
{JSON.stringify(
|
||||
{
|
||||
defaultRange,
|
||||
customRange,
|
||||
largeRange,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const BasicExample = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
title="Basic Section"
|
||||
description="This is a basic section with a title and description">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>This is the main content of the section.</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const BasicSection = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
title="Basic Section"
|
||||
description="This is a basic section with a title and description"
|
||||
>
|
||||
<Section.HeaderRight>
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.HeaderRight>
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>This is the main content of the section.</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
title: Section
|
||||
---
|
||||
import { Section } from "@calcom/ui/components/section"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { BasicExample } from "./section.basic"
|
||||
import { WithIconExample } from "./section.withIcon"
|
||||
import { SubSectionExample } from "./section.subsection"
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The Section component provides a structured way to organize content with a consistent visual hierarchy. It includes several subcomponents for different parts of the section.
|
||||
|
||||
<BasicExample/>
|
||||
|
||||
## With Icon
|
||||
|
||||
Sections can include an icon in the header to provide visual context.
|
||||
|
||||
<WithIconExample/>
|
||||
|
||||
## SubSections
|
||||
|
||||
Sections can contain subsections for organizing related content in a nested structure.
|
||||
|
||||
<SubSectionExample/>
|
||||
|
||||
## Components
|
||||
|
||||
The Section component is composed of several subcomponents:
|
||||
|
||||
- `Section` - The root container
|
||||
- `Section.Header` - Container for the section header
|
||||
- `Section.Title` - The section title
|
||||
- `Section.Description` - Optional description text
|
||||
- `Section.Content` - The main content area
|
||||
- `Section.SubSection` - A nested section
|
||||
- `Section.SubSectionHeader` - Header for a subsection
|
||||
- `Section.SubSectionContent` - Content area for a subsection
|
||||
@@ -1,151 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Input, Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const SubSectionExample = () => {
|
||||
const [isMainOpen, setIsMainOpen] = useState(true);
|
||||
const [isFirstSubsectionOpen, setIsFirstSubsectionOpen] = useState(true);
|
||||
const [isSecondSubsectionOpen, setIsSecondSubsectionOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Subsections"
|
||||
description="This section contains nested subsections">
|
||||
<Switch size="sm" checked={isMainOpen} onCheckedChange={setIsMainOpen} />
|
||||
</Section.Header>
|
||||
{isMainOpen && (
|
||||
<Section.Content>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="zap" title="Automatic Sync (Justify Start)" justify="start">
|
||||
<Switch size="sm" />
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="user" title="Create new records for guests added">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isFirstSubsectionOpen}
|
||||
onCheckedChange={setIsFirstSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isFirstSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<p>This is the content of the first subsection.</p>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="settings" title="Open Settings">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isSecondSubsectionOpen}
|
||||
onCheckedChange={setIsSecondSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isSecondSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<div className="flex gap-3 px-3 py-1.5">
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Name</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Type</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Value</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">When to Write</div>
|
||||
</div>
|
||||
<Section.SubSectionNested>
|
||||
<div className="flex gap-3">
|
||||
<Input value="Customer Name" className="w-full" />
|
||||
<Input value="Text" className="w-full" />
|
||||
<Input value="John Doe" className="w-full" />
|
||||
<Input value="On Create" className="w-full" />
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubSectionExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const SectionWithSubsections = () => {
|
||||
const [isMainOpen, setIsMainOpen] = useState(true);
|
||||
const [isFirstSubsectionOpen, setIsFirstSubsectionOpen] = useState(true);
|
||||
const [isSecondSubsectionOpen, setIsSecondSubsectionOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Subsections"
|
||||
description="This section contains nested subsections">
|
||||
<Switch size="sm" checked={isMainOpen} onCheckedChange={setIsMainOpen} />
|
||||
</Section.Header>
|
||||
{isMainOpen && (
|
||||
<Section.Content>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="zap" title="Automatic Sync (Justify Start)" justify="start">
|
||||
<Switch size="sm" />
|
||||
</Section.SubSectionHeader>
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="user" title="Create new records for guests added">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isFirstSubsectionOpen}
|
||||
onCheckedChange={setIsFirstSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isFirstSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<p>This is the content of the first subsection.</p>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
<Section.SubSection>
|
||||
<Section.SubSectionHeader icon="settings" title="Open Settings">
|
||||
<Switch
|
||||
size="sm"
|
||||
checked={isSecondSubsectionOpen}
|
||||
onCheckedChange={setIsSecondSubsectionOpen}
|
||||
/>
|
||||
</Section.SubSectionHeader>
|
||||
{isSecondSubsectionOpen && (
|
||||
<Section.SubSectionContent>
|
||||
<div className="flex gap-3 px-3 py-1.5">
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Name</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Field Type</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">Value</div>
|
||||
<div className="text-subtle w-full text-sm font-medium">When to Write</div>
|
||||
</div>
|
||||
<Section.SubSectionNested>
|
||||
<div className="flex gap-3">
|
||||
<Input value="Customer Name" className="w-full" />
|
||||
<Input value="Text" className="w-full" />
|
||||
<Input value="John Doe" className="w-full" />
|
||||
<Input value="On Create" className="w-full" />
|
||||
</div>
|
||||
</Section.SubSectionNested>
|
||||
</Section.SubSectionContent>
|
||||
)}
|
||||
</Section.SubSection>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -1,63 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
|
||||
export const WithIconExample = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Icon"
|
||||
description="This section includes an icon in the header. Toggle to see auto-animation.">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>
|
||||
This section demonstrates how to include an icon in the header and uses FormKit auto-animate for
|
||||
smooth transitions when toggling content visibility.
|
||||
</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithIconExampleSnippet = `
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Section } from "@calcom/ui/components/section";
|
||||
import { Switch } from "@calcom/ui/components/form";
|
||||
|
||||
const SectionWithIcon = () => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section.Header
|
||||
icon="calendar"
|
||||
title="Section with Icon"
|
||||
description="This section includes an icon in the header. Toggle to see auto-animation.">
|
||||
<Switch size="sm" checked={isOpen} onCheckedChange={setIsOpen} />
|
||||
</Section.Header>
|
||||
{isOpen && (
|
||||
<Section.Content>
|
||||
<p>
|
||||
This section demonstrates how to include an icon in the header and uses FormKit auto-animate for
|
||||
smooth transitions when toggling content visibility.
|
||||
</p>
|
||||
</Section.Content>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
`;
|
||||
@@ -1,89 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { Select } from "@calcom/ui/components/form";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
const countries = [
|
||||
{ value: "us", label: "United States" },
|
||||
{ value: "uk", label: "United Kingdom" },
|
||||
{ value: "ca", label: "Canada" },
|
||||
{ value: "fr", label: "France" },
|
||||
{ value: "de", label: "Germany" },
|
||||
{ value: "it", label: "Italy" },
|
||||
{ value: "es", label: "Spain" },
|
||||
{ value: "au", label: "Australia" },
|
||||
{ value: "jp", label: "Japan" },
|
||||
{ value: "br", label: "Brazil" },
|
||||
];
|
||||
|
||||
// Simulate an API call with delay
|
||||
const searchCountries = (query: string) => {
|
||||
return new Promise<{ value: string; label: string }[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
const filtered = countries.filter((country) =>
|
||||
country.label.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
resolve(filtered);
|
||||
}, 1000); // Simulate network delay
|
||||
});
|
||||
};
|
||||
|
||||
export const AsyncExample: React.FC = () => {
|
||||
const [asyncOptions, setAsyncOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleValueChange = (newValue: unknown, actionMeta: { action: string }) => {
|
||||
showToast(`Selected: ${JSON.stringify(newValue)}, Action: ${actionMeta.action}`, "success");
|
||||
};
|
||||
|
||||
const loadOptions = useCallback(async (inputValue: string) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const results = await searchCountries(inputValue);
|
||||
setAsyncOptions(results);
|
||||
} catch (error) {
|
||||
showToast("Error loading options", "error");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-4 md:w-80">
|
||||
<Select
|
||||
options={asyncOptions}
|
||||
onInputChange={(value) => {
|
||||
if (value) {
|
||||
loadOptions(value);
|
||||
}
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
placeholder="Search for a country..."
|
||||
noOptionsMessage={({ inputValue }) =>
|
||||
inputValue ? "No countries found" : "Start typing to search..."
|
||||
}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
<Select
|
||||
options={asyncOptions}
|
||||
onInputChange={(value) => {
|
||||
if (value) {
|
||||
loadOptions(value);
|
||||
}
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
placeholder="Search for a country... (small)"
|
||||
noOptionsMessage={({ inputValue }) =>
|
||||
inputValue ? "No countries found" : "Start typing to search..."
|
||||
}
|
||||
onChange={handleValueChange}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Select } from "@calcom/ui/components/form";
|
||||
|
||||
const options = [
|
||||
{ value: "chocolate", label: "Chocolate" },
|
||||
{ value: "strawberry", label: "Strawberry" },
|
||||
{ value: "vanilla", label: "Vanilla" },
|
||||
{ value: "mint", label: "Mint" },
|
||||
{ value: "coffee", label: "Coffee" },
|
||||
];
|
||||
|
||||
export const BasicExample: React.FC = () => {
|
||||
const [singleValue, setSingleValue] = useState<{ value: string; label: string } | null>(null);
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<div className="space-y-4 md:w-80">
|
||||
<Select
|
||||
options={options}
|
||||
value={singleValue}
|
||||
onChange={(newValue) => setSingleValue(newValue)}
|
||||
isClearable
|
||||
placeholder="Choose a flavor..."
|
||||
size="md"
|
||||
/>
|
||||
<Select
|
||||
options={options}
|
||||
value={singleValue}
|
||||
onChange={(newValue) => setSingleValue(newValue)}
|
||||
isClearable
|
||||
placeholder="Small size select..."
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user