feat: v3/UI docs (#20454)

* ui playground

* add types to ui

* push avatar ignore ts and eslint errors

* feat: V3 UI avatar  (#18997)

* avatar playground

* add overflow-hidden back for loading indicator

* update lock

* feat: v3/badge  (#18999)

* badge intro

* fix text on small badge

* add dark mode warning

* feat: v3/button (#19014)

* TOC

* wip buttons

* shadows

* secondary

* semantic colours

* minimal buttons

* update disabled states

* checkbox

* dropdown v3

* load icons

* fix design sections

* fix icon gap

* fix

* remove client component from rsc

* use peer as avatar

* gif

* fix button gap - dropdown iocn

* support start icon in dropdowns

* add alert demo + v3 component + darken semantic text

* base inputs and text field

* wip input

* input field addon/suffix

* toast page

* wip select

* match designs for input and select rounded

* fix

* fix select isloading transform

* fix multi icons transition rotation

* fix group heading

* range slider demo + range slider

* range slider popover design

* v3 dialog

* fix slider thumb size

* wrap in suspense for useSearchParam difference in next14+

* base of tabs + horizontal tabs

* fix select sizing to match input 36px

* fix dark mode hover on button

* target domState class for light/dark so we work on portal elements

* fix default bg on popover

* lightmode toasts + eleveation token

* empty screen

* intro to split button concept

* mass colour updates

* intro to themes

* remove i18n from label with name

* bouncy buttons

* feat: small select

* nav item dumb component

* full demo

* settings demo

* fix tabs

* fix mt

* fix dropdown feedback from loom

* alert fixes mentioned in loom

* push font-cal

* move to fumadocs

* fix avatar

* colors table

* add typeography

* icon + spacing guide

* add border radius and shadow tables

* fix colours and border radius

* remove prose undline via fumas fork

* fix icon buttons sizes

* button group component

* fix horizontal tabs

* tab fixes

* switches

* pagination component

* show toasts components raw

* sync with airtable

* add use client

* 10px on input again

* 10px in select also

* fix input height

* fix files merge

* fix loading state for icon buttons

* fix button fab

* Enable addon to now overflow to next line in DS

* use cal font for dialog text

* add avatar square variant

* fix select padding and menu size

* fix type errors

* fix type errors

* add switch small size and fix type errors

* fix ref types on input

* update web styles

* remove addonFilled to fix type errors

* push dialog fixes

* fix checkbox display

* remove prose from alert examples

* remove prose from all our component examples

* badge rounded-lg on lg size

* update colors

* force w/h on icon buttons instead of inheriting

* remove border-focus

* remove border-focus from tailwind config

* update tokens to match new colours

* add new border subtle tokens and add to toasts

* fix lg button padding

* fix and drastically improve rtl rests

* multi select use badges

* use badge design for select pills

* fix avatar alignment

* fix tests and remove redudant ones

* fix tests and remove redudant ones

* update atoms globals css

* extract tokens.css

* extract app.css

* force flex for atoms

* add @calcom/ui/css

* revert ui playground styles abstraction

* fix input focus states

* fix focus states for buttons

* feat: v3/app work (#19233)

* globals.css for app + event types listing search

* few app wide NITS

* fix select foucs within styling

* fix formbuilder switch styles

* fix disabled state of nested inputs

* workflow fixes

* webhook form fixes

* i18n on tabs info

* booking filters + tab

* fix filters spacing

* make first item so we get correct border radius if not default

* round radio group in create event types + make button small

* profile page

* update editor br

* button nits in existing places in the app

* fix timezone select and multilist in guest form

* fix text area on public booking page

* size small event types create

* margin on routing filter

* filters improvments

* routing insights fixes

* fix date filter range

* ooo fixes

* fix teams/members bulk actions

* create team UI

* i18n in useTabs instead of in tab component now

* Add event type setup border to availability

* fix bulk actions on org members table

* onboarding fixes

* fix input types with ref stripe comparision

* i18n fixes for platform

* update globals of atoms

* fix form builder firstname/lastname switch + correct footer useage

* fix select

* Revert "add @calcom/ui/css"

This reverts commit 0dd8f9012907a278cfede4a98f289cb0744c9244.

* revert tokens css

* Revert "extract app.css"

This reverts commit 10b998604816ad3310c24b36a02a638dabc5be24.

* linear nits

* workflow dropdown

* fix page theme heading sizes to match cards

* fix stepper for team creation

* adjust margin to FilterSearchField

* fix ooo icon

* fix booker and dark mode due to darkgray 50

* more dark mode fixes

* add size prop

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Morgan Vernay <morgan@cal.com>

* update package name

* update package json lost in merge

* update package json lost in merge

* re-genrate source

* fix imports

* imports

* fix merge issues and imports

* restore globals css

* add source to git ignore

* remove old source files

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
sean-brydon
2025-04-02 09:10:07 +01:00
committed by GitHub
co-authored by Eunjae Lee Morgan Vernay
parent bc9d76e7da
commit 1fb778ae78
125 changed files with 11714 additions and 64 deletions
+44
View File
@@ -0,0 +1,44 @@
# 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/*
+36
View File
@@ -0,0 +1,36 @@
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.
+9
View File
@@ -0,0 +1,9 @@
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>;
}
+12
View File
@@ -0,0 +1,12 @@
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 font-bold uppercase leading-none tracking-tight md:text-8xl">
Open UI for absolutely
<br /> Everyone
</h1>
</div>
</div>
);
}
@@ -0,0 +1,16 @@
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,
});
@@ -0,0 +1,21 @@
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";
@@ -0,0 +1,57 @@
"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-gray-1 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>
);
};
@@ -0,0 +1,7 @@
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";
@@ -0,0 +1,56 @@
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;
}
+17
View File
@@ -0,0 +1,17 @@
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
+26
View File
@@ -0,0 +1,26 @@
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",
},
],
};
+34
View File
@@ -0,0 +1,34 @@
import { RootProvider } from "fumadocs-ui/provider";
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./global.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
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">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}
+13
View File
@@ -0,0 +1,13 @@
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, []),
});
@@ -0,0 +1,19 @@
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>
);
}
@@ -0,0 +1,65 @@
"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>
</>
);
};
@@ -0,0 +1,371 @@
"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-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>
</>
);
};
@@ -0,0 +1,126 @@
"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>
</>
);
};
@@ -0,0 +1,45 @@
"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>
</>
);
};
@@ -0,0 +1,103 @@
"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>
</>
);
};
@@ -0,0 +1,109 @@
"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>
</>
);
};
@@ -0,0 +1,32 @@
---
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 />
@@ -0,0 +1,144 @@
---
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]} />
@@ -0,0 +1,28 @@
"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>
);
@@ -0,0 +1,22 @@
"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>
);
@@ -0,0 +1,24 @@
"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>
);
@@ -0,0 +1,25 @@
"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>
);
@@ -0,0 +1,42 @@
---
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/>
@@ -0,0 +1,21 @@
"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>
);
@@ -0,0 +1,22 @@
"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>
);
@@ -0,0 +1,25 @@
"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>
);
@@ -0,0 +1,18 @@
"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>
);
@@ -0,0 +1,42 @@
---
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"
## Size Variations
Avatars come in different sizes to suit various use cases.
<SizesExample/>
## 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/>
@@ -0,0 +1,29 @@
"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>
);
@@ -0,0 +1,18 @@
"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>
);
@@ -0,0 +1,33 @@
"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>
);
@@ -0,0 +1,33 @@
"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>
);
@@ -0,0 +1,33 @@
"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>
);
@@ -0,0 +1,42 @@
---
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/>
@@ -0,0 +1,33 @@
"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>
);
@@ -0,0 +1,42 @@
"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>
);
@@ -0,0 +1,63 @@
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>
);
};
@@ -0,0 +1,42 @@
"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>
);
@@ -0,0 +1,42 @@
"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>
);
@@ -0,0 +1,44 @@
"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>
);
};
@@ -0,0 +1,67 @@
---
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
#### Seperated
<ButtonGroupSizes/>
#### Combined
<ButtonGroupSizesCombined/>
@@ -0,0 +1,14 @@
"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>
);
@@ -0,0 +1,43 @@
"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>
);
@@ -0,0 +1,31 @@
"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>
);
@@ -0,0 +1,15 @@
"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>
);
@@ -0,0 +1,16 @@
"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>
);
@@ -0,0 +1,24 @@
"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>
);
@@ -0,0 +1,35 @@
---
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/>
@@ -0,0 +1,33 @@
"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>
);
};
@@ -0,0 +1,37 @@
"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>
);
};
@@ -0,0 +1,56 @@
"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>
);
};
@@ -0,0 +1,58 @@
"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>
);
};
@@ -0,0 +1,35 @@
---
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/>
@@ -0,0 +1,38 @@
"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>
);
@@ -0,0 +1,84 @@
"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>
);
@@ -0,0 +1,88 @@
"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 className="cursor-pointer">
<Button variant="icon" color="secondary" StartIcon="calendar" />
</button>
</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>
);
@@ -0,0 +1,28 @@
---
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/>
@@ -0,0 +1,17 @@
"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>
);
@@ -0,0 +1,37 @@
"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>
);
@@ -0,0 +1,18 @@
"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>
);
@@ -0,0 +1,28 @@
"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>
);
@@ -0,0 +1,41 @@
---
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/>
@@ -0,0 +1,26 @@
"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>
);
@@ -0,0 +1,67 @@
"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>
);
};
@@ -0,0 +1,59 @@
"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>
);
};
@@ -0,0 +1,26 @@
---
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/>
@@ -0,0 +1,53 @@
"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>
);
};
@@ -0,0 +1,35 @@
---
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/>
@@ -0,0 +1,37 @@
"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>
);
@@ -0,0 +1,46 @@
"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>
);
@@ -0,0 +1,31 @@
"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>
);
@@ -0,0 +1,31 @@
"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>
);
@@ -0,0 +1,92 @@
"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>
);
@@ -0,0 +1,20 @@
---
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/>
@@ -0,0 +1,204 @@
"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>
);
};
@@ -0,0 +1,42 @@
---
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 />
@@ -0,0 +1,97 @@
"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>
);
};
@@ -0,0 +1,72 @@
"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>
);
};
@@ -0,0 +1,22 @@
---
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 />
@@ -0,0 +1,71 @@
"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>
);
};
@@ -0,0 +1,89 @@
"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>
);
};
@@ -0,0 +1,41 @@
"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>
);
};
@@ -0,0 +1,27 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { Select, SelectField } 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 DisabledExample: React.FC = () => (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<Select options={options} isDisabled placeholder="This select is disabled..." />
<SelectField
label="Disabled Field"
options={options}
isDisabled
placeholder="This field is disabled..."
/>
</div>
</RenderComponentWithSnippet>
);
@@ -0,0 +1,63 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { SelectField } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/components/toast";
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" },
{ value: "mint", label: "Mint" },
{ value: "coffee", label: "Coffee" },
];
export const FieldExample: React.FC = () => {
const handleValueChange = (newValue: unknown, actionMeta: { action: string }) => {
showToast(`Selected: ${JSON.stringify(newValue)}, Action: ${actionMeta.action}`, "success");
};
return (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<SelectField
label="Flavor"
options={options}
onChange={handleValueChange}
placeholder="Choose a flavor..."
/>
<SelectField
label="Required Field"
options={options}
required
onChange={handleValueChange}
placeholder="This field is required..."
/>
<SelectField label="With Error" options={options} />
</div>
<div className="mt-4 space-y-4 md:w-80">
<SelectField
label="Flavor"
options={options}
onChange={handleValueChange}
placeholder="Choose a flavor... (small)"
size="sm"
/>
<SelectField
label="Required Field"
options={options}
required
onChange={handleValueChange}
placeholder="This field is required... (small)"
size="sm"
/>
<SelectField label="With Error (small)" options={options} size="sm" />
</div>
</RenderComponentWithSnippet>
);
};
@@ -0,0 +1,32 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { Select } from "@calcom/ui/components/form";
const groupedOptions = [
{
label: "Fruits",
options: [
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "orange", label: "Orange" },
],
},
{
label: "Vegetables",
options: [
{ value: "carrot", label: "Carrot" },
{ value: "broccoli", label: "Broccoli" },
{ value: "spinach", label: "Spinach" },
],
},
];
export const GroupedExample: React.FC = () => (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<Select options={groupedOptions} placeholder="Choose food..." />
</div>
</RenderComponentWithSnippet>
);
@@ -0,0 +1,21 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
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 LoadingExample: React.FC = () => (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<Select options={options} isLoading placeholder="Loading options..." />
</div>
</RenderComponentWithSnippet>
);
@@ -0,0 +1,57 @@
---
title: Select
airtableId: reccfLvVzECvskVmu
---
import { RenderComponentWithSnippet } from "@/app/components/render"
import { Row } from "@/app/components/row"
import { BasicExample } from "./select.basic"
import { MultiExample } from "./select.multi"
import { GroupedExample } from "./select.grouped"
import { FieldExample } from "./select.field"
import { DisabledExample } from "./select.disabled"
import { LoadingExample } from "./select.loading"
import { AsyncExample } from "./select.async"
## Basic
Basic select component with single selection and clearable option.
<BasicExample/>
## Multi Select
Select component that allows multiple selections.
<MultiExample/>
## Grouped Options
Select with options grouped into categories.
<GroupedExample/>
## Async Select with Search
Select with asynchronous loading and search functionality.
<AsyncExample/>
{/* ## Select Field
Select component with label and validation support.
<FieldExample/> */}
## Disabled State
Select component in disabled state.
<DisabledExample/>
## Loading State
Select component in loading state.
<LoadingExample/>
@@ -0,0 +1,42 @@
"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 MultiExample: React.FC = () => {
const [multiValue, setMultiValue] = useState<{ value: string; label: string }[]>([]);
return (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<Select
options={options}
value={multiValue}
onChange={(newValue) => setMultiValue(newValue as { value: string; label: string }[])}
isMulti
isClearable
placeholder="Choose multiple flavors..."
/>
<Select
options={options}
value={multiValue}
onChange={(newValue) => setMultiValue(newValue as { value: string; label: string }[])}
isMulti
isClearable
placeholder="Choose multiple flavors... (small)"
size="sm"
/>
</div>
</RenderComponentWithSnippet>
);
};
@@ -0,0 +1,53 @@
---
title: Switch
airtableId: reccfLvVzECvskVmu
---
import { BasicExample, ControlledExample, LabelPositionExample, StatesExample,WithPadding,SwitchSizes } from "./switch";
The Switch component is a toggle that allows users to turn a setting on or off. It's built on top of Radix UI's Switch primitive and includes support for labels, tooltips, and various size options.
## Installation
```ts
import { Switch } from "@calcom/ui";
```
## Examples
### Basic Switch
A simple switch with and without a label.
<BasicExample />
### Switch sizes
A switch can be sized either small or 'base'.
<SwitchSizes />
### Label Position
The label can be positioned either before or after the switch.
<LabelPositionExample />
### States
Switches can be enabled, disabled, or disabled in a checked state.
<StatesExample />
### Controlled Switch
Use state to control the switch's checked state.
<ControlledExample />
### With Padding
Use the padding prop to add padding to the switch.
<WithPadding />
@@ -0,0 +1,87 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { useState } from "react";
import { Switch } from "@calcom/ui/components/form";
export const BasicExample = () => {
const [checked, setChecked] = useState(false);
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch label="Basic switch with label" checked={checked} onCheckedChange={setChecked} />
</div>
</RenderComponentWithSnippet>
);
};
export const LabelPositionExample = () => {
const [checked, setChecked] = useState(false);
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch label="Label on trailing side (default)" checked={checked} onCheckedChange={setChecked} />
<Switch label="Label on leading side" labelOnLeading checked={checked} onCheckedChange={setChecked} />
</div>
</RenderComponentWithSnippet>
);
};
export const StatesExample = () => {
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch label="Enabled switch" checked />
<Switch label="Disabled switch" disabled />
<Switch label="Disabled and checked switch" disabled defaultChecked />
</div>
</RenderComponentWithSnippet>
);
};
export const ControlledExample = () => {
const [checked, setChecked] = useState(false);
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch
label={`Switch is ${checked ? "on" : "off"}`}
checked={checked}
onCheckedChange={setChecked}
/>
</div>
</RenderComponentWithSnippet>
);
};
export const WithPadding = () => {
const [checked, setChecked] = useState(false);
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch
label={`Switch is ${checked ? "on" : "off"}`}
checked={checked}
onCheckedChange={setChecked}
padding
/>
</div>
</RenderComponentWithSnippet>
);
};
export const SwitchSizes = () => {
const [checked, setChecked] = useState(false);
return (
<RenderComponentWithSnippet>
<div className="flex flex-col space-y-4">
<Switch label="Small switch" size="sm" checked={checked} onCheckedChange={setChecked} />
<Switch label="Medium switch" checked={checked} onCheckedChange={setChecked} />
</div>
</RenderComponentWithSnippet>
);
};
@@ -0,0 +1,81 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { useState } from "react";
import { Icon } from "@calcom/ui/components/icon";
import { HorizontalTabs } from "@calcom/ui/components/navigation";
const tabItems = [
{
name: "Overview",
href: "#",
icon: "grid-3x3",
},
{
name: "Settings",
href: "#",
icon: "settings",
},
{
name: "Notifications",
href: "#",
icon: "bell",
info: "3 unread notifications",
},
{
name: "Profile",
href: "#",
avatar: "https://avatars.githubusercontent.com/u/8019099?v=4",
},
] as const;
export const HorizontalExample: React.FC = () => {
const [activeTab, setActiveTab] = useState("Overview");
return (
<RenderComponentWithSnippet>
<div className="space-y-6">
<div>
<h3 className="text-emphasis mb-2 text-sm font-medium">Basic</h3>
<HorizontalTabs
tabs={tabItems.map((item) => ({
...item,
icon: undefined,
onClick: () => setActiveTab(item.name),
isActive: activeTab === item.name,
}))}
/>
</div>
<div>
<h3 className="text-emphasis mb-2 text-sm font-medium">With Icons</h3>
<HorizontalTabs
tabs={tabItems.map((item) => ({
...item,
onClick: () => setActiveTab(item.name),
isActive: activeTab === item.name,
}))}
/>
</div>
<div>
<h3 className="text-emphasis mb-2 text-sm font-medium">With Actions</h3>
<HorizontalTabs
tabs={tabItems.map((item) => ({
...item,
onClick: () => setActiveTab(item.name),
isActive: activeTab === item.name,
}))}
actions={
<button className="hover:text-emphasis text-default ml-auto flex items-center space-x-1 rounded-md px-3 py-[10px] transition">
<Icon name="plus" className="h-4 w-4" />
<span>Add New</span>
</button>
}
/>
</div>
</div>
</RenderComponentWithSnippet>
);
};
@@ -0,0 +1,20 @@
---
title: Tabs
airtableId: recdPvp9q8sfBXrXG
---
import { RenderComponentWithSnippet } from "@/app/components/render"
import { Row } from "@/app/components/row"
import { HorizontalExample } from "./tabs.horizontal"
import { VerticalExample } from "./tabs.vertical"
## Horizontal Tabs
Horizontal tabs for navigation with various styles.
<HorizontalExample/>
## Vertical Tabs
Vertical tabs with nested navigation and additional features.
<VerticalExample/>
@@ -0,0 +1,72 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { useState } from "react";
import type { VerticalTabItemProps } from "@calcom/ui/components/navigation";
import { VerticalTabs } from "@calcom/ui/components/navigation";
const verticalTabItems = [
{
name: "Overview",
href: "#",
icon: "grid-3x3",
},
{
name: "Settings",
href: "#",
icon: "settings",
children: [
{
name: "General",
href: "#",
},
{
name: "Security",
href: "#",
},
{
name: "Notifications",
href: "#",
},
],
},
{
name: "Notifications",
href: "#",
icon: "bell",
},
{
name: "Documentation",
href: "https://docs.example.com",
icon: "book-open",
isExternalLink: true,
},
{
name: "Disabled Tab",
href: "#",
icon: "lock",
disabled: true,
},
] as VerticalTabItemProps[];
export const VerticalExample: React.FC = () => {
const [activeTab, setActiveTab] = useState("Overview");
return (
<RenderComponentWithSnippet>
<div className="max-w-xs space-y-6">
<div>
<h3 className="text-emphasis mb-2 text-sm font-medium">Basic</h3>
<VerticalTabs
tabs={verticalTabItems.map((item) => ({
...item,
onClick: () => setActiveTab(item.name),
isActive: activeTab === item.name,
}))}
/>
</div>
</div>
</RenderComponentWithSnippet>
);
};
@@ -0,0 +1,18 @@
"use client";
import { RenderComponentWithSnippet } from "@/app/components/render";
import { TextAreaField } from "@calcom/ui/components/form";
export const BasicExample: React.FC = () => (
<RenderComponentWithSnippet>
<div className="space-y-4 md:w-80">
<TextAreaField name="message" placeholder="Enter your message..." defaultValue="" />
<TextAreaField
name="description"
placeholder="With default value..."
defaultValue="This is a default value for the textarea component."
/>
</div>
</RenderComponentWithSnippet>
);
@@ -0,0 +1,27 @@
---
title: Textarea
airtableId: recUXs75TMoHNJnZM
---
import { RenderComponentWithSnippet } from "@/app/components/render"
import { Row } from "@/app/components/row"
import { BasicExample } from "./textarea.basic.tsx"
import { StatesExample } from "./textarea.states.tsx"
import { SizesExample } from "./textarea.sizes.tsx"
## Basic
Basic textarea component for multiline text input.
<BasicExample/>
## States
Textarea in different states: default, disabled, and readonly.
<StatesExample/>
## Sizes
Textarea with different height configurations.
<SizesExample/>

Some files were not shown because too many files have changed in this diff Show More