Compare commits

..
10 Commits
Author SHA1 Message Date
emrysal 37c26793f5 chore: release v5.9.2
Post release / release (push) Canceled after 0s
Release Docker / Release AMD64 (push) Canceled after 0s
Release Docker / Release ARM (push) Canceled after 0s
2025-11-20 10:33:06 +00:00
sean-brydon 956f81fab3 chore: tidy up onboarding with new animations and illustrations (#25124)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Revamps onboarding with subtle route transitions, new org/teams illustrations, and clearer invite flows with real-time previews. Adds “Skip for now” on invites and guardrails to avoid incomplete team creation.

- **New Features**
  - Added email invite pages for organizations and teams with shared EmailInviteForm and RoleSelector.
  - Built a live invite browser preview that renders a 3×3 grid and updates as you type; works for orgs and teams.
  - Applied route-keyed framer-motion transitions to layout, cards, and browser views for smoother page changes.
  - Continuation prompt now detects saved org or team and routes to the right next step; localized copy.
  - Added loading state to plan selection to prevent double navigation.
  - Refreshed Organizations welcome modal with a new animated ring illustration and better scrolling.
  - Added “Skip for now” to org and team invite-by-email steps to proceed without invites.
  - Calendar browser preview now focuses on a time window around “now” for a more realistic demo.
  - Added an optional floating footer to keep actions visible on long, scrollable lists.

- **Refactors and Fixes**
  - Unified invite browser and replaced the org preview on the org email step.
  - Split and simplified legacy invite views; team and org routes are cleaner and easier to extend.
  - Streamlined the team invite step by removing a redundant action; non-admins are redirected to the email invite page.
  - Prevented creating teams with empty details by redirecting back to team details.
  - Minor UI cleanup: tighter borders, padding, and mobile max-heights across onboarding screens.
  - Hide team select on org invites when no teams exist.

<sup>Written for commit ae7f5277ab998560ae6e2f3f9144852a7b4959a7. Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2025-11-20 10:20:38 +00:00
1dcd54aab7 feat: add avatarUrl and bio fields to /me endpoint response (#25224)
* feat: add avatarUrl to /v2/me endpoint response

- Add avatarUrl field to userSchemaResponse schema in packages/platform/types/me.ts
- Update e2e tests to verify avatarUrl is returned in GET and PATCH /v2/me responses
- Field is nullable to match User model in Prisma schema
- Fix pre-existing lint warnings by removing 'as any' type assertions in test file

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* feat: add avatarUrl to MeOutput DTO for OpenAPI docs

- Add avatarUrl field to MeOutput class in apps/api/v2/src/ee/me/outputs/me.output.ts
- Field is nullable to match the Zod schema and Prisma model
- This ensures OpenAPI documentation will include avatarUrl when regenerated

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* feat: add bio field to /v2/me endpoint response

- Add bio field to userSchemaResponse Zod schema in packages/platform/types/me.ts
- Add bio field to MeOutput NestJS DTO in apps/api/v2/src/ee/me/outputs/me.output.ts
- Update e2e tests to verify bio is returned in both GET and PATCH responses
- Field is nullable to match the User model in Prisma schema

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: chauhan_s <somaychauhan98@gmail.com>
2025-11-19 23:24:31 +00:00
68d3d9d964 docs: add Stripe Connect atom documentation (#24791)
* docs: add Stripe Connect atom documentation

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* docs: restructure Stripe Connect docs with Steps component

- Add Steps component explaining two ways to connect Stripe
- Step 1: Direct Stripe Connect atom usage
- Step 2: Event Type Settings payment tab usage
- Include code examples for both methods
- Add image placeholders for visual demonstrations
- Move advanced usage examples under separate section

Co-Authored-By: rajiv@cal.com <sahalrajiv6900@gmail.com>

* chore: add link to stripe connect atom

* chore: update docs

* chore: implement feedback

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-11-19 23:12:05 +00:00
717a26f223 fix: prevent bulk update of locked locations in child managed event types (#24978)
* fix: prevent bulk update of locked locations in child managed event types

- Filter out child managed event types with locked locations in getBulkUserEventTypes
- Add validation in bulkUpdateEventsToDefaultLocation to prevent updating locked fields
- Implements defense in depth with validation at multiple layers

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Abstract filtering logic

* test: add comprehensive tests for bulk location update filtering

- Add unit tests for filterEventTypesWhereLocationUpdateIsAllowed
- Add unit tests for bulkUpdateEventsToDefaultLocation
- Add integration tests for getBulkUserEventTypes
- Fix bug: change unlockedFields?.locations check from !== undefined to === true
  This ensures that locations: false is properly treated as locked, addressing
  the security issue identified in PR review comments

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: filter locked managed event types on app installation page

- Add parentId to eventTypeSelect in getEventTypes function
- Apply filterEventTypesWhereLocationUpdateIsAllowed to both team and user event types
- Only filter when isConferencing is true to avoid affecting other app types
- Fixes issue where locked managed event types were showing in the event type selection list on /apps/installation/event-types page

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix(embed-react): remove obsolete availabilityLoaded event listener

The availabilityLoaded event does not exist in the EventDataMap type system
in embed-core. This code was causing 5 TypeScript errors in CI:
- Type 'availabilityLoaded' does not satisfy constraint 'keyof EventDataMap'
- 'data' is of type 'unknown' (2 occurrences)
- Type 'availabilityLoaded' is not assignable to action union (2 occurrences)

Since this is an example file and the event is not defined in the type system,
removing this obsolete code resolves the type errors.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: correct Prisma type for metadata in test helper function

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: use flexible PrismaLike type for better test compatibility

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: properly type mock Prisma objects in test files

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: properly mock Prisma methods in test file

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Filter out metadata

* Undo change in embed file

* Address feedback

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-11-19 15:46:05 -05:00
Alex van Andel 6818806c5e chore: No more clientside markdown when importing Checkbox (#25278) 2025-11-19 15:40:37 -03:00
Eunjae Lee dd7f108f08 fix: put booking details and calendar behind feature flag (#25175)
* Revert "fix: revert bookings redesign (#25172)"

This reverts commit 1f102bf3b4.

* add bookings-v3 feature flag

* put things behind a feature flag

* remove no longer needed test

* revert e2e tests

* put back description

* revert AvatarGroup

* apply feedback

* remove "view" booking action

* remove Alert (When the bookings query errors, this branch now renders only the alert and skips the data-table filter/segment controls. Those controls moved into BookingsList, so in error states users can no longer clear or tweak filters to recover from the failure, effectively trapping them behind the alert.)

* address feedback

* revert useMediaQuery
2025-11-19 16:27:13 +01:00
7e4d9e26c6 refactor: implement DI in team billing service and team billing data repository factory (#24803)
* Move TeamBillingRepositories

* WIP refactor team internal billing service

* Remove duplicate billing repository files

* Remove logic check in repository for billing is enabled

* Rename repository to `TeamBillingData`

* Use repository factory in main service

* Fix new import paths

* Rename  to

* Ensure `IS_TEAM_BILLING_ENABLED` is of type boolean

* Rename classes to TeamBillingService and TeamBillingServiceFactory

* Implement DI in `BookingServiceFactory`

* `TeamBillingService` use repository in `getOrgIfNeeded`

* DI `isTeamBillingEnabled` to `TeamBillingServiceFactory`

* Rename files for consistency

* Return stub BillingRepository if billing is not enabled

* Move Stripe billing service to service folder

* Rename file

* `StripeBillingService.getSubscriptionStatus` return `SubscriptionStatus`

* Type fices in StripeBillingService

* Type fix in `stubTeamBillingService`

* DI the `BillingProviderService` into the `TeamBillingService`

* Implement DI in `skipTeamTrials.handler`

* Implement DI for team billing in `inviteMember.handler`

* `skipTeamTrials.handler` use `team.isOrganization`

* Implement DI for billing in `hasActiveTeamPlan.handler`

* Type fixes

* Implement DI in `bulkDeleteUsers.handler`

* Implement `BillingProviderServiceFactory` in `updateProfile.handler`

* Implment `BillingProviderServiceFactory` in `buyCredits.handler`

* Fix import in `stripeCustomer.handler`

* Add a constructor to `teamBillingServiceFactory`

* Add DI to `PrismaTeamBillingRepository`

* Add DI to `StripeBillingService`

* Implement singleton in `BillingProviderServiceFactory`

* Add DI folder and contents to billing folder

* Use `getTeamBillingServiceFactory` in `inviteMember.handler`

* Add `saveTeamBilling` method to `ITeamBillingService`

* Implement DI in new team route

* Implement DI in `teamService`

* Implement DI in `OrganizationPaymentService`

* Implement DI in `credit-service`

* In `StripeBillingService` remove `static` from status methods

* Implemnt DI in `_invoice.paid.org`

* Refactor `hasActiveTeamPlan` to use `getTeamBillingFactory`

* Refactor `skipTeamTrials` to use `getTeamBillingFactory`

* Refactor `skipTeamTrials` to use `getTeamBillingServiceFactory`

* `stripeCustomer.handler` to use `getBillingProviderService`

* Remove old factories

* Type fix

* Remove unused factory

* Refactor `updateProfile.handler` to use `getBillingProviderService`

* Change name to `TeamBillingDataRepositoryFactory`

* Type Prisma return in `prisma.module`

* Type fix

* Refactor `buyCredits.handler` to use `getBillingProviderService`

* Refactor `credit-service` to use billing DI containers

* Type fix

* Add `getTeamBillingDataRepository`

* Refactor `_invoice.paid.org` to use DI container

* Refactor `_customer.subscription.deleted.team-plan` to use DI container

* Refactor `calcomHandler` to use DI container

* Refactor `getCustomerAndCheckoutSession` to use DI container

* Refactor `verify-email` to use DI containers

* Refactor `api/create/route` to use DI container

* Refactor downgradeUsers to use DI container

* Type fix

* Clean up console.logs

* Add await to `this.billingRepository.create` in `saveTeamBilling`

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Fix type errors

* Address comments

* fix: update tests to work with new DI pattern

- Update teamBillingService.test.ts to properly inject DI dependencies
- Remove unused billingModule import and mock
- Fix import naming in teamService.integration-test.ts (remove unused rename)
- Fix import path for TeamBillingPublishResponseStatus

All tests now properly mock IBillingProviderService, ITeamBillingDataRepository,
and IBillingRepository instead of using the old BillingRepositoryFactory pattern.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add compatibility layer and env setup for unit tests

- Add STRIPE_PRIVATE_KEY dummy value to vitest.config.ts to prevent DI module errors
- Fix import paths in credit-service.test.ts (StripeBillingService, TeamBillingService)
- Create compatibility barrel at packages/features/ee/billing/teams/index.ts for test mocking

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: update unit tests to mock DI container properly

- Update teamService.test.ts to mock getTeamBillingServiceFactory() instead of TeamBilling.findAndInit
- Update teamService.alternative.test.ts to mock DI container
- Update credit-service.test.ts to mock getBillingProviderService() and use SubscriptionStatus enum values
- Update OrganizationPaymentService.test.ts to mock DI container instead of direct StripeBillingService import
- Remove all 'as any' type casting to comply with Cal.com coding standards
- Fix unused variable warnings by prefixing with underscore

All 53 tests now passing (16 + 1 + 30 + 6)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: update remaining unit tests to use DI pattern

- Fix StripeBillingService.test.ts to inject mock Stripe client directly
- Fix teamBillingFactory.test.ts to mock getTeamBillingServiceFactory() from DI container
- Fix skipTeamTrials.test.ts to mock DI container and use SubscriptionStatus enum

All 11 previously failing tests now pass (5 + 5 + 1)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Undo changes made to Prisma module

* fix: address test-related PR comments

- Fix OrganizationPaymentService.test.ts mock path from @calcom/ee to @calcom/features/ee
- Refactor teamBillingFactory.test.ts to test real factory logic instead of mocking container
- Remove duplicate teamBillingService.test..ts file with incorrect double-dot filename

All three test files now pass successfully with proper DI patterns.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Address feedback

* fix: update teamService integration test to mock new DI factory pattern

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: remove duplicate imports in credit-service.test.ts

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Remove unused index file

* `getBySubscriptionId` to return team or null

* Address feedback

* Merge fix

* Refactor file names

* fix: correct mockStripe variable name to stripeMock in StripeBillingService.test.ts

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: update internal-team-billing.test.ts to use new DI structure with TeamBillingService

- Replace InternalTeamBilling with TeamBillingService
- Use constructor injection with mock dependencies instead of factory pattern
- Remove BillingRepositoryFactory mock and import
- Update all test cases to use mockBillingProviderService, mockTeamBillingDataRepository, and mockBillingRepository
- Simplify saveTeamBilling tests to focus on repository.create calls
- All 11 tests now pass with the new DI structure

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: update createWithPaymentIntent.handler.test.ts to mock DI container's getBillingProviderService

- OrganizationPaymentService now uses getBillingProviderService() from DI container
- Test was mocking @calcom/features/ee/payments/server/stripe directly, which no longer works
- Added mock for @calcom/features/ee/billing/di/containers/Billing module
- Mock returns fake billing provider that delegates to mockSharedStripe
- Preserves all existing test assertions and helpers
- Fixed lint error by prefixing unused lastCreatedSessionId with underscore
- All 11 tests now pass (1 skipped as expected)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

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

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