feat: v3 of UI components (#19136)

* ui playground

* add types to ui

* push avatar ignore ts and eslint errors

* feat: V3 UI avatar  (#18997)

* avatar playground

* add overflow-hidden back for loading indicator

* update lock

* feat: v3/badge  (#18999)

* badge intro

* fix text on small badge

* add dark mode warning

* feat: v3/button (#19014)

* TOC

* wip buttons

* shadows

* secondary

* semantic colours

* minimal buttons

* update disabled states

* checkbox

* dropdown v3

* load icons

* fix design sections

* fix icon gap

* fix

* remove client component from rsc

* use peer as avatar

* gif

* fix button gap - dropdown iocn

* support start icon in dropdowns

* add alert demo + v3 component + darken semantic text

* base inputs and text field

* wip input

* input field addon/suffix

* toast page

* wip select

* match designs for input and select rounded

* fix

* fix select isloading transform

* fix multi icons transition rotation

* fix group heading

* range slider demo + range slider

* range slider popover design

* v3 dialog

* fix slider thumb size

* wrap in suspense for useSearchParam difference in next14+

* base of tabs + horizontal tabs

* fix select sizing to match input 36px

* fix dark mode hover on button

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

* fix default bg on popover

* lightmode toasts + eleveation token

* empty screen

* intro to split button concept

* mass colour updates

* intro to themes

* remove i18n from label with name

* bouncy buttons

* feat: small select

* nav item dumb component

* full demo

* settings demo

* fix tabs

* fix mt

* fix dropdown feedback from loom

* alert fixes mentioned in loom

* push font-cal

* move to fumadocs

* fix avatar

* colors table

* add typeography

* icon + spacing guide

* add border radius and shadow tables

* fix colours and border radius

* remove prose undline via fumas fork

* fix icon buttons sizes

* button group component

* fix horizontal tabs

* tab fixes

* switches

* pagination component

* show toasts components raw

* sync with airtable

* add use client

* 10px on input again

* 10px in select also

* fix input height

* fix files merge

* fix loading state for icon buttons

* fix button fab

* Enable addon to now overflow to next line in DS

* use cal font for dialog text

* add avatar square variant

* fix select padding and menu size

* fix type errors

* fix type errors

* add switch small size and fix type errors

* fix ref types on input

* update web styles

* remove addonFilled to fix type errors

* push dialog fixes

* fix checkbox display

* remove prose from alert examples

* remove prose from all our component examples

* badge rounded-lg on lg size

* update colors

* force w/h on icon buttons instead of inheriting

* remove border-focus

* remove border-focus from tailwind config

* update tokens to match new colours

* add new border subtle tokens and add to toasts

* fix lg button padding

* fix and drastically improve rtl rests

* multi select use badges

* use badge design for select pills

* fix avatar alignment

* fix tests and remove redudant ones

* fix tests and remove redudant ones

* update atoms globals css

* extract tokens.css

* extract app.css

* force flex for atoms

* add @calcom/ui/css

* revert ui playground styles abstraction

* fix input focus states

* fix focus states for buttons

* feat: v3/app work (#19233)

* globals.css for app + event types listing search

* few app wide NITS

* fix select foucs within styling

* fix formbuilder switch styles

* fix disabled state of nested inputs

* workflow fixes

* webhook form fixes

* i18n on tabs info

* booking filters + tab

* fix filters spacing

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

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

* profile page

* update editor br

* button nits in existing places in the app

* fix timezone select and multilist in guest form

* fix text area on public booking page

* size small event types create

* margin on routing filter

* filters improvments

* routing insights fixes

* fix date filter range

* ooo fixes

* fix teams/members bulk actions

* create team UI

* i18n in useTabs instead of in tab component now

* Add event type setup border to availability

* fix bulk actions on org members table

* onboarding fixes

* fix input types with ref stripe comparision

* i18n fixes for platform

* update globals of atoms

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

* fix select

* Revert "add @calcom/ui/css"

This reverts commit 0dd8f9012907a278cfede4a98f289cb0744c9244.

* revert tokens css

* Revert "extract app.css"

This reverts commit 10b998604816ad3310c24b36a02a638dabc5be24.

* linear nits

* workflow dropdown

* fix page theme heading sizes to match cards

* fix stepper for team creation

* adjust margin to FilterSearchField

* fix ooo icon

* fix booker and dark mode due to darkgray 50

* more dark mode fixes

* add size prop

---------

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

* remove ui-playground

* fix grow missing from merge

* add search icon data-test-id back to search

* fix FormAction style

* fix datatestid for apps and tabs

* change selector for team invitation e2e

* fix use data-testid from item instaed of using names

* rename upper case O

* fix unit tests data-testid

* fix unit tests data-testid

* more data-testid changes

* add data-testid to apps tab

* data-testid to installed appPage

* fix embed tests

* fix filter-popover-trigger

* use text-error and use dataId for locator

* more e2e improvment

* pass in single testId as button is now duplicate

* show field-error instead

* add test to eventtype tabs

* Add css vars to embed too\

* add scrollbar and change dialog overlay colour

* fix type

* use attribute instead of class for tab detection

* await promise

* use new tokens for sprite generation

* add correct testing Ids to e2e instead of button:text

* use testIds for creation of orgs

* Webhook receiver wait for 10 seconds

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
This commit is contained in:
sean-brydon
2025-02-14 16:31:21 -05:00
committed by GitHub
co-authored by Eunjae Lee Morgan Vernay Hariom Joe Au-Yeung Joe Au-Yeung
parent 0f67faeef3
commit 5d709cf63c
145 changed files with 17438 additions and 10708 deletions
+1 -1
View File
@@ -230,7 +230,7 @@
--cal-text-info: hsla(228, 56%, 33%, 1);
--cal-text-success: hsla(133, 34%, 24%, 1);
--cal-text-attention: hsla(16, 62%, 28%, 1);
--cal-text-error: hsla(2, 55%, 30%, 1);
--cal-text-error: hsla(0, 63%, 31%, 1);
/* Brand shinanigans
-> These will be computed for the users theme at runtime.
-41
View File
@@ -1,41 +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
-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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

-485
View File
@@ -1,485 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/**
* Add css variables here as well for light mode in addition to tailwind.config to avoid FOUC
*/
:root {
/* background */
--cal-bg-emphasis: hsla(220,13%,91%,1);
--cal-bg: hsla(0,0%,100%,1);
--cal-bg-subtle: hsla(220, 14%, 96%,1);
--cal-bg-muted: hsla(210,20%,98%,1);
--cal-bg-inverted: hsla(0,0%,6%,1);
/* background -> components*/
--cal-bg-info: hsla(218,83%,98%,1);
--cal-bg-success: hsla(134,76%,94%,1);
--cal-bg-attention: hsla(37, 86%, 92%, 1);
--cal-bg-error: hsla(3,66%,93%,1);
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
/* Borders */
--cal-border-emphasis: hsla(218, 11%, 65%, 1);
--cal-border: hsla(216, 12%, 84%, 1);
--cal-border-subtle: hsla(220, 13%, 91%, 1);
--cal-border-booker: #e5e7eb;
--cal-border-muted: hsla(220, 14%, 96%, 1);
--cal-border-error: hsla(4, 63%, 41%, 1);
--cal-border-focus: hsla(0, 0%, 10%, 1);
/* Content/Text */
--cal-text-emphasis: hsla(217, 19%, 27%, 1);
--cal-text: hsla(217, 19%, 27%, 1);
--cal-text-subtle: hsla(220, 9%, 46%, 1);
--cal-text-muted: hsla(218, 11%, 65%, 1);
--cal-text-inverted: hsla(0, 0%, 100%, 1);
/* Content/Text -> components */
--cal-text-info: hsla(228, 56%, 33%, 1);
--cal-text-success: hsla(133, 34%, 24%, 1);
--cal-text-attention: hsla(16, 62%, 28%, 1);
--cal-text-error: hsla(2, 55%, 30%, 1);
/* Brand shinanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand: hsla(221, 39%, 11%, 1);
--cal-brand-emphasis: hsla(0, 0%, 6%, 1);
--cal-brand-text: hsla(0, 0%, 100%, 1);
}
.dark {
/* background */
--cal-bg-emphasis: hsla(0, 0%, 25%, 1);
--cal-bg: hsla(0, 0%, 10%, 1);
--cal-bg-subtle: hsla(0, 0%, 18%, 1);
--cal-bg-muted: hsla(0, 0%, 12%, 1);
--cal-bg-inverted: hsla(220, 14%, 96%, 1);
/* background -> components*/
--cal-bg-info: hsla(228, 56%, 33%, 1);
--cal-bg-success: hsla(133, 34%, 24%, 1);
--cal-bg-attention: hsla(16, 62%, 28%, 1);
--cal-bg-error: hsla(2, 55%, 30%, 1);
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
/* Borders */
--cal-border-emphasis: hsla(0, 0%, 46%, 1);
--cal-border: hsla(0, 0%, 34%, 1);
--cal-border-subtle: hsla(0, 0%, 22%, 1);
--cal-border-booker: hsla(0, 0%, 22%, 1);
--cal-border-muted: hsla(0, 0%, 18%, 1);
--cal-border-error: hsla(4, 63%, 41%, 1);
--cal-border-focus: hsla(0, 0%, 100%, 1);
/* Content/Text */
--cal-text-emphasis: hsla(240, 20%, 99%, 1);
--cal-text: hsla(0, 0%, 84%, 1);
--cal-text-subtle: hsla(0, 0%, 65%, 1);
--cal-text-muted: hsla(0, 0%, 34%, 1);
--cal-text-inverted: hsla(0, 0%, 10%, 1);
/* Content/Text -> components */
--cal-text-info: hsla(218, 83%, 93%, 1);
--cal-text-success: hsla(134, 76%, 94%, 1);
--cal-text-attention: hsla(37, 86%, 92%, 1);
--cal-text-error: hsla(3, 66%, 93%, 1);
/* Brand shenanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand: hsla(0, 0%, 100%, 1);
--cal-brand-emphasis: hsla(218, 11%, 65%, 1);
--cal-brand-text: hsla(0, 0%, 0%,1);
}
@layer base {
* {
@apply border-default
}
}
::-moz-selection {
color: var(--cal-brand-text);
background: var(--cal-brand);
}
::selection {
color: var(--cal-brand-text);
background: var(--cal-brand);
}
body  {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
/*
Desktop App specific CSS
https://docs.todesktop.com/
*/
html.todesktop-platform-win32 .todesktop\:\!bg-transparent{
background: inherit !important;
}
/* disable user selection on buttons, links and images on desktop app */
html.todesktop button,
html.todesktop a,
html.todesktop img,
html.todesktop header {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default !important;
}
html.todesktop,
html.todesktop div {
cursor: default !important;
}
/* make header draggable on desktop app */
html.todesktop header {
-webkit-app-region: drag;
}
html.todesktop header button,
html.todesktop header a {
-webkit-app-region: no-drag;
}
html.todesktop-platform-darwin body, html.todesktop-platform-darwin aside {
background: transparent !important;
}
html.todesktop-platform-darwin.dark main.bg-default {
background: rgba(0, 0, 0, 0.6) !important;
}
html.todesktop-platform-darwin.light main.bg-default {
background: rgba(255, 255, 255, 0.8) !important;
}
html.todesktop.light {
--cal-bg-emphasis: hsla(0, 0%, 11%, 0.1);
}
html.todesktop.dark {
--cal-bg-emphasis: hsla(220, 2%, 26%, 0.3);
}
/*
Adds Utility to hide scrollbar to tailwind
https://github.com/tailwindlabs/tailwindcss/discussions/2394
https://github.com/tailwindlabs/tailwindcss/pull/5732
*/
@layer utilities {
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
/*
* Override the default tailwindcss-forms styling (default is: 'colors.blue.600')
* @see: https://github.com/tailwindlabs/tailwindcss-forms/issues/14#issuecomment-1005376006
*/
[type="text"]:focus,
[type="email"]:focus,
[type="url"]:focus,
[type="password"]:focus,
[type="number"]:focus,
[type="date"]:focus,
[type="datetime-local"]:focus,
[type="month"]:focus,
[type="search"]:focus,
[type="tel"]:focus,
[type="checkbox"]:focus,
[type="radio"]:focus,
[type="time"]:focus,
[type="week"]:focus,
[multiple]:focus,
textarea:focus,
select:focus {
--tw-ring-color: var(--brand-color);
border-color: var(--brand-color);
}
@layer components {
.scroll-bar {
@apply scrollbar-thin scrollbar-thumb-rounded-md dark:scrollbar-thumb-darkgray-300 scrollbar-thumb-gray-300 scrollbar-track-transparent;
}
}
/* TODO: avoid global specific css */
/* button[role="switch"][data-state="checked"] span {
transform: translateX(16px);
} */
@layer components {
/* slider */
.slider {
@apply relative flex h-4 w-40 select-none items-center;
}
.slider > .slider-track {
@apply relative h-1 flex-grow rounded-md bg-gray-400;
}
.slider .slider-range {
@apply absolute h-full rounded-full bg-gray-700;
}
.slider .slider-thumb {
@apply block h-3 w-3 cursor-pointer rounded-full bg-gray-700 transition-all;
}
.slider .slider-thumb:hover {
@apply bg-gray-600;
}
.slider .slider-thumb:focus {
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.2);
}
}
/* hide chat bubble on mobile */
@media only screen and (max-width: 768px) {
/* Intercom FAB*/
#launcher {
display: none !important;
}
/* Zendesk FAB*/
div[role="presentation"] > iframe {
display: none !important;
}
/* Helpscout FAB*/
.BeaconFabButtonFrame {
margin-left: -30px;
left: 50%;
bottom: 28px !important;
z-index: 1058 !important;
}
}
/* TODO: implement styling for react-multi-email */
/* !important to override react-dates */
.DateRangePickerInput__withBorder {
border: 0 !important;
}
.DateInput_input {
border: 1px solid #d1d5db !important;
border-radius: 2px !important;
font-size: inherit !important;
font-weight: inherit !important;
color: #000;
padding: 11px ​11px 9px !important;
line-height: 16px !important;
}
.DateInput_input__focused {
border: 2px solid #000 !important;
border-radius: 2px !important;
box-shadow: none !important;
padding: 10px ​10px 9px !important;
}
.DateRangePickerInput_arrow {
padding: 0px 10px;
}
.loader {
display: block;
width: 30px;
height: 30px;
margin: 60px auto;
position: relative;
border-width: 4px;
border-style: solid;
animation: loader 2s infinite ease;
}
.loader-inner {
vertical-align: top;
display: inline-block;
width: 100%;
animation: loader-inner 2s infinite ease-in;
}
.no-ring-inset {
--tw-ring-inset: unset;
}
@keyframes loader {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(180deg);
}
50% {
transform: rotate(180deg);
}
75% {
transform: rotate(360deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes loader-inner {
0% {
height: 0%;
}
25% {
height: 0%;
}
50% {
height: 100%;
}
75% {
height: 100%;
}
100% {
height: 0%;
}
}
.text-inverted-important {
color: white !important;
}
@layer utilities {
.transition-max-width {
-webkit-transition-property: max-width;
transition-property: max-width;
}
}
#timeZone input:focus {
box-shadow: none;
}
/* react-date-picker forces a border upon us, cast it away */
.react-date-picker__wrapper {
border: none !important;
}
.react-date-picker__inputGroup__input {
padding-top: 0;
padding-bottom: 0;
}
/* animations */
.slideInBottom {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-name: slideInBottom;
}
@keyframes slideInBottom {
from {
opacity: 0;
transform: translateY(30%);
pointer-events: none;
}
to {
opacity: 1;
pointer-events: auto;
}
}
/* animations */
.slideInTop {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-name: slideInTop;
}
@keyframes slideInTop {
from {
opacity: 0;
transform: translateY(-20%);
pointer-events: none;
}
to {
opacity: 1;
pointer-events: auto;
transform: translateY(0%);
}
}
.fadeIn {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-name: fadeIn;
animation-timing-function: ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/**
* Makes sure h-screen works on mobile Safari. By default h-screen
* does not take into account the height of the address bar, causing
* weird behaviour when scrolling — sometimes the height will be correct
* and sometimes it won't, depending on whether the address bar is
* in 'collapsed' state or not.
* @see: https://benborgers.com/posts/tailwind-h-screen
*/
@supports (-webkit-touch-callout: none) {
.h-screen {
height: -webkit-fill-available;
}
}
::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.react-tel-input .country-list .country:hover,
.react-tel-input .country-list .country.highlight {
@apply !bg-emphasis;
}
.react-tel-input .flag-dropdown .selected-flag,
.react-tel-input .flag-dropdown.open .selected-flag {
@apply !bg-default;
}
.react-tel-input .flag-dropdown {
@apply !border-r-default left-0.5 !border-y-0 !border-l-0;
}
.intercom-lightweight-app {
@apply z-40 !important;
}
-31
View File
@@ -1,31 +0,0 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
</html>
);
}
-5
View File
@@ -1,5 +0,0 @@
// import AvatarDemo from "./_components/AvatarDemo";
export default function Home() {
return <div className="p-4 font-[family-name:var(--font-geist-sans)]">{/* <AvatarDemo /> */}</div>;
}
-16
View File
@@ -1,16 +0,0 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;
-7
View File
@@ -1,7 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
-30
View File
@@ -1,30 +0,0 @@
{
"name": "ui-playground",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack --port=1337",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@calcom/config": "*",
"@calcom/lib": "*",
"@calcom/ui": "*",
"next": "15.1.6",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^9",
"eslint-config-next": "15.1.6",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
-8
View File
@@ -1,8 +0,0 @@
import base from "@calcom/config/tailwind-preset";
/** @type {import('tailwindcss').Config} */
module.exports = {
...base,
content: [...base.content],
plugins: [...base.plugins, require("tailwindcss-animate")],
};
-27
View File
@@ -1,27 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
+1 -1
View File
@@ -35,7 +35,7 @@ export default async function IconsPage() {
<IconGrid
title="Filled Icons"
icons={icons}
rootClassName="bg-darkgray-100 text-gray-50"
rootClassName="bg-inverted text-inverted"
iconClassName="fill-blue-500"
/>
</div>
+1 -1
View File
@@ -122,7 +122,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
`}</style>
</head>
<body
className="dark:bg-darkgray-50 bg-subtle antialiased"
className="dark:bg-default bg-subtle antialiased"
style={
isEmbed
? {
@@ -18,7 +18,7 @@ const Page = async () => {
<SettingsHeader
title={t("out_of_office")}
description={t("out_of_office_description")}
CTA={<CreateNewOutOfOfficeEntryButton />}>
CTA={<CreateNewOutOfOfficeEntryButton data-testid="add_entry_ooo" />}>
<OutOfOfficeEntriesList />
</SettingsHeader>
);
@@ -6,7 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { TRPCClientErrorLike } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Form, Icon } from "@calcom/ui";
import { Button, Form } from "@calcom/ui";
interface ISetupAvailabilityProps {
nextStep: () => void;
@@ -73,12 +73,13 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
<div>
<Button
EndIcon="arrow-right"
data-testid="save-availability"
type="submit"
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
loading={availabilityForm.formState.isSubmitting}
disabled={availabilityForm.formState.isSubmitting}>
{t("next_step_text")} <Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" />
{t("next_step_text")}
</Button>
</div>
</Form>
@@ -9,7 +9,7 @@ import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { Button, TimezoneSelect, Icon, Input } from "@calcom/ui";
import { Button, TimezoneSelect, Input } from "@calcom/ui";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@@ -110,12 +110,12 @@ const UserSettings = (props: IUserSettingsProps) => {
</div>
</div>
<Button
EndIcon="arrow-right"
type="submit"
className="mt-8 flex w-full flex-row justify-center"
loading={mutation.isPending}
disabled={mutation.isPending}>
{t("next_step_text")}
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</Button>
</form>
);
@@ -45,7 +45,7 @@ const CustomEmailTextField = ({
return (
<>
<div
className={`border-default mt-2 flex w-full items-center rounded-md border ${
className={`border-default mt-2 flex w-full items-center rounded-[10px] border ${
inputFocus ? "ring-brand-default border-neutral-300 ring-2" : ""
}`}>
<TextField
@@ -73,8 +73,8 @@ const CustomEmailTextField = ({
<Button
StartIcon="ellipsis"
variant="icon"
size="sm"
color="secondary"
size="xs"
color="minimal"
className="ml-2 rounded-md"
data-testid="secondary-email-action-group-button"
/>
@@ -115,7 +115,6 @@ const EnterpriseLicense = (
placeholder="xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx"
labelSrOnly={true}
value={value}
addOnFilled={false}
addOnClassname={classNames(
"hover:border-default",
errors.licenseKey === undefined && isDirty && "group-hover:border-emphasis"
+1 -1
View File
@@ -13,7 +13,7 @@ interface Props {
export default function AuthContainer(props: React.PropsWithChildren<Props>) {
return (
<div className="bg-subtle dark:bg-darkgray-50 flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="bg-subtle dark:bg-default flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8">
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
-1
View File
@@ -42,7 +42,6 @@ function AppsSearch({
const { t } = useLocale();
return (
<TextField
className="bg-subtle !border-muted !pl-0 focus:!ring-offset-0"
addOnLeading={<Icon name="search" className="text-subtle h-4 w-4" />}
addOnClassname="!border-muted"
containerClassName={classNames("focus:!ring-offset-0 m-1", className)}
@@ -44,22 +44,27 @@ const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
name: "upcoming",
href: "/bookings/upcoming",
"data-testid": "upcoming",
},
{
name: "unconfirmed",
href: "/bookings/unconfirmed",
"data-testid": "unconfirmed",
},
{
name: "recurring",
href: "/bookings/recurring",
"data-testid": "recurring",
},
{
name: "past",
href: "/bookings/past",
"data-testid": "past",
},
{
name: "cancelled",
href: "/bookings/cancelled",
"data-testid": "cancelled",
},
];
@@ -250,7 +255,12 @@ function BookingsContent({ status }: BookingsProps) {
return (
<div className="flex flex-col">
<div className="flex flex-row flex-wrap justify-between">
<HorizontalTabs tabs={tabs} />
<HorizontalTabs
tabs={tabs.map((tab) => ({
...tab,
name: t(tab.name),
}))}
/>
<FilterToggle setIsFiltersVisible={setIsFiltersVisible} />
</div>
<FiltersContainer isFiltersVisible={isFiltersVisible} />
@@ -983,7 +983,7 @@ export default function Success(props: PageProps) {
className={classNames(
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
rateValue === 1
? "border-focus bg-emphasis"
? "border-emphasis bg-emphasis"
: "border-muted bg-default opacity-50"
)}
disabled={isFeedbackSubmitted}
@@ -994,7 +994,7 @@ export default function Success(props: PageProps) {
className={classNames(
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
rateValue === 2
? "border-focus bg-emphasis"
? "border-emphasis bg-emphasis"
: "border-muted bg-default opacity-50"
)}
disabled={isFeedbackSubmitted}
@@ -1005,7 +1005,7 @@ export default function Success(props: PageProps) {
className={classNames(
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
rateValue === 3
? "border-focus bg-emphasis"
? "border-emphasis bg-emphasis"
: " border-muted bg-default opacity-50"
)}
disabled={isFeedbackSubmitted}
@@ -1016,7 +1016,7 @@ export default function Success(props: PageProps) {
className={classNames(
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
rateValue === 4
? "border-focus bg-emphasis"
? "border-emphasis bg-emphasis"
: "border-muted bg-default opacity-50"
)}
disabled={isFeedbackSubmitted}
@@ -1027,7 +1027,7 @@ export default function Success(props: PageProps) {
className={classNames(
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
rateValue === 5
? "border-focus bg-emphasis"
? "border-emphasis bg-emphasis"
: "border-muted bg-default opacity-50"
)}
disabled={isFeedbackSubmitted}
@@ -120,9 +120,8 @@ const InfiniteTeamsTab: FC<InfiniteTeamsTabProps> = (props) => {
return (
<div>
<TextField
className="max-w-64 bg-subtle !border-muted mb-4 mr-auto rounded-md !pl-0 focus:!ring-offset-0"
className="max-w-64"
addOnLeading={<Icon name="search" className="text-subtle h-4 w-4" />}
addOnClassname="!border-muted"
containerClassName="max-w-64 focus:!ring-offset-0 mb-4"
type="search"
value={searchTerm}
@@ -600,7 +599,8 @@ export const InfiniteEventTypeList = ({
variant="icon"
color="secondary"
StartIcon="ellipsis"
className="ltr:radix-state-open:rounded-r-md rtl:radix-state-open:rounded-l-md"
// Unsual practice to use radix state open but for some reason this dropdown and only thi dropdown clears the border radius of this button.
className="ltr:radix-state-open:rounded-r-[--btn-group-radius] rtl:radix-state-open:rounded-l-[--btn-group-radius]"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
@@ -915,6 +915,7 @@ const InfiniteScrollMain = ({
name: item.profile.name ?? "",
href: item.teamId ? `/event-types?teamId=${item.teamId}` : "/event-types?noTeam",
avatar: item.profile.image,
"data-testid": item.profile.name ?? "",
}));
const activeEventTypeGroup =
@@ -250,7 +250,7 @@ const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: Gene
) : (
<div className="bg-muted border-subtle mt-2 rounded-md border p-4">
<Label>{t("travel_schedule")}</Label>
<div className="dark:bg-darkgray-100 border-subtle mt-4 rounded-md border bg-white text-sm">
<div className="border-subtle bg-default mt-4 rounded-md border text-sm">
{watchedTzSchedules.map((schedule, index) => {
return (
<div
-1
View File
@@ -126,7 +126,6 @@ function UsernameField({
{...props}
{...register("username")}
data-testid="signup-usernamefield"
addOnFilled={false}
/>
{(!formState.isSubmitting || !formState.isSubmitted) && (
<div className="text-gray text-default flex items-center text-sm">
+3 -5
View File
@@ -74,7 +74,7 @@ function TeamPage({ team, considerUnpublished, isValidOrgDomain }: PageProps) {
<li
key={index}
className={classNames(
"dark:bg-darkgray-100 bg-default hover:bg-muted border-subtle group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0",
"bg-default hover:bg-muted border-subtle group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0",
!isEmbed && "bg-default"
)}>
<div className="px-6 py-4 ">
@@ -157,7 +157,7 @@ function TeamPage({ team, considerUnpublished, isValidOrgDomain }: PageProps) {
return (
<>
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
<main className="dark:bg-default bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
<div className="mx-auto mb-8 max-w-3xl text-center">
<div className="relative">
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
@@ -208,9 +208,7 @@ function TeamPage({ team, considerUnpublished, isValidOrgDomain }: PageProps) {
<div className="border-subtle w-full border-t" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-subtle text-subtle px-2 text-sm">
{t("or")}
</span>
<span className="bg-subtle text-subtle px-2 text-sm">{t("or")}</span>
</div>
</div>
+1 -1
View File
@@ -121,7 +121,7 @@ class MyDocument extends Document<Props> {
</Head>
<body
className="dark:bg-darkgray-50 bg-subtle antialiased"
className="dark:bg-default bg-subtle antialiased"
style={
isEmbed
? {
+1 -1
View File
@@ -74,7 +74,7 @@ test.describe("apps/ A/B tests", () => {
const locator = page.getByTestId("horizontal-tab-upcoming");
await expect(locator).toHaveClass(/bg-emphasis/);
await expect(locator).toBeDefined();
});
test("should render the /getting-started", async ({ page, users }) => {
@@ -336,7 +336,7 @@ async function goToReactCodeTab(page: Page) {
// To prevent early timeouts
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.locator("[data-testid=horizontal-tab-React]").click();
await page.locator("[data-testid=horizontal-tab-react]").click();
}
async function clickEmbedButton(page: Page) {
+2 -2
View File
@@ -66,9 +66,9 @@ export function createHttpServer(opts: { requestHandler?: RequestHandler } = {})
eventEmitter.on("push", pushHandler);
setTimeout(() => {
if (resolved) return;
// Timeout after 5 seconds
// Timeout after 10 seconds
reject(new Error("Timeout waiting for webhook"));
}, 5000);
}, 10000);
});
};
@@ -258,7 +258,6 @@ test.describe("Manage Booking Questions", () => {
await test.step("Go to First Team Event", async () => {
const locator = page.getByTestId(`horizontal-tab-${team?.name}`);
await locator.click();
await expect(locator).toHaveClass(/bg-emphasis/);
const $eventTypes = page.locator("[data-testid=event-types]").locator("li a");
const firstEventTypeElement = $eventTypes.first();
@@ -56,6 +56,6 @@ async function inviteAnEmail(page: Page, invitedUserEmail: string) {
await page.getByTestId("new-organization-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await submitAndWaitForResponse(page, "/api/trpc/teams/inviteMember?batch=1", {
action: () => page.locator('button:text("Send invite")').click(),
action: () => page.getByTestId("invite-new-member-button").click(),
});
}
@@ -135,7 +135,7 @@ test.describe("Organization", () => {
await test.step("Basic info", async () => {
// Check required fields
await page.locator("button[type=submit]").click();
await expect(page.locator(".text-red-700")).toHaveCount(3);
await expect(page.getByTestId("field-error")).toHaveCount(3);
// Happy path
await fillAndSubmitFirstStepAsAdmin(page, orgOwnerEmail, orgName, orgSlug);
@@ -152,12 +152,12 @@ test.describe("Organization", () => {
await test.step("About the organization", async () => {
// Choosing an avatar
await page.locator('button:text("Upload")').click();
await page.getByTestId("open-upload-avatar-dialog").click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Choose a file...").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "../../public/apple-touch-icon.png"));
await page.locator('button:text("Save")').click();
await page.getByTestId("upload-avatar").click();
// About text
await page.locator('textarea[name="about"]').fill("This is a testing org");
@@ -202,7 +202,7 @@ test.describe("Organization", () => {
await page.locator('input[name="teams.0.name"]').fill("Marketing");
// Adding another team
await page.locator('button:text("Add a team")').click();
await page.getByTestId("add_a_team").click();
await page.locator('input[name="teams.1.name"]').fill("Sales");
// Finishing the creation wizard
@@ -269,7 +269,7 @@ test.describe("Organization", () => {
await test.step("Basic info", async () => {
// Check required fields
await page.locator("button[type=submit]").click();
await expect(page.locator(".text-red-700")).toHaveCount(3);
await expect(page.getByTestId("field-error")).toHaveCount(3);
// Happy path
await fillAndSubmitFirstStepAsAdmin(page, orgOwnerEmail, orgName, orgSlug);
@@ -319,12 +319,12 @@ test.describe("Organization", () => {
await test.step("About the organization", async () => {
// Choosing an avatar
await page.locator('button:text("Upload")').click();
await page.getByTestId("open-upload-avatar-dialog").click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Choose a file...").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "../../public/apple-touch-icon.png"));
await page.locator('button:text("Save")').click();
await page.getByTestId("upload-avatar").click();
// About text
await page.locator('textarea[name="about"]').fill("This is a testing org");
@@ -369,7 +369,7 @@ test.describe("Organization", () => {
await page.locator('input[name="teams.0.name"]').fill("Marketing");
// Adding another team
await page.locator('button:text("Add a team")').click();
await page.getByTestId("add_a_team").click();
await page.locator('input[name="teams.1.name"]').fill("Sales");
// Finishing the creation wizard
@@ -451,12 +451,12 @@ test.describe("Organization", () => {
await test.step("About the organization", async () => {
// Choosing an avatar
await page.locator('button:text("Upload")').click();
await page.getByTestId("open-upload-avatar-dialog").click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Choose a file...").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, "../../public/apple-touch-icon.png"));
await page.locator('button:text("Save")').click();
await page.getByTestId("upload-avatar").click();
// About text
await page.locator('textarea[name="about"]').fill("This is a testing org");
@@ -511,7 +511,7 @@ test.describe("Organization", () => {
await page.locator('input[name="teams.0.name"]').fill("Marketing");
// Adding another team
await page.locator('button:text("Add a team")').click();
await page.getByTestId("new-team-dialog-button").click();
await page.locator('input[name="teams.1.name"]').fill("Sales");
// Finishing the creation wizard
@@ -546,7 +546,7 @@ async function inviteAnEmail(page: Page, invitedUserEmail: string, teamPage?: bo
}
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
const submitPromise = page.waitForResponse("/api/trpc/teams/inviteMember?batch=1");
await page.locator('button:text("Send invite")').click();
await page.getByTestId("invite-new-member-button").click();
const response = await submitPromise;
expect(response.status()).toBe(200);
}
@@ -29,7 +29,7 @@ test.describe("Team", () => {
});
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.locator(`button:text("${t("send_invite")}")`).click();
await page.getByText(t("send_invite")).click();
const inviteLink = await expectInvitationEmailToBeReceived(
page,
emails,
@@ -110,7 +110,7 @@ test.describe("Team", () => {
});
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.locator(`button:text("${t("send_invite")}")`).click();
await page.getByText(t("send_invite")).click();
await expectInvitationEmailToBeReceived(
page,
emails,
@@ -155,7 +155,7 @@ test.describe("Team", () => {
await page.goto(`/settings/teams/${team.id}/members`);
await page.getByTestId("new-member-button").click();
await page.locator('input[name="inviteUser"]').fill(invitedMember.email);
await page.locator(`button:text("${t("send_invite")}")`).click();
await page.getByText(t("send_invite")).click();
await invitedMember.apiLogin();
await page.goto(`/teams`);
+230 -73
View File
@@ -5,94 +5,248 @@
* Add css variables here as well for light mode in addition to tailwind.config to avoid FOUC
*/
:root {
/* background */
/* Spacing Scale */
--cal-spacing-0: 0px;
--cal-spacing-px: 1px;
--cal-spacing-0_5: 0.125rem; /* 2px */
--cal-spacing-1: 0.25rem; /* 4px */
--cal-spacing-1_5: 0.375rem; /* 6px */
--cal-spacing-2: 0.5rem; /* 8px */
--cal-spacing-2_5: 0.625rem; /* 10px */
--cal-spacing-3: 0.75rem; /* 12px */
--cal-spacing-3_5: 0.875rem; /* 14px */
--cal-spacing-4: 1rem; /* 16px */
--cal-spacing-5: 1.25rem; /* 20px */
--cal-spacing-6: 1.5rem; /* 24px */
--cal-spacing-7: 1.75rem; /* 28px */
--cal-spacing-8: 2rem; /* 32px */
--cal-spacing-9: 2.25rem; /* 36px */
--cal-spacing-10: 2.5rem; /* 40px */
--cal-spacing-11: 2.75rem; /* 44px */
--cal-spacing-12: 3rem; /* 48px */
--cal-spacing-14: 3.5rem; /* 56px */
--cal-spacing-16: 4rem; /* 64px */
--cal-spacing-20: 5rem; /* 80px */
--cal-spacing-24: 6rem; /* 96px */
--cal-spacing-28: 7rem; /* 112px */
--cal-spacing-32: 8rem; /* 128px */
--cal-spacing-36: 9rem; /* 144px */
--cal-spacing-40: 10rem; /* 160px */
--cal-spacing-44: 11rem; /* 176px */
--cal-spacing-48: 12rem; /* 192px */
--cal-spacing-52: 13rem; /* 208px */
--cal-spacing-56: 14rem; /* 224px */
--cal-spacing-60: 15rem; /* 240px */
--cal-spacing-64: 16rem; /* 256px */
--cal-spacing-72: 18rem; /* 288px */
--cal-spacing-80: 20rem; /* 320px */
--cal-spacing-96: 24rem; /* 384px */
--cal-bg-emphasis: hsla(220,13%,91%,1);
--cal-bg: hsla(0,0%,100%,1);
--cal-bg-subtle: hsla(220, 14%, 96%,1);
--cal-bg-muted: hsla(210,20%,98%,1);
--cal-bg-inverted: hsla(0,0%,6%,1);
/* Border Radius */
--cal-radius-none: 0px;
--cal-radius-sm: 0.125rem; /* 2px */
--cal-radius: 0.25rem; /* 4px */
--cal-radius-md: 0.375rem; /* 6px */
--cal-radius-lg: 0.5rem; /* 8px */
--cal-radius-xl: 0.75rem; /* 12px */
--cal-radius-2xl: 1rem; /* 16px */
--cal-radius-3xl: 1.5rem; /* 24px */
--cal-radius-full: 9999px;
/* background -> components*/
--cal-bg-info: hsla(218,83%,98%,1);
--cal-bg-success: hsla(134,76%,94%,1);
--cal-bg-attention: hsla(37, 86%, 92%, 1);
--cal-bg-error: hsla(3,66%,93%,1);
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
/* Background Standard */
--cal-bg-emphasis: hsla(220, 13%, 91%, 1); /* gray.200 - active items and emphasising */
--cal-bg: hsla(0, 0%, 100%, 1); /* white - default background */
--cal-bg-subtle: hsla(220, 14%, 94%, 1); /* gray.100 - hovering on items, subtle raising */
--cal-bg-muted: hsla(210, 20%, 97%, 1); /* gray.50 - large items, sidebar, sections */
--cal-bg-inverted: hsla(210, 30%, 4%, 1); /* gray.900 - tooltips */
/* Background Primary */
--cal-bg-primary: hsla(214, 30%, 16%, 1); /* stone-100 */
--cal-bg-primary-emphasis: hsla(220, 6%, 25%, 1); /* stone-800 */
--cal-bg-primary-muted: hsla(220, 14%, 94%, 1); /* stone-400 */
/* Background Brand */
--cal-bg-brand: hsla(214, 30%, 16%, 1); /* gray-50 */
--cal-bg-brand-emphasis: hsla(220, 6%, 25%, 1); /* stone-100 */
--cal-bg-brand-muted: hsla(220, 14%, 94%, 1); /* stone-100 */
/* Background Semantic */
--cal-bg-semantic-info-subtle: hsla(212, 88%, 97%, 1); /* blue-100 */
--cal-bg-semantic-info-emphasis: hsla(236, 80%, 25%, 1); /* blue-500 */
--cal-bg-semantic-success-subtle: hsla(167, 54%, 93%, 1); /* green-100 */
--cal-bg-semantic-success-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-semantic-attention-subtle: hsla(34, 100%, 92%, 1); /* orange-100 */
--cal-bg-semantic-attention-emphasis: hsla(15, 79%, 34%, 1); /* orange-500 */
--cal-bg-semantic-error-subtle: hsla(0, 93%, 94%, 1); /* red-100 */
--cal-bg-semantic-error-emphasis: hsla(0, 63%, 24%, 1); /* red-500 */
/* Background Visualization */
--cal-bg-visualization-1-subtle: hsla(326, 78%, 95%, 1); /* pink-100 */
--cal-bg-visualization-1-emphasis: hsla(330, 81%, 60%, 1); /* pink-500 */
--cal-bg-visualization-2-subtle: hsla(256, 86%, 91%, 1); /* purple-100 */
--cal-bg-visualization-2-emphasis: hsla(256, 85%, 57%, 1); /* purple-500 */
--cal-bg-visualization-3-subtle: hsla(217, 87%, 91%, 1); /* blue-100 */
--cal-bg-visualization-3-emphasis: hsla(235, 100%, 63%, 1); /* blue-500 */
--cal-bg-visualization-4-subtle: hsla(167, 54%, 93%, 1); /* green-100 */
--cal-bg-visualization-4-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-visualization-5-subtle: hsla(55, 97%, 88%, 1); /* yellow-100 */
--cal-bg-visualization-5-emphasis: hsla(45, 93%, 47%, 1); /* yellow-500 */
--cal-bg-visualization-6-subtle: hsla(34, 100%, 92%, 1); /* orange-100 */
--cal-bg-visualization-6-emphasis: hsla(25, 95%, 53%, 1); /* orange-500 */
--cal-bg-visualization-7-subtle: hsla(0, 96%, 89%, 1); /* red-100 */
--cal-bg-visualization-7-emphasis: hsla(0, 84%, 60%, 1); /* red-500 */
/* Legacy Background Components - Consider deprecating */
--cal-bg-info: hsla(221, 91%, 93%, 1); /* #dee9fc - info backgrounds */
--cal-bg-success: hsla(142, 76%, 94%, 1); /* #e2fbe8 - success backgrounds */
--cal-bg-attention: hsla(33, 100%, 92%, 1); /* #fceed8 - attention backgrounds */
--cal-bg-error: hsla(3, 66%, 93%, 1); /* #f9e3e2 - error backgrounds */
--cal-bg-dark-error: hsla(2, 55%, 30%, 1); /* Keeping existing dark error */
/* Borders */
--cal-border-emphasis: hsla(218, 11%, 65%, 1);
--cal-border: hsla(216, 12%, 84%, 1);
--cal-border-subtle: hsla(220, 13%, 91%, 1);
--cal-border-booker: #e5e7eb;
--cal-border-muted: hsla(220, 14%, 96%, 1);
--cal-border-error: hsla(4, 63%, 41%, 1);
--cal-border-focus: hsla(0, 0%, 10%, 1);
--cal-border-emphasis: hsla(218, 11%, 65%, 1); /* gray.400 - input:hover */
--cal-border: hsla(216, 12%, 84%, 1); /* gray.300 - inputs */
--cal-border-subtle: hsla(220, 13%, 91%, 1); /* gray.200 - container borders */
--cal-border-muted: hsla(220, 14%, 94%, 1); /* gray.100 */
--cal-border-error: hsla(0, 96%, 89%, 1); /* Keeping existing error border */
--cal-border-semantic-error: hsla(0, 96%, 89%, 1);
--cal-border-booker: var(--cal-border-subtle);
/* Content/Text */
--cal-text-emphasis: hsla(217, 19%, 27%, 1);
--cal-text: hsla(217, 19%, 27%, 1);
--cal-text-subtle: hsla(220, 9%, 46%, 1);
--cal-text-muted: hsla(218, 11%, 65%, 1);
--cal-text-inverted: hsla(0, 0%, 100%, 1);
/* Content/Text Standard */
--cal-text-emphasis: hsla(210, 30%, 4%, 1); /* gray-900 */
--cal-text: hsla(220, 6%, 25%, 1); /* gray-700 */
--cal-text-subtle: hsla(220, 9%, 46%, 1); /* gray-500 */
--cal-text-muted: hsla(218, 11%, 65%, 1); /* gray-400 */
--cal-text-inverted: hsla(0, 0%, 100%, 1); /* white */
/* Content/Text -> components */
--cal-text-info: hsla(228, 56%, 33%, 1);
--cal-text-success: hsla(133, 34%, 24%, 1);
--cal-text-attention: hsla(16, 62%, 28%, 1);
--cal-text-error: hsla(2, 55%, 30%, 1);
/* Content/Text Semantic */
--cal-text-semantic-info: hsla(236, 80%, 25%, 1); /* blue-800 */
--cal-text-semantic-success: hsla(150, 84%, 22%, 1); /* green-800 */
--cal-text-semantic-attention: hsla(15, 79%, 34%, 1); /* orange-800 */
--cal-text-semantic-error: hsla(0, 63%, 24%, 1); /* red-800 */
/* Brand shinanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand: hsla(221, 39%, 11%, 1);
--cal-brand-emphasis: hsla(0, 0%, 6%, 1);
--cal-brand-text: hsla(0, 0%, 100%, 1);
/* Content/Text Visualization */
--cal-text-visualization-1: hsla(332, 79%, 25%, 1); /* pink-800 */
--cal-text-visualization-2: hsla(270, 91%, 25%, 1); /* purple-800 */
--cal-text-visualization-3: hsla(217, 91%, 25%, 1); /* blue-800 */
--cal-text-visualization-4: hsla(142, 71%, 25%, 1); /* green-800 */
--cal-text-visualization-5: hsla(45, 93%, 25%, 1); /* yellow-800 */
--cal-text-visualization-6: hsla(24, 95%, 25%, 1); /* orange-800 */
--cal-text-visualization-7: hsla(0, 84%, 25%, 1); /* red-800 */
/* Border/Semantic Subtle */
--cal-border-semantic-attention-subtle: hsla(32, 98%, 83%, 1);
--cal-border-semantic-error-subtle: hsla(0, 96%, 89%, 1);
/* Legacy Content/Text Components - Consider deprecating */
--cal-text-info: hsla(225, 57%, 33%, 1); /* #253985 - interactive text/icons */
--cal-text-success: hsla(144, 34%, 24%, 1); /* #285231 */
--cal-text-attention: hsla(16, 62%, 28%, 1); /* Keeping existing attention text */
--cal-text-error: hsla(0, 63%, 31%, 1); /* Keeping existing error text */
/* Brand */
--cal-brand: hsla(221, 39%, 11%, 1); /* Keeping existing brand color */
--cal-brand-emphasis: hsla(0, 0%, 6%, 1); /* Keeping existing brand emphasis */
--cal-brand-text: hsla(0, 0%, 100%, 1); /* white */
}
.dark {
/* background */
/* Background Standard */
--cal-bg-emphasis: hsla(0, 0%, 25%, 1); /* stone-700 */
--cal-bg: hsla(0, 0%, 6%, 1); /* stone-950 */
--cal-bg-subtle: hsla(0, 0%, 15%, 1); /* stone-800 */
--cal-bg-muted: hsla(0, 0%, 9%, 1); /* stone-900 */
--cal-bg-inverted: hsla(0, 0%, 98%, 1); /* stone-50 */
--cal-bg-emphasis: hsla(0, 0%, 25%, 1);
--cal-bg: hsla(0, 0%, 10%, 1);
--cal-bg-subtle: hsla(0, 0%, 18%, 1);
--cal-bg-muted: hsla(0, 0%, 12%, 1);
--cal-bg-inverted: hsla(220, 14%, 96%, 1);
/* Background Primary */
--cal-bg-primary: hsla(0, 0%, 96%, 1); /* stone-100 */
--cal-bg-primary-emphasis: hsla(0, 0%, 64%, 1); /* stone-800 */
--cal-bg-primary-muted: hsla(0, 0%, 15%, 1); /* stone-400 */
/* background -> components*/
--cal-bg-info: hsla(228, 56%, 33%, 1);
--cal-bg-success: hsla(133, 34%, 24%, 1);
--cal-bg-attention: hsla(16, 62%, 28%, 1);
--cal-bg-error: hsla(2, 55%, 30%, 1);
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
/* Background Brand */
--cal-bg-brand: hsla(0, 0%, 98%, 1); /* gray-50 */
--cal-bg-brand-emphasis: hsla(0, 0%, 96%, 1); /* stone-100 */
--cal-bg-brand-muted: hsla(0, 0%, 96%, 1); /* stone-100 */
/* Borders */
--cal-border-emphasis: hsla(0, 0%, 46%, 1);
--cal-border: hsla(0, 0%, 34%, 1);
--cal-border-subtle: hsla(0, 0%, 22%, 1);
--cal-border-booker: hsla(0, 0%, 22%, 1);
--cal-border-muted: hsla(0, 0%, 18%, 1);
--cal-border-error: hsla(4, 63%, 41%, 1);
--cal-border-focus: hsla(0, 0%, 100%, 1);
/* Background Semantic */
--cal-bg-semantic-info-subtle: hsla(236, 80%, 8%, 1); /* blue-800 */
--cal-bg-semantic-info-emphasis: hsla(229, 90%, 74%, 1); /* blue-500 */
--cal-bg-semantic-success-subtle: hsla(148, 88%, 16%, 1); /* green-800 */
--cal-bg-semantic-success-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-semantic-attention-subtle: hsla(21, 86%, 8%, 1); /* orange-800 */
--cal-bg-semantic-attention-emphasis: hsla(27, 96%, 61%, 1); /* orange-500 */
--cal-bg-semantic-error-subtle: hsla(0, 70%, 8%, 1); /* red-800 */
--cal-bg-semantic-error-emphasis: hsla(0, 91%, 71%, 1); /* red-500 */
/* Content/Text */
--cal-text-emphasis: hsla(240, 20%, 99%, 1);
--cal-text: hsla(0, 0%, 84%, 1);
--cal-text-subtle: hsla(0, 0%, 65%, 1);
--cal-text-muted: hsla(0, 0%, 34%, 1);
--cal-text-inverted: hsla(0, 0%, 10%, 1);
/* Background Visualization */
--cal-bg-visualization-1-subtle: hsla(336, 74%, 35%, 1); /* pink-200 */
--cal-bg-visualization-1-emphasis: hsla(330, 81%, 60%, 1); /* pink-500 */
--cal-bg-visualization-2-subtle: hsla(252, 83%, 23%, 1); /* purple-200 */
--cal-bg-visualization-2-emphasis: hsla(256, 85%, 57%, 1); /* purple-500 */
--cal-bg-visualization-3-subtle: hsla(236, 74%, 35%, 1); /* blue-200 */
--cal-bg-visualization-3-emphasis: hsla(235, 100%, 63%, 1); /* blue-500 */
--cal-bg-visualization-4-subtle: hsla(150, 84%, 22%, 1); /* green-200 */
--cal-bg-visualization-4-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-visualization-5-subtle: hsla(28, 73%, 26%, 1); /* yellow-200 */
--cal-bg-visualization-5-emphasis: hsla(45, 93%, 47%, 1); /* yellow-500 */
--cal-bg-visualization-6-subtle: hsla(15, 75%, 23%, 1); /* orange-200 */
--cal-bg-visualization-6-emphasis: hsla(25, 95%, 53%, 1); /* orange-500 */
--cal-bg-visualization-7-subtle: hsla(0, 70%, 35%, 1); /* red-200 */
--cal-bg-visualization-7-emphasis: hsla(0, 84%, 60%, 1); /* red-500 */
/* Content/Text -> components */
--cal-text-info: hsla(218, 83%, 93%, 1);
--cal-text-success: hsla(134, 76%, 94%, 1);
--cal-text-attention: hsla(37, 86%, 92%, 1);
--cal-text-error: hsla(3, 66%, 93%, 1);
/* Legacy Background Components - Consider deprecating */
--cal-bg-info: hsla(228, 56%, 33%, 1); /* Keeping existing info background */
--cal-bg-success: hsla(133, 34%, 24%, 1); /* Keeping existing success background */
--cal-bg-attention: hsla(16, 62%, 28%, 1); /* Keeping existing attention background */
--cal-bg-error: hsla(2, 55%, 30%, 1); /* Keeping existing error background */
--cal-bg-dark-error: hsla(2, 55%, 30%, 1); /* Keeping existing dark error */
/* Brand shenanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand: hsla(0, 0%, 100%, 1);
--cal-brand-emphasis: hsla(218, 11%, 65%, 1);
--cal-brand-text: hsla(0, 0%, 0%,1);
/* Border Standard */
--cal-border: hsla(0, 0%, 30%, 1); /* stone-600 */
--cal-border-muted: hsla(0, 0%, 9%, 1); /* stone-500 */
--cal-border-subtle: hsla(0, 0%, 15%, 1); /* stone-700 */
--cal-border-emphasis: hsla(0, 0%, 45%, 1); /* stone-800 */
--cal-border-booker: var(--cal-border-subtle);
/* Border Semantic */
--cal-border-semantic-error: hsla(0, 63%, 24%, 1); /* red-800 */
/* Border/Semantic Subtle */
--cal-border-semantic-attention-subtle: hsla(15, 75%, 23%, 1);
--cal-border-semantic-error-subtle: hsla(0, 63%, 24%, 1);
/* Legacy Border - Consider deprecating */
--cal-border-error: hsla(0, 63%, 24%, 1); /* Keeping existing error border */
/* Content/Text Standard */
--cal-text-emphasis: hsla(0, 0%, 98%, 1); /* stone-50 */
--cal-text: hsla(0, 0%, 83%, 1); /* stone-300 */
--cal-text-subtle: hsla(0, 0%, 64%, 1); /* stone-400 */
--cal-text-muted: hsla(0, 0%, 64%, 1); /* stone-400 */
--cal-text-inverted: hsla(0, 0%, 0%, 1); /* black */
/* Content/Text Semantic */
--cal-text-semantic-info: hsla(229, 90%, 74%, 1); /* blue-100 */
--cal-text-semantic-success: hsla(161, 49%, 54%, 1); /* green-100 */
--cal-text-semantic-attention: hsla(27, 96%, 61%, 1); /* orange-100 */
--cal-text-semantic-error: hsla(0, 91%, 71%, 1); /* red-100 */
/* Content/Text Semantic Emphasis */
--cal-text-semantic-info-emphasis: hsla(217, 91%, 25%, 1); /* blue-800 */
--cal-text-semantic-success-emphasis: hsla(142, 71%, 25%, 1); /* green-800 */
--cal-text-semantic-attention-emphasis: hsla(24, 95%, 25%, 1); /* orange-800 */
--cal-text-semantic-error-emphasis: hsla(0, 84%, 25%, 1); /* red-800 */
/* Legacy Content/Text Components - Consider deprecating */
--cal-text-info: hsla(218, 83%, 93%, 1); /* Keeping existing info text */
--cal-text-success: hsla(134, 76%, 94%, 1); /* Keeping existing success text */
--cal-text-attention: hsla(37, 86%, 92%, 1); /* Keeping existing attention text */
--cal-text-error: hsla(3, 66%, 93%, 1); /* Keeping existing error text */
/* Brand */
--cal-brand: hsla(0, 0%, 100%, 1); /* white */
--cal-brand-emphasis: hsla(218, 11%, 65%, 1); /* Keeping existing brand emphasis */
--cal-brand-text: hsla(0, 0%, 0%, 1); /* black */
}
@layer base {
@@ -484,5 +638,8 @@ select:focus {
@apply z-40 !important;
}
[data-radix-popper-content-wrapper] {
border: none;
}
@import "./raqb.css";
@@ -12,6 +12,7 @@ type AppCategoryEntry = {
name: AppCategories;
href: string;
icon: IconName;
"data-testid": string;
};
const getAppCategories = (baseURL: string, useQueryParam: boolean): AppCategoryEntry[] => {
@@ -22,41 +23,49 @@ const getAppCategories = (baseURL: string, useQueryParam: boolean): AppCategoryE
name: "analytics",
href: getHref(baseURL, "analytics", useQueryParam),
icon: "chart-bar",
"data-testid": "analytics",
},
{
name: "automation",
href: getHref(baseURL, "automation", useQueryParam),
icon: "share-2",
"data-testid": "automation",
},
{
name: "calendar",
href: getHref(baseURL, "calendar", useQueryParam),
icon: "calendar",
"data-testid": "calendar",
},
{
name: "conferencing",
href: getHref(baseURL, "conferencing", useQueryParam),
icon: "video",
"data-testid": "conferencing",
},
{
name: "crm",
href: getHref(baseURL, "crm", useQueryParam),
icon: "contact",
"data-testid": "crm",
},
{
name: "messaging",
href: getHref(baseURL, "messaging", useQueryParam),
icon: "mail",
"data-testid": "messaging",
},
{
name: "payment",
href: getHref(baseURL, "payment", useQueryParam),
icon: "credit-card",
"data-testid": "payment",
},
{
name: "other",
href: getHref(baseURL, "other", useQueryParam),
icon: "grid-3x3",
"data-testid": "other",
},
];
};
@@ -10,17 +10,15 @@ import type { InputFieldProps } from "@calcom/ui/components/form/inputs/types";
type AddonProps = {
children: React.ReactNode;
isFilled?: boolean;
className?: string;
error?: boolean;
onClickAddon?: () => void;
};
const Addon = ({ isFilled, children, className, error }: AddonProps) => (
const Addon = ({ children, className, error }: AddonProps) => (
<div
className={classNames(
"addon-wrapper border-default [input:hover_+_&]:border-emphasis [input:hover_+_&]:border-l-default [&:has(+_input:hover)]:border-emphasis [&:has(+_input:hover)]:border-r-default h-9 border px-3 transition disabled:hover:cursor-not-allowed",
isFilled && "bg-subtle",
className
)}>
<div
@@ -63,7 +61,6 @@ export const KeyField = forwardRef<HTMLInputElement, InputFieldProps & { default
placeholder = isLocaleReady && i18n.exists(`${name}_placeholder`) ? t(`${name}_placeholder`) : "",
className,
addOnLeading,
addOnFilled = true,
addOnClassname,
inputIsFullWidth,
labelSrOnly,
@@ -128,9 +125,7 @@ export const KeyField = forwardRef<HTMLInputElement, InputFieldProps & { default
dir="ltr"
className="focus-within:ring-brand-default group relative mb-1 flex items-center rounded-md transition focus-within:outline-none focus-within:ring-2">
{addOnLeading && (
<Addon
isFilled={addOnFilled}
className={classNames("ltr:rounded-l-md rtl:rounded-r-md", addOnClassname)}>
<Addon className={classNames("ltr:rounded-l-md rtl:rounded-r-md", addOnClassname)}>
{addOnLeading}
</Addon>
)}
@@ -525,7 +525,7 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
);
}
return (
<DropdownMenuItem>
<DropdownMenuItem className="hover:bg-[initial]">
<Component
ref={forwardedRef}
{...actionProps}
@@ -178,7 +178,7 @@ export default function RoutingForms({
newFormDialogState={newFormDialogState}
setNewFormDialogState={setNewFormDialogState}>
<div className="mb-10 w-full">
<div className="flex">
<div className="mb-2 flex">
<TeamsFilter />
</div>
<FilterResults
@@ -198,7 +198,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
) : (
<div className="mx-auto my-0 max-w-3xl md:my-24">
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
<div className="main dark:bg-darkgray-100 sm:border-subtle bg-default -mx-4 rounded-md border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
<div className="main sm:border-subtle bg-default -mx-4 rounded-md border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
<div className="text-emphasis">{customPageMessage}</div>
</div>
</div>
+201 -48
View File
@@ -1,5 +1,6 @@
const plugin = require("tailwindcss/plugin");
const { fontFamily } = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
/** @type {import('tailwindcss').Config} */
const subtleColor = "#E5E7EB";
@@ -16,72 +17,191 @@ module.exports = {
],
darkMode: "class",
theme: {
spacing: {
0: "var(--cal-spacing-0)",
px: "var(--cal-spacing-px)",
0.5: "var(--cal-spacing-0_5)",
1: "var(--cal-spacing-1)",
1.5: "var(--cal-spacing-1_5)",
2: "var(--cal-spacing-2)",
2.5: "var(--cal-spacing-2_5)",
3: "var(--cal-spacing-3)",
3.5: "var(--cal-spacing-3_5)",
4: "var(--cal-spacing-4)",
5: "var(--cal-spacing-5)",
6: "var(--cal-spacing-6)",
7: "var(--cal-spacing-7)",
8: "var(--cal-spacing-8)",
9: "var(--cal-spacing-9)",
10: "var(--cal-spacing-10)",
11: "var(--cal-spacing-11)",
12: "var(--cal-spacing-12)",
14: "var(--cal-spacing-14)",
16: "var(--cal-spacing-16)",
20: "var(--cal-spacing-20)",
24: "var(--cal-spacing-24)",
28: "var(--cal-spacing-28)",
32: "var(--cal-spacing-32)",
36: "var(--cal-spacing-36)",
40: "var(--cal-spacing-40)",
44: "var(--cal-spacing-44)",
48: "var(--cal-spacing-48)",
52: "var(--cal-spacing-52)",
56: "var(--cal-spacing-56)",
60: "var(--cal-spacing-60)",
64: "var(--cal-spacing-64)",
72: "var(--cal-spacing-72)",
80: "var(--cal-spacing-80)",
96: "var(--cal-spacing-96)",
},
borderRadius: {
none: "var(--cal-radius-none)",
sm: "var(--cal-radius-sm)",
DEFAULT: "var(--cal-radius)",
md: "var(--cal-radius-md)",
lg: "var(--cal-radius-lg)",
xl: "var(--cal-radius-xl)",
"2xl": "var(--cal-radius-2xl)",
"3xl": "var(--cal-radius-3xl)",
full: "var(--cal-radius-full)",
},
extend: {
colors: {
// Standard Background
emphasis: "var(--cal-bg-emphasis)",
default: "var(--cal-bg, white)",
subtle: "var(--cal-bg-subtle)",
muted: "var(--cal-bg-muted)",
inverted: "var(--cal-bg-inverted)",
info: "var(--cal-bg-info)",
success: "var(--cal-bg-success)",
attention: "var(--cal-bg-attention)",
error: "var(--cal-bg-error)",
darkerror: "var(--cal-bg-dark-error)",
black: "#111111",
// Primary Background
primary: {
default: "var(--cal-bg-primary)",
emphasis: "var(--cal-bg-primary-emphasis)",
muted: "var(--cal-bg-primary-muted)",
},
// Brand
brand: {
default: "var(--cal-brand,#111827)",
emphasis: "var(--cal-brand-emphasis,#101010)",
subtle: "var(--cal-brand-subtle,#9CA3AF)",
accent: "var(--cal-brand-accent,white)",
},
gray: {
50: "#F9FAFB",
100: "#F3F4F6",
200: "#E5E7EB",
300: "#D1D5DB",
400: "#9CA3AF",
500: "#6B7280",
600: "#4B5563",
700: "#374151",
800: "#1F2937",
900: "#111827",
// Semantic Background
semantic: {
info: {
subtle: "var(--cal-bg-semantic-info-subtle)",
emphasis: "var(--cal-bg-semantic-info-emphasis)",
},
success: {
subtle: "var(--cal-bg-semantic-success-subtle)",
emphasis: "var(--cal-bg-semantic-success-emphasis)",
},
attention: {
subtle: "var(--cal-bg-semantic-attention-subtle)",
emphasis: "var(--cal-bg-semantic-attention-emphasis)",
},
error: {
subtle: "var(--cal-bg-semantic-error-subtle)",
emphasis: "var(--cal-bg-semantic-error-emphasis)",
},
},
darkgray: {
50: "#101010",
100: "#1c1c1c",
200: "#2b2b2b",
300: "#444444",
400: "#575757",
500: "#767676",
600: "#a5a5a5",
700: "#d6d6d6",
800: "#e8e8e8",
900: "#f3f4f6",
// Visualization Background
visualization: {
1: {
subtle: "var(--cal-bg-visualization-1-subtle)",
emphasis: "var(--cal-bg-visualization-1-emphasis)",
},
2: {
subtle: "var(--cal-bg-visualization-2-subtle)",
emphasis: "var(--cal-bg-visualization-2-emphasis)",
},
3: {
subtle: "var(--cal-bg-visualization-3-subtle)",
emphasis: "var(--cal-bg-visualization-3-emphasis)",
},
4: {
subtle: "var(--cal-bg-visualization-4-subtle)",
emphasis: "var(--cal-bg-visualization-4-emphasis)",
},
5: {
subtle: "var(--cal-bg-visualization-5-subtle)",
emphasis: "var(--cal-bg-visualization-5-emphasis)",
},
6: {
subtle: "var(--cal-bg-visualization-6-subtle)",
emphasis: "var(--cal-bg-visualization-6-emphasis)",
},
7: {
subtle: "var(--cal-bg-visualization-7-subtle)",
emphasis: "var(--cal-bg-visualization-7-emphasis)",
},
},
// Legacy - Consider deprecating
info: "var(--cal-bg-info)",
success: "var(--cal-bg-success)",
attention: "var(--cal-bg-attention)",
error: "var(--cal-bg-error)",
darkerror: "var(--cal-bg-dark-error)",
// Base colors
black: "#111111",
gray: colors.gray,
darkgray: colors.slate,
},
borderColor: {
emphasis: "var(--cal-border-emphasis, #9CA3AF)",
default: "var(--cal-border, #D1D5DB)",
subtle: `var(--cal-border-subtle, ${subtleColor})`,
muted: "var(--cal-border-muted, #F3F4F6)",
// Standard Borders
emphasis: "var(--cal-border-emphasis)",
default: "var(--cal-border)",
subtle: "var(--cal-border-subtle)",
muted: "var(--cal-border-muted)",
// Semantic Borders
semantic: {
error: "var(--cal-border-semantic-error)",
"attention-subtle": "var(--cal-border-semantic-attention-subtle)",
"error-subtle": "var(--cal-border-semantic-error-subtle)",
},
// Used in booker embed customization
booker: `var(--cal-border-booker, ${subtleColor})`,
error: "var(--cal-border-error, #AA2E26)",
focus: "var(--cal-border-focus, #1A1A1A)",
"cal-bg": "var(--cal-bg, white)",
// Legacy - Consider deprecating
error: "var(--cal-border-error)",
"cal-bg": "var(--cal-bg)",
"cal-bg-muted": "var(--cal-bg-muted)",
},
textColor: {
emphasis: "var(--cal-text-emphasis, #111827)",
default: "var(--cal-text, #374151)",
subtle: "var(--cal-text-subtle, #6B7280)",
muted: "var(--cal-text-muted, #9CA3AF)",
inverted: "var(--cal-text-inverted, white)",
info: "var(--cal-text-info, #253985)",
success: "var(--cal-text-success, #285231)",
attention: "var(--cal-text-attention, #73321B)",
error: "var(--cal-text-error, #752522)",
brand: "var(--cal-brand-text,'white')",
// Standard Text
emphasis: "var(--cal-text-emphasis)",
default: "var(--cal-text)",
subtle: "var(--cal-text-subtle)",
muted: "var(--cal-text-muted)",
inverted: "var(--cal-text-inverted)",
// Semantic Text
semantic: {
info: "var(--cal-text-semantic-info)",
success: "var(--cal-text-semantic-success)",
attention: "var(--cal-text-semantic-attention)",
error: "var(--cal-text-semantic-error)",
},
// Semantic Text Emphasis
"semantic-info-emphasis": "var(--cal-text-semantic-info-emphasis)",
"semantic-success-emphasis": "var(--cal-text-semantic-success-emphasis)",
"semantic-attention-emphasis": "var(--cal-text-semantic-attention-emphasis)",
"semantic-error-emphasis": "var(--cal-text-semantic-error-emphasis)",
// Legacy - Consider deprecating
info: "var(--cal-text-info)",
success: "var(--cal-text-success)",
attention: "var(--cal-text-attention)",
error: "var(--cal-text-error)",
brand: "var(--cal-brand-text)",
},
fill: {
emphasis: "var(--cal-text-emphasis, #111827)",
@@ -122,9 +242,6 @@ module.exports = {
drawerSlideLeftAndFade: "drawerSlideLeftAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)",
drawerSlideRightAndFade: "drawerSlideRightAndFade 150ms ease-in",
},
boxShadow: {
dropdown: "0px 2px 6px -1px rgba(0, 0, 0, 0.08)",
},
borderWidth: {
"booker-width": "var(--cal-border-booker-width, 1px)",
},
@@ -162,6 +279,39 @@ module.exports = {
backgroundImage: {
"gradient-primary": "radial-gradient(162.05% 170% at 109.58% 35%, #667593 0%, #E3E3E3 100%)",
},
boxShadow: {
dropdown: "0px 5px 20px 0px rgba(0, 0, 0, 0.10), 0px 10px 40px 0px rgba(0, 0, 0, 0.03)",
"switch-thumb": "0px 0.8px 0.8px 0px rgba(0, 0, 0, 0.10), 0px 0.8px 3.2px 0px rgba(0, 0, 0, 0.08)",
"solid-gray-rested":
"0px 2px 3px 0px rgba(0, 0, 0, 0.06), 0px 1px 1px 0px rgba(0, 0, 0, 0.08), 1px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 2px 0.4px 0px rgba(255, 255, 255, 0.16) inset, 0px -1.5px 2px 0px rgba(0, 0, 0, 0.40) inset",
"solid-gray-hover":
"0px 2px 3px 0px rgba(0, 0, 0, 0.06), 0px 1px 1px 0px rgba(0, 0, 0, 0.08), 1px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 2px 0.4px 0px rgba(255, 255, 255, 0.16) inset, 0px -2px 2px 0px rgba(0, 0, 0, 0.40) inset",
"solid-gray-active":
"0px 2px 3px 0px rgba(0, 0, 0, 0.40) inset, 0px 0px 2px 1px rgba(0, 0, 0, 0.40) inset",
"outline-gray-rested": "0px 2px 3px 0px rgba(0, 0, 0, 0.03), 0px 2px 2px -1px rgba(0, 0, 0, 0.03)",
"outline-gray-hover": "0px 2px 3px 0px rgba(0, 0, 0, 0.03), 0px 2px 2px -1px rgba(0, 0, 0, 0.03)",
"outline-gray-active": "0px 2px 1px 0px rgba(0, 0, 0, 0.05) inset",
"outline-gray-focused":
"0px 0px 0px 1px rgba(255, 255, 255, 0.20), 0px 0px 0px 2px rgba(0, 0, 0, 0.10)",
"outline-red-rested": "0px 2px 3px 0px rgba(0, 0, 0, 0.03), 0px 2px 2px -1px rgba(0, 0, 0, 0.03)",
"outline-red-hover": "0px 1px 1px 0px rgba(0, 0, 0, 0.06), 0px 2px 3px 0px rgba(0, 0, 0, 0.08)",
"outline-red-active":
"0px 1px 1px 0px rgba(127, 29, 29, 0.06), 0px 0px 3px 0px rgba(127, 29, 29, 0.08), 0px 2px 2px 1px rgba(127, 29, 29, 0.06) inset",
"elevation-low":
"0px 1px 1px 0px rgba(0, 0, 0, 0.07), 0px 1px 2px 0px rgba(0, 0, 0, 0.08), 0px 2px 2px 0px rgba(0, 0, 0, 0.10), 0px 0px 8px 0px rgba(0, 0, 0, 0.05)",
// Brand
"button-solid-brand-default":
" 0px 2px 3px 0px rgba(0, 0, 0, 0.06), 0px 1px 1px 0px rgba(0, 0, 0, 0.08), 1px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 2px 0.4px 0px rgba(255, 255, 255, 0.12) inset, 0px -3px 2px 0px rgba(0, 0, 0, 0.04) inset;",
"button-solid-brand-hover":
"0px 1px 1px 0px rgba(0, 0, 0, 0.10), 0px 2px 3px 0px rgba(0, 0, 0, 0.08), 1px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px -3px 2px 0px rgba(0, 0, 0, 0.10) inset, 0px 2px 0.4px 0px rgba(255, 255, 255, 0.24) inset",
"button-solid-brand-active":
"0px 3px 1px 0px rgba(0, 0, 0, 0.10) inset, 0px 0px 2px 0px rgba(0, 0, 0, 0.10) inset",
"button-solid-brand-focused":
"0px 0px 0px 1px rgba(255, 255, 255, 0.40), 0px 0px 0px 2px rgba(0, 0, 0, 0.20), 0px 1px 1px 0px rgba(0, 0, 0, 0.10), 0px 2px 3px 0px rgba(0, 0, 0, 0.08), 1px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px -3px 2px 0px rgba(0, 0, 0, 0.10) inset, 0px 2px 0.4px 0px rgba(255, 255, 255, 0.24) inset",
// Outline - red
"button-outline-red-focused":
"0px 0px 0px 1px rgba(255, 255, 255, 0.32), 0px 0px 0px 2px rgba(220, 38, 38, 0.15)",
},
},
},
plugins: [
@@ -183,6 +333,9 @@ module.exports = {
},
});
}),
plugin(function ({ addVariant }) {
addVariant("enabled", "&:not(:disabled)");
}),
],
variants: {
scrollbar: ["dark"],
+1 -2
View File
@@ -32,8 +32,7 @@
--cal-text-info: #253985;
--cal-text-success: #285231;
--cal-text-attention: #73321b;
--cal-text-error: #752522;
--cal-text-error: hsla(0, 63%, 31%, 1);
/* Brand shenanigans
-> These will be computed for the users theme at runtime.
*/
@@ -0,0 +1,157 @@
export const addAppCssVars = () => {
try {
const cssVarsStyle = `cal-floating-button, cal-modal-box, cal-inline {
/* Spacing Scale */
--cal-spacing-0: 0px;
--cal-spacing-px: 1px;
--cal-spacing-0_5: 0.125rem; /* 2px */
--cal-spacing-1: 0.25rem; /* 4px */
--cal-spacing-1_5: 0.375rem; /* 6px */
--cal-spacing-2: 0.5rem; /* 8px */
--cal-spacing-2_5: 0.625rem; /* 10px */
--cal-spacing-3: 0.75rem; /* 12px */
--cal-spacing-3_5: 0.875rem; /* 14px */
--cal-spacing-4: 1rem; /* 16px */
--cal-spacing-5: 1.25rem; /* 20px */
--cal-spacing-6: 1.5rem; /* 24px */
--cal-spacing-7: 1.75rem; /* 28px */
--cal-spacing-8: 2rem; /* 32px */
--cal-spacing-9: 2.25rem; /* 36px */
--cal-spacing-10: 2.5rem; /* 40px */
--cal-spacing-11: 2.75rem; /* 44px */
--cal-spacing-12: 3rem; /* 48px */
--cal-spacing-14: 3.5rem; /* 56px */
--cal-spacing-16: 4rem; /* 64px */
--cal-spacing-20: 5rem; /* 80px */
--cal-spacing-24: 6rem; /* 96px */
--cal-spacing-28: 7rem; /* 112px */
--cal-spacing-32: 8rem; /* 128px */
--cal-spacing-36: 9rem; /* 144px */
--cal-spacing-40: 10rem; /* 160px */
--cal-spacing-44: 11rem; /* 176px */
--cal-spacing-48: 12rem; /* 192px */
--cal-spacing-52: 13rem; /* 208px */
--cal-spacing-56: 14rem; /* 224px */
--cal-spacing-60: 15rem; /* 240px */
--cal-spacing-64: 16rem; /* 256px */
--cal-spacing-72: 18rem; /* 288px */
--cal-spacing-80: 20rem; /* 320px */
--cal-spacing-96: 24rem; /* 384px */
/* Border Radius */
--cal-radius-none: 0px;
--cal-radius-sm: 0.125rem; /* 2px */
--cal-radius: 0.25rem; /* 4px */
--cal-radius-md: 0.375rem; /* 6px */
--cal-radius-lg: 0.5rem; /* 8px */
--cal-radius-xl: 0.75rem; /* 12px */
--cal-radius-2xl: 1rem; /* 16px */
--cal-radius-3xl: 1.5rem; /* 24px */
--cal-radius-full: 9999px;
/* Background Standard */
--cal-bg-emphasis: hsla(220, 13%, 91%, 1); /* gray.200 - active items and emphasising */
--cal-bg: hsla(0, 0%, 100%, 1); /* white - default background */
--cal-bg-subtle: hsla(220, 14%, 94%, 1); /* gray.100 - hovering on items, subtle raising */
--cal-bg-muted: hsla(210, 20%, 97%, 1); /* gray.50 - large items, sidebar, sections */
--cal-bg-inverted: hsla(210, 30%, 4%, 1); /* gray.900 - tooltips */
/* Background Primary */
--cal-bg-primary: hsla(214, 30%, 16%, 1); /* stone-100 */
--cal-bg-primary-emphasis: hsla(220, 6%, 25%, 1); /* stone-800 */
--cal-bg-primary-muted: hsla(220, 14%, 94%, 1); /* stone-400 */
/* Background Brand */
--cal-bg-brand: hsla(214, 30%, 16%, 1); /* gray-50 */
--cal-bg-brand-emphasis: hsla(220, 6%, 25%, 1); /* stone-100 */
--cal-bg-brand-muted: hsla(220, 14%, 94%, 1); /* stone-100 */
/* Background Semantic */
--cal-bg-semantic-info-subtle: hsla(212, 88%, 97%, 1); /* blue-100 */
--cal-bg-semantic-info-emphasis: hsla(236, 80%, 25%, 1); /* blue-500 */
--cal-bg-semantic-success-subtle: hsla(167, 54%, 93%, 1); /* green-100 */
--cal-bg-semantic-success-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-semantic-attention-subtle: hsla(34, 100%, 92%, 1); /* orange-100 */
--cal-bg-semantic-attention-emphasis: hsla(15, 79%, 34%, 1); /* orange-500 */
--cal-bg-semantic-error-subtle: hsla(0, 93%, 94%, 1); /* red-100 */
--cal-bg-semantic-error-emphasis: hsla(0, 63%, 24%, 1); /* red-500 */
/* Background Visualization */
--cal-bg-visualization-1-subtle: hsla(326, 78%, 95%, 1); /* pink-100 */
--cal-bg-visualization-1-emphasis: hsla(330, 81%, 60%, 1); /* pink-500 */
--cal-bg-visualization-2-subtle: hsla(256, 86%, 91%, 1); /* purple-100 */
--cal-bg-visualization-2-emphasis: hsla(256, 85%, 57%, 1); /* purple-500 */
--cal-bg-visualization-3-subtle: hsla(217, 87%, 91%, 1); /* blue-100 */
--cal-bg-visualization-3-emphasis: hsla(235, 100%, 63%, 1); /* blue-500 */
--cal-bg-visualization-4-subtle: hsla(167, 54%, 93%, 1); /* green-100 */
--cal-bg-visualization-4-emphasis: hsla(158, 74%, 38%, 1); /* green-500 */
--cal-bg-visualization-5-subtle: hsla(55, 97%, 88%, 1); /* yellow-100 */
--cal-bg-visualization-5-emphasis: hsla(45, 93%, 47%, 1); /* yellow-500 */
--cal-bg-visualization-6-subtle: hsla(34, 100%, 92%, 1); /* orange-100 */
--cal-bg-visualization-6-emphasis: hsla(25, 95%, 53%, 1); /* orange-500 */
--cal-bg-visualization-7-subtle: hsla(0, 96%, 89%, 1); /* red-100 */
--cal-bg-visualization-7-emphasis: hsla(0, 84%, 60%, 1); /* red-500 */
/* Legacy Background Components - Consider deprecating */
--cal-bg-info: hsla(221, 91%, 93%, 1); /* #dee9fc - info backgrounds */
--cal-bg-success: hsla(142, 76%, 94%, 1); /* #e2fbe8 - success backgrounds */
--cal-bg-attention: hsla(33, 100%, 92%, 1); /* #fceed8 - attention backgrounds */
--cal-bg-error: hsla(3, 66%, 93%, 1); /* #f9e3e2 - error backgrounds */
--cal-bg-dark-error: hsla(2, 55%, 30%, 1); /* Keeping existing dark error */
/* Borders */
--cal-border-emphasis: hsla(218, 11%, 65%, 1); /* gray.400 - input:hover */
--cal-border: hsla(216, 12%, 84%, 1); /* gray.300 - inputs */
--cal-border-subtle: hsla(220, 13%, 91%, 1); /* gray.200 - container borders */
--cal-border-muted: hsla(220, 14%, 94%, 1); /* gray.100 */
--cal-border-error: hsla(0, 96%, 89%, 1); /* Keeping existing error border */
--cal-border-semantic-error: hsla(0, 96%, 89%, 1);
--cal-border-booker: var(--cal-border-subtle);
/* Content/Text Standard */
--cal-text-emphasis: hsla(210, 30%, 4%, 1); /* gray-900 */
--cal-text: hsla(220, 6%, 25%, 1); /* gray-700 */
--cal-text-subtle: hsla(220, 9%, 46%, 1); /* gray-500 */
--cal-text-muted: hsla(218, 11%, 65%, 1); /* gray-400 */
--cal-text-inverted: hsla(0, 0%, 100%, 1); /* white */
/* Content/Text Semantic */
--cal-text-semantic-info: hsla(236, 80%, 25%, 1); /* blue-800 */
--cal-text-semantic-success: hsla(150, 84%, 22%, 1); /* green-800 */
--cal-text-semantic-attention: hsla(15, 79%, 34%, 1); /* orange-800 */
--cal-text-semantic-error: hsla(0, 63%, 24%, 1); /* red-800 */
/* Content/Text Visualization */
--cal-text-visualization-1: hsla(332, 79%, 25%, 1); /* pink-800 */
--cal-text-visualization-2: hsla(270, 91%, 25%, 1); /* purple-800 */
--cal-text-visualization-3: hsla(217, 91%, 25%, 1); /* blue-800 */
--cal-text-visualization-4: hsla(142, 71%, 25%, 1); /* green-800 */
--cal-text-visualization-5: hsla(45, 93%, 25%, 1); /* yellow-800 */
--cal-text-visualization-6: hsla(24, 95%, 25%, 1); /* orange-800 */
--cal-text-visualization-7: hsla(0, 84%, 25%, 1); /* red-800 */
/* Border/Semantic Subtle */
--cal-border-semantic-attention-subtle: hsla(32, 98%, 83%, 1);
--cal-border-semantic-error-subtle: hsla(0, 96%, 89%, 1);
/* Legacy Content/Text Components - Consider deprecating */
--cal-text-info: hsla(225, 57%, 33%, 1); /* #253985 - interactive text/icons */
--cal-text-success: hsla(144, 34%, 24%, 1); /* #285231 */
--cal-text-attention: hsla(16, 62%, 28%, 1); /* Keeping existing attention text */
--cal-text-error: hsla(0, 63%, 31%, 1); /* Keeping existing error text */
/* Brand */
--cal-brand: hsla(221, 39%, 11%, 1); /* Keeping existing brand color */
--cal-brand-emphasis: hsla(0, 0%, 6%, 1); /* Keeping existing brand emphasis */
--cal-brand-text: hsla(0, 0%, 100%, 1); /* white */
}`;
// Create a style rule for above css
const style = document.createElement("style");
style.id = "cal-css-vars";
style.textContent = cssVarsStyle;
document.head.appendChild(style);
} catch (error) {
console.error("Error adding app css vars - Report this issue to support", error);
}
};
+3
View File
@@ -2,6 +2,7 @@
import { FloatingButton } from "./FloatingButton/FloatingButton";
import { Inline } from "./Inline/inline";
import { ModalBox } from "./ModalBox/ModalBox";
import { addAppCssVars } from "./addAppCssVars";
import type { InterfaceWithParent, interfaceWithParent, PrefillAndIframeAttrsConfig } from "./embed-iframe";
import css from "./embed.css";
import { SdkActionManager } from "./sdk-action-manager";
@@ -1056,6 +1057,8 @@ function initializeGlobalCalProps() {
globalCal.config.forwardQueryParams = globalCal.config.forwardQueryParams ?? false;
}
addAppCssVars();
function log(...args: unknown[]) {
console.log(...args);
}
@@ -214,7 +214,8 @@ export const EventMeta = ({
menuPosition="absolute"
timezoneSelectCustomClassname={classNames?.eventMetaTimezoneSelect}
classNames={{
control: () => "!min-h-0 p-0 w-full border-0 bg-transparent focus-within:ring-0",
control: () =>
"!min-h-0 p-0 w-full border-0 bg-transparent focus-within:ring-0 shadow-none!",
menu: () => "!w-64 max-w-[90vw] mb-1 ",
singleValue: () => "text-text py-1",
indicatorsContainer: () => "ml-auto",
@@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from "react";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge, Button, Icon, Tooltip } from "@calcom/ui";
import { Badge, Button, Tooltip } from "@calcom/ui";
export interface FilterToggleProps {
setIsFiltersVisible: Dispatch<SetStateAction<boolean>>;
@@ -19,8 +19,7 @@ export function FilterToggle({ setIsFiltersVisible }: FilterToggleProps) {
}
return (
<Button color="secondary" onClick={toggleFiltersVisibility} className="mb-4">
<Icon name="filter" className="h-4 w-4" />
<Button color="secondary" onClick={toggleFiltersVisibility} className="mb-4" StartIcon="filter">
<Tooltip content={t("filters")}>
<div className="mx-2">{t("filters")}</div>
</Tooltip>
@@ -28,7 +28,7 @@ export function FiltersContainer({ isFiltersVisible }: FiltersContainerProps) {
return (
<div ref={animationParentRef}>
{isFiltersVisible ? (
<div className="no-scrollbar flex w-full space-x-2 overflow-x-scroll rtl:space-x-reverse">
<div className="no-scrollbar mb-2 flex w-full space-x-2 overflow-x-scroll rtl:space-x-reverse">
<PeopleFilter />
<EventTypeFilter />
<TeamsFilter />
@@ -5,7 +5,7 @@ import { forwardRef } from "react";
import { classNames } from "@calcom/lib";
import { Button, Icon } from "@calcom/ui";
import type { IconName } from "@calcom/ui";
import type { IconName, ButtonProps } from "@calcom/ui";
export type ActionItem<TData> =
| {
@@ -26,12 +26,12 @@ type ResponsiveButtonProps = {
icon: IconName;
onClick?: () => void;
children: string;
};
} & ButtonProps;
const ResponsiveButton = forwardRef<HTMLButtonElement, ResponsiveButtonProps>(
({ className, icon, onClick, children }, ref) => {
({ icon, onClick, children, ...rest }, ref) => {
return (
<Button ref={ref} onClick={onClick} title={children}>
<Button ref={ref} onClick={onClick} title={children} {...rest}>
<Icon name={icon} size={18} className="sm:hidden" />
<Icon name={icon} size={16} className="mr-2 hidden shrink-0 sm:inline" />
<span className="sr-only shrink-0 md:not-sr-only">{children}</span>
@@ -51,7 +51,7 @@ const Root = forwardRef<
<div
ref={ref}
className={classNames(
"bg-brand-default text-brand fixed bottom-0 left-0 z-40 flex w-full items-center space-x-1 overflow-x-auto px-2 py-2 sm:space-x-2 md:bottom-4 md:left-1/2 md:z-auto md:w-fit md:-translate-x-1/2 md:transform md:space-x-3 md:overflow-x-hidden md:rounded-lg md:px-4",
"bg-default text-emphasis shadow-outline-gray-rested fixed bottom-0 left-0 z-40 flex w-full items-center space-x-1 overflow-x-auto border px-2 py-2 sm:space-x-2 md:bottom-4 md:left-1/2 md:z-auto md:w-fit md:-translate-x-1/2 md:transform md:space-x-3 md:overflow-x-hidden md:rounded-lg md:px-4",
className
)}
style={{ ...style }}
@@ -47,8 +47,12 @@ function AddFilterButtonComponent<TData>(
<div className="flex items-center space-x-2">
<Popover>
<PopoverTrigger asChild>
<Button ref={ref} color="secondary" data-testid="add-filter-button">
<Icon name="sliders-horizontal" className="mr-2 h-4 w-4" />
<Button
ref={ref}
color="secondary"
data-testid="add-filter-button"
StartIcon="sliders-horizontal"
className="h-full">
{t("filter")}
</Button>
</PopoverTrigger>
@@ -1,6 +1,6 @@
import { useDataTable } from "@calcom/features/data-table";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Button, Tooltip } from "@calcom/ui";
import { Button, Tooltip } from "@calcom/ui";
export const ClearFiltersButton = ({ exclude }: { exclude?: string[] }) => {
const { t } = useLocale();
@@ -13,14 +13,13 @@ export const ClearFiltersButton = ({ exclude }: { exclude?: string[] }) => {
return (
<Tooltip content={t("clear_filters")}>
<Button
color="minimal"
data-testid="clear-filters-button"
variant="icon"
color="secondary"
target="_blank"
rel="noreferrer"
className="min-w-24 h-[38px] border-0"
className="min-w-24"
StartIcon="x"
onClick={() => clearAll(exclude)}>
<Icon name="x" className="mr-1 h-4 w-4" />
{t("clear")}
</Button>
</Tooltip>
@@ -147,7 +147,7 @@ export const DateRangeFilter = ({ column }: DateRangeFilterProps) => {
return (
<Popover>
<PopoverTrigger asChild>
<Button color="secondary" className="items-center capitalize">
<Button color="secondary" className="items-center capitalize" EndIcon="chevron-down">
{!isCustomPreset && <span>{t(selectedPreset.labelKey, selectedPreset.i18nOptions)}</span>}
{isCustomPreset &&
(endDate ? (
@@ -157,7 +157,6 @@ export const DateRangeFilter = ({ column }: DateRangeFilterProps) => {
) : (
<span>{format(startDate.toDate(), "LLL dd, y")} - End</span>
))}
<Icon name="chevron-down" className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="flex w-fit p-0" align="end">
@@ -2,7 +2,7 @@
import startCase from "lodash/startCase";
import type { IconName } from "@calcom/ui";
import { Button, Icon, Popover, PopoverContent, PopoverTrigger, Badge } from "@calcom/ui";
import { Button, Popover, PopoverContent, PopoverTrigger, Badge } from "@calcom/ui";
import { useFilterValue } from "../../hooks";
import { type FilterableColumn, type FilterValue, ZFilterValue, ColumnFilterType } from "../../lib/types";
@@ -137,11 +137,11 @@ export function FilterPopover({ column }: FilterPopoverProps) {
<Button
color="secondary"
className="items-center"
StartIcon={icon}
EndIcon="chevron-down"
data-testid={`filter-popover-trigger-${column.id}`}>
<Icon name={icon} className="mr-2 h-4 w-4" />
<span>{startCase(column.title)}</span>
<AppliedFilterValue column={column} filterValue={filterValue} />
<Icon name="chevron-down" className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
@@ -31,7 +31,7 @@ export const LockedSwitch = (
setUnlockedFields(fieldName, !enabled || undefined);
}}
checked={fieldState[fieldName]}
small={!options.simple}
size="sm"
/>
) : null;
};
@@ -58,7 +58,7 @@ export const LockedIndicator = (
<Badge
variant={isLocked ? "gray" : "green"}
className={classNames(
"ml-2 transform justify-between gap-1.5 p-1",
"ml-2 transform justify-between p-1",
isManagedEventType && !options.simple && "w-28"
)}>
{!options.simple && (
@@ -78,7 +78,7 @@ export const LockedIndicator = (
setUnlockedFields(fieldName, !enabled || undefined);
}}
checked={isLocked}
small={!options.simple}
size="sm"
/>
)}
</Badge>
@@ -236,7 +236,8 @@ const AddNewTeamsFormChild = ({
disabled={createTeamsMutation.isPending}
onClick={handleCounterIncrease}
aria-label={t("add_a_team")}
className="my-1">
className="my-1"
data-testId="add_a_team">
{t("add_a_team")}
</Button>
)}
@@ -120,7 +120,6 @@ export const CreateANewTeamForm = (props: CreateANewTeamFormProps) => {
rules={{ required: t("team_url_required") }}
render={({ field: { value } }) => (
<TextField
className="mt-2"
name="slug"
placeholder="acme"
label={t("team_url")}
@@ -31,7 +31,9 @@ export default function DeleteBulkTeamMembers({ users, onRemove, isOrg, teamId }
return (
<Dialog>
<DialogTrigger asChild>
<DataTableSelectionBar.Button icon="ban">{t("Delete")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="ban" color="destructive">
{t("Delete")}
</DataTableSelectionBar.Button>
</DialogTrigger>
<ConfirmationDialogContent
variety="danger"
@@ -94,7 +94,9 @@ export function EventTypesList({ table, teamId }: Props) {
<>
<Popover>
<PopoverTrigger asChild>
<DataTableSelectionBar.Button icon="link">{t("add_to_event_type")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="link" color="secondary">
{t("add_to_event_type")}
</DataTableSelectionBar.Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0 shadow-md" align="start" sideOffset={12}>
<Command>
@@ -433,8 +433,8 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
}
}}
className={classNames("gap-2", props.token && "opacity-50")}
StartIcon="link"
data-testid="copy-invite-link-button">
<Icon name="link" className="text-default h-4 w-4" aria-hidden="true" />
<span className="hidden sm:inline">{t("copy_invite_link")}</span>
</Button>
</div>
@@ -674,7 +674,6 @@ function MemberListContent(props: Props) {
type="button"
color="primary"
StartIcon="plus"
className="rounded-md"
onClick={() => props.setShowMemberInvitationModal(true)}
data-testid="new-member-button">
{t("add")}
@@ -698,6 +697,7 @@ function MemberListContent(props: Props) {
</p>
{numberOfSelectedRows >= 2 && (
<DataTableSelectionBar.Button
color="secondary"
onClick={() => setDynamicLinkVisible(!dynamicLinkVisible)}
icon="handshake">
{t("group_meeting")}
@@ -91,8 +91,8 @@ const ProfileView = ({ team }: ProfileViewProps) => {
}}>
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
<div>
<p className="font-semibold">{t("theme")}</p>
<p className="text-default">{t("theme_applies_note")}</p>
<p className="mt-0.5 text-base font-semibold leading-none">{t("theme")}</p>
<p className="text-default text-sm leading-normal">{t("theme_applies_note")}</p>
</div>
</div>
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
@@ -1,11 +1,8 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TeamsListing } from "../components";
const TeamListingView = () => {
const { t } = useLocale();
return <TeamsListing />;
};
@@ -64,7 +64,7 @@ export const TimeTimeUnitInput = (props: Props) => {
label=""
disabled={props.disabled}
defaultValue={form.getValues("time") ?? props.defaultTime ?? 24}
className="-mt-2 rounded-r-none text-sm focus:ring-0"
className="rounded-r-none text-sm focus:ring-0"
{...form.register("time", { valueAsNumber: true })}
addOnSuffix={
<TimeUnitAddonSuffix
@@ -239,7 +239,7 @@ export default function WorkflowListPage({ workflows }: Props) {
workflow?.team.logo,
workflow.team?.name as string
)}
size="xxs"
size="xs"
className="mt-[3px] inline-flex justify-center"
/>
<div>{workflow.team.name}</div>
@@ -917,7 +917,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
emailSubjectFormRef?.(e);
refEmailSubject.current = e;
}}
rows={1}
rows={2}
disabled={props.readOnly || !hasActiveTeamPlan}
className="my-0 focus:ring-transparent"
required
@@ -21,6 +21,7 @@ export const tabs = [
href: "embedTabName=embed-code",
icon: "code" as const,
type: "code",
"data-testid": "HTML",
Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null,
{ embedType: EmbedType; calLink: string; previewState: PreviewState; namespace: string }
@@ -75,6 +76,7 @@ export const tabs = [
{
name: "React",
href: "embedTabName=embed-react",
"data-testid": "react",
icon: "code" as const,
type: "code",
Component: forwardRef<
@@ -126,6 +128,7 @@ export const tabs = [
href: "embedTabName=embed-preview",
icon: "trello" as const,
type: "iframe",
"data-testid": "Preview",
Component: forwardRef<
HTMLIFrameElement | HTMLTextAreaElement | null,
{ calLink: string; embedType: EmbedType; previewState: PreviewState; namespace: string }
@@ -2,20 +2,20 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
const SkeletonEventType = () => {
return (
<div className="dark:bg-darkgray-100 bg-default h-24 w-full">
<div className="bg-default h-24 w-full">
<div className="p-5">
<div className="flex space-x-2 rtl:space-x-reverse">
<div className="dark:bg-darkgray-400 h-2 w-1/6 rounded-md bg-neutral-200" />
<div className="dark:bg-darkgray-400 h-2 w-1/6 rounded-md bg-neutral-200" />
<div className="bg-subtle h-2 w-1/6 rounded-md" />
<div className="bg-subtle h-2 w-1/6 rounded-md" />
</div>
<div className="flex space-x-2 py-2 rtl:space-x-reverse">
<div className="dark:bg-darkgray-400 h-2 w-1/12 rounded-md bg-neutral-200" />
<div className="dark:bg-darkgray-400 h-2 w-1/6 rounded-md bg-neutral-200" />
<div className="dark:bg-darkgray-400 h-2 w-1/12 rounded-md bg-neutral-200" />
<div className="bg-subtle h-2 w-1/12 rounded-md" />
<div className="bg-subtle h-2 w-1/6 rounded-md" />
<div className="bg-subtle h-2 w-1/12 rounded-md" />
</div>
<div className="flex space-x-2 py-1 rtl:space-x-reverse">
<div className="dark:bg-darkgray-200 h-6 w-1/6 rounded-md bg-neutral-300" />
<div className="dark:bg-darkgray-200 h-6 w-1/6 rounded-md bg-neutral-300" />
<div className="bg-subtle h-6 w-1/6 rounded-md" />
<div className="bg-subtle h-6 w-1/6 rounded-md" />
</div>
</div>
</div>
@@ -26,7 +26,7 @@ function EmptyPage({ name }: { name: string }) {
const { t } = useLocale();
return (
<div className="relative text-center">
<div className="dark:divide-darkgray-100 flex flex-col divide-y-2 blur-[3px]">
<div className="flex flex-col divide-y-2 blur-[3px]">
<SkeletonEventType />
<SkeletonEventType />
<SkeletonEventType />
@@ -55,7 +55,6 @@ export const MultiplePrivateLinksController = ({
color="minimal"
size="sm"
type="button"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
aria-label="copy link"
onClick={() => {
navigator.clipboard.writeText(singleUseURL);
@@ -184,7 +184,7 @@ export default function RequiresConfirmationController({
);
}}
className={classNames(
"border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r",
"border-default h-8! !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r",
customClassNames?.conditionalConfirmationRadio?.timeInput
)}
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
@@ -92,7 +92,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
<div className="before:border-0">
{(isManagedEventType || isChildrenManagedEventType) && (
<Alert
severity={appsDisableProps.isLocked ? "neutral" : "green"}
severity={appsDisableProps.isLocked ? "neutral" : "info"}
className="mb-2"
title={
<Trans i18nKey={`${lockedText}_${isManagedEventType ? "for_members" : "by_team_admins"}`}>
@@ -576,7 +576,7 @@ const UseCommonScheduleSettingsToggle = ({
const { t } = useLocale();
const { useHostSchedulesForTeamEvent, toggleScheduleState } = useCommonScheduleState(eventType.schedule);
return (
<>
<div className="border-subtle space-y-6 rounded-lg border p-6">
<SettingsToggle
checked={!useHostSchedulesForTeamEvent}
onCheckedChange={toggleScheduleState}
@@ -598,7 +598,7 @@ const UseCommonScheduleSettingsToggle = ({
/>
</div>
)}
</>
</div>
);
};
@@ -327,7 +327,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
label={t("minimum_booking_notice")}
type="number"
placeholder="0"
className={classNames("mb-0 h-9 rounded-[4px] ltr:mr-2 rtl:ml-2", customClassNames?.input)}
className={classNames("mb-0 h-9 ltr:mr-2 rtl:ml-2", customClassNames?.input)}
min={0}
/>
<input type="hidden" ref={ref} {...passThroughProps} />
@@ -115,7 +115,7 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
<>
{(isManagedEventType || isChildrenManagedEventType) && (
<Alert
severity={webhooksDisableProps.isLocked ? "neutral" : "green"}
severity={webhooksDisableProps.isLocked ? "neutral" : "info"}
className="mb-2"
title={
<Trans
@@ -132,9 +132,8 @@ const WorkflowListItem = (props: ItemProps) => {
{!workflow.readOnly && (
<div className="flex-none">
<Link href={`/workflows/${workflow.id}`} passHref={true} target="_blank">
<Button type="button" color="minimal" className="mr-4">
<Button type="button" color="minimal" className="mr-4" EndIcon="external-link">
<div className="hidden ltr:mr-2 rtl:ml-2 sm:block">{t("edit")}</div>
<Icon name="external-link" className="text-default -mt-[2px] h-4 w-4 stroke-2" />
</Button>
</Link>
</div>
@@ -244,7 +243,7 @@ function EventWorkflowsTab(props: Props) {
<>
{(isManagedEventType || isChildrenManagedEventType) && (
<Alert
severity={workflowsDisableProps.isLocked ? "neutral" : "green"}
severity={workflowsDisableProps.isLocked ? "neutral" : "info"}
className="mb-2"
title={
<Trans i18nKey={`${lockedText}_${isManagedEventType ? "for_members" : "by_team_admins"}`}>
@@ -236,8 +236,6 @@ export const Components: Record<FieldType, Component> = {
const placeholder = props.placeholder;
const { t } = useLocale();
value = value || [];
const inputClassName =
"dark:placeholder:text-muted focus:border-emphasis border-subtle block w-full rounded-md border-default text-sm focus:ring-black disabled:bg-emphasis disabled:hover:cursor-not-allowed dark:selection:bg-green-500 disabled:dark:text-subtle bg-default";
return (
<>
{value.length ? (
@@ -252,7 +250,6 @@ export const Components: Record<FieldType, Component> = {
id={`${props.name}.${index}`}
disabled={readOnly}
value={value[index]}
className={inputClassName}
onChange={(e) => {
value[index] = e.target.value.toLowerCase();
setValue(value);
@@ -353,7 +350,7 @@ export const Components: Record<FieldType, Component> = {
}
setValue(newValue);
}}
className="border-default dark:border-default hover:bg-subtle checked:hover:bg-brand-default checked:bg-brand-default dark:checked:bg-brand-default dark:bg-darkgray-100 dark:hover:bg-subtle dark:checked:hover:bg-brand-default h-4 w-4 cursor-pointer rounded transition ltr:mr-2 rtl:ml-2"
className="border-default dark:border-default hover:bg-subtle checked:hover:bg-brand-default checked:bg-brand-default dark:checked:bg-brand-default dark:hover:bg-subtle dark:checked:hover:bg-brand-default h-4 w-4 cursor-pointer rounded transition ltr:mr-2 rtl:ml-2"
value={option.value}
checked={value.includes(option.value)}
/>
@@ -248,7 +248,6 @@ export const FormBuilder = function FormBuilder({
onCheckedChange={(checked) => {
update(index, { ...field, hidden: !checked });
}}
classNames={{ container: "p-2 hover:bg-subtle rounded transition" }}
tooltip={t("show_on_booking_page")}
/>
)}
@@ -487,12 +486,9 @@ function FieldEditDialog({
return (
<Dialog open={dialog.isOpen} onOpenChange={onOpenChange} modal={false}>
<DialogContent
className="max-h-none p-0"
data-testid="edit-field-dialog"
forceOverlayWhenNoModal={true}>
<DialogContent className="max-h-none" data-testid="edit-field-dialog" forceOverlayWhenNoModal={true}>
<Form id="form-builder" form={fieldForm} handleSubmit={handleSubmit}>
<div className="h-auto max-h-[85vh] overflow-auto px-8 pb-7 pt-8">
<div className="h-auto max-h-[85vh] overflow-auto">
<DialogHeader title={t("add_a_booking_question")} subtitle={t("booking_questions_description")} />
<SelectField
defaultValue={fieldTypesConfigMap.text}
@@ -648,7 +644,7 @@ function FieldEditDialog({
})()}
</div>
<DialogFooter className="relative rounded px-8" showDivider>
<DialogFooter className="relative">
<DialogClose color="secondary">{t("cancel")}</DialogClose>
<Button data-testid="field-add-save" type="submit">
{isFieldEditMode ? t("save") : t("add")}
@@ -805,7 +801,7 @@ function VariantFields({
onCheckedChange={(checked) => {
fieldForm.setValue("variant", checked ? otherVariant : defaultVariant);
}}
classNames={{ container: "p-2 mt-2 sm:hover:bg-muted rounded transition" }}
classNames={{ container: "mt-2" }}
tooltip={t("Toggle Variant")}
/>
) : (
@@ -249,6 +249,7 @@ export const ComponentForField = ({
`Value ${value} is not valid for type ${componentConfig.propsType} for field ${field.name}`
);
}
if (componentConfig.propsType === "text") {
return (
<WithLabel field={field} htmlFor={field.name} readOnly={readOnly} noLabel={noLabel}>
@@ -44,7 +44,7 @@ const Download = () => {
EndIcon="file-down"
color="secondary"
{...(isPending && { loading: isPending })}
className="self-end sm:self-baseline">
className="h-full self-end sm:self-baseline">
{t("download")}
</Button>
</DropdownMenuTrigger>
@@ -73,7 +73,7 @@ export const OrgTeamsFilter = () => {
<Avatar
alt={`${placeholder} logo`}
imageSrc={getPlaceholderAvatar(imageUrl, placeholder)}
size="xsm"
size="xs"
className="mr-2"
/>
) : null;
@@ -43,8 +43,8 @@ export function ScheduleListItem({
return (
<li key={schedule.id}>
<div className="hover:bg-muted flex items-center justify-between py-5 transition ltr:pl-4 rtl:pr-4 sm:ltr:pl-0 sm:rtl:pr-0">
<div className="group flex w-full items-center justify-between sm:px-6">
<div className="hover:bg-muted flex items-center justify-between px-3 py-5 transition sm:px-4">
<div className="group flex w-full items-center justify-between ">
<Link
href={`/availability/${schedule.id}`}
className="flex-grow truncate text-sm"
@@ -87,7 +87,6 @@ export function ScheduleListItem({
<DropdownMenuTrigger asChild>
<Button
data-testid="schedule-more"
className="mx-5"
type="button"
variant="icon"
color="secondary"
@@ -96,8 +95,8 @@ export function ScheduleListItem({
</DropdownMenuTrigger>
{!isPending && data && (
<DropdownMenuContent>
<DropdownMenuItem className="min-w-40 focus:ring-muted">
{!schedule.isDefault && (
{!schedule.isDefault && (
<DropdownMenuItem className="min-w-40 focus:ring-muted">
<DropdownItem
type="button"
StartIcon="star"
@@ -109,8 +108,8 @@ export function ScheduleListItem({
}}>
{t("set_as_default")}
</DropdownItem>
)}
</DropdownMenuItem>
</DropdownMenuItem>
)}
<DropdownMenuItem className="outline-none">
<DropdownItem
type="button"
@@ -4,11 +4,18 @@ import { useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon } from "@calcom/ui";
import type { ButtonProps } from "@calcom/ui";
import { Button } from "@calcom/ui";
import { CreateOrEditOutOfOfficeEntryModal } from "./CreateOrEditOutOfOfficeModal";
const CreateNewOutOfOfficeEntry = () => {
const CreateNewOutOfOfficeEntry = ({
size,
...rest
}: {
size?: ButtonProps["size"];
"data-testid"?: string;
}) => {
const { t } = useLocale();
const params = useSearchParams();
@@ -25,10 +32,12 @@ const CreateNewOutOfOfficeEntry = () => {
<>
<Button
color="primary"
size={size ?? "base"}
className="flex items-center justify-between px-4"
StartIcon="plus"
onClick={() => setOpenModal(true)}
data-testid="add_entry_ooo">
<Icon name="plus" size={16} /> {t("add")}
data-testid={rest["data-testid"]}>
{t("add")}
</Button>
{openModal && (
<CreateOrEditOutOfOfficeEntryModal
@@ -140,6 +140,7 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
}
}}>
<DialogContent
enableOverflow
onOpenAutoFocus={(event) => {
event.preventDefault();
}}>
@@ -245,7 +246,7 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
onChange={(e) => setSearchText(e.target.value)}
value={searchText}
/>
<div className="scroll-bar flex h-[150px] flex-col gap-0.5 overflow-y-scroll rounded-md border p-1">
<div className="scroll-bar bg-default mt-2 flex h-[150px] flex-col gap-0.5 overflow-y-scroll rounded-[10px] border p-1">
{memberListOptions.map((member) => (
<label
key={member.value}
@@ -20,6 +20,7 @@ import {
Tooltip,
} from "@calcom/ui";
import CreateNewOutOfOfficeEntryButton from "./CreateNewOutOfOfficeEntryButton";
import { CreateOrEditOutOfOfficeEntryModal } from "./CreateOrEditOutOfOfficeModal";
import type { BookingRedirectForm } from "./CreateOrEditOutOfOfficeModal";
@@ -53,6 +54,7 @@ export const OutOfOfficeEntriesList = () => {
className="mt-6"
headline={t("ooo_empty_title")}
description={t("ooo_empty_description")}
buttonRaw={<CreateNewOutOfOfficeEntryButton size="sm" />}
customIcon={
<div className="mt-4 h-[102px]">
<div className="flex h-full flex-col items-center justify-center p-2 md:mt-0 md:p-0">
@@ -63,7 +65,7 @@ export const OutOfOfficeEntriesList = () => {
<div className="dark:bg-darkgray-50 absolute -top-3 left-3 -z-10 h-[70px] w-[70px] rotate-[24deg] rounded-3xl border-2 border-[#e5e7eb] p-8 opacity-60 dark:opacity-90">
<div className="w-12" />
</div>
<div className="dark:bg-darkgray-50 relative z-0 flex h-[70px] w-[70px] items-center justify-center rounded-3xl border-2 border-[#e5e7eb] bg-white">
<div className="dark:bg-darkgray-50 text-inverted relative z-0 flex h-[70px] w-[70px] items-center justify-center rounded-3xl border-2 border-[#e5e7eb] bg-white">
<Icon name="clock" size={28} />
<div className="dark:bg-darkgray-50 absolute right-4 top-5 h-[12px] w-[12px] rotate-[56deg] bg-white text-lg font-bold" />
<span className="absolute right-4 top-3 font-sans text-sm font-extrabold">z</span>
@@ -121,7 +123,7 @@ export const OutOfOfficeEntriesList = () => {
<Button
className="self-center rounded-lg border"
type="button"
color="minimal"
color="secondary"
variant="icon"
data-testid={`ooo-edit-${item.toUser?.username || "n-a"}`}
StartIcon="pencil"
@@ -146,7 +148,7 @@ export const OutOfOfficeEntriesList = () => {
<Button
className="self-center rounded-lg border"
type="button"
color="minimal"
color="destructive"
variant="icon"
disabled={deleteOutOfOfficeEntryMutation.isPending}
StartIcon="trash-2"
@@ -39,7 +39,9 @@ export function DeleteBulkUsers({ users, onRemove }: Props) {
return (
<Dialog>
<DialogTrigger asChild>
<DataTableSelectionBar.Button icon="ban">{t("Delete")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="ban" color="destructive">
{t("Delete")}
</DataTableSelectionBar.Button>
</DialogTrigger>
<ConfirmationDialogContent
variety="danger"
@@ -35,11 +35,17 @@ export function DynamicLink<T extends { username: string | null }>({
<div className="items-center truncate p-2 md:max-w-[300px]">
<p className="text-center md:text-left">{domainWithoutHttps}</p>
</div>
<div className="ml-auto flex items-center justify-center md:justify-start">
<Button StartIcon="copy" size="sm" onClick={() => copyToClipboard(dynamicLinkOfSelectedUsers)}>
{!isCopied ? t("copy") : t("copied")}
<div className="ml-auto flex items-center justify-center gap-1 md:justify-start">
<Button
variant="icon"
StartIcon={isCopied ? "check" : "copy"}
size="sm"
onClick={() => copyToClipboard(dynamicLinkOfSelectedUsers)}
color="secondary">
<span className="sr-only">{!isCopied ? t("copy") : t("copied")}</span>
</Button>
<Button
color="secondary"
EndIcon="external-link"
size="sm"
href={dynamicLinkOfSelectedUsers}
@@ -98,7 +98,9 @@ export function EventTypesList({ table, orgTeams }: Props) {
<>
<Popover>
<PopoverTrigger asChild>
<DataTableSelectionBar.Button icon="link">{t("add_to_event_type")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="link" color="secondary">
{t("add_to_event_type")}
</DataTableSelectionBar.Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0 shadow-md" align="start" sideOffset={12}>
<Command>
@@ -332,7 +332,9 @@ function MassAssignAttributesBulkActionComponent({ table, filters }: Props) {
}
}}>
<PopoverTrigger asChild>
<DataTableSelectionBar.Button icon="tags">{t("add_attributes")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="tags" color="secondary">
{t("add_attributes")}
</DataTableSelectionBar.Button>
</PopoverTrigger>
{/* We dont really use shadows much - but its needed here */}
<PopoverContent className="p-0 shadow-md" align="start" sideOffset={12}>
@@ -87,7 +87,9 @@ export function TeamListBulkAction({ table }: Props) {
<>
<Popover>
<PopoverTrigger asChild>
<DataTableSelectionBar.Button icon="users">{t("add_to_team")}</DataTableSelectionBar.Button>
<DataTableSelectionBar.Button icon="users" color="secondary">
{t("add_to_team")}
</DataTableSelectionBar.Button>
</PopoverTrigger>
{/* We dont really use shadows much - but its needed here */}
<PopoverContent className="w-[200px] p-0 shadow-md" align="start" sideOffset={12}>
@@ -579,7 +579,6 @@ function UserListTableContent() {
type="button"
color="primary"
StartIcon="plus"
className="rounded-md"
onClick={() =>
dispatch({
type: "INVITE_MEMBER",
@@ -604,8 +603,12 @@ function UserListTableContent() {
</DataTableSelectionBar.Root>
)}
{numberOfSelectedRows > 0 && (
<DataTableSelectionBar.Root className="justify-center">
<p className="text-brand-subtle px-2 text-center text-xs leading-none sm:text-sm sm:font-medium">
<DataTableSelectionBar.Root
className="justify-center"
style={{
width: "max-content",
}}>
<p className="text-brand-subtle shrink-0 px-2 text-center text-xs leading-none sm:text-sm sm:font-medium">
{t("number_selected", { count: numberOfSelectedRows })}
</p>
{!isPlatformUser ? (
@@ -613,6 +616,7 @@ function UserListTableContent() {
<TeamListBulkAction table={table} />
{numberOfSelectedRows >= 2 && (
<DataTableSelectionBar.Button
color="secondary"
onClick={() => setDynamicLinkVisible(!dynamicLinkVisible)}
icon="handshake">
{t("group_meeting")}
@@ -146,7 +146,7 @@ const WebhookForm = (props: {
<Form
form={formMethods}
handleSubmit={(values) => props.onSubmit({ ...values, changeSecret, newSecret })}>
<div className="border-subtle border-x p-6">
<div className="border-subtle rounded-t-lg border p-6">
<Controller
name="subscriberUrl"
control={formMethods.control}
@@ -199,6 +199,7 @@ const WebhookForm = (props: {
<>{t("event_triggers")}</>
</Label>
<Select
grow
options={translatedTriggerOptions}
isMulti
value={selectValue}
@@ -353,7 +354,7 @@ const WebhookForm = (props: {
</Button>
</SectionBottomActions>
<div className="mt-6 rounded-md">
<div className="mb-4 mt-6 rounded-md">
<WebhookTestDisclosure />
</div>
</Form>
@@ -65,18 +65,19 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
if (!requirePayment && tabs.includes("recurring")) {
navigation.splice(3, 0, {
name: "recurring",
name: t("recurring"),
onClick: () => setCurrentTab("recurring"),
isActive: currentTab === "recurring",
href: `${url}?tabName=recurring`,
icon: "repeat",
info: `recurring_event_tab_description`,
info: t(`recurring_event_tab_description`),
"data-testid": "recurring",
});
}
tabs.includes("availability") &&
navigation.splice(1, 0, {
name: "availability",
name: t("availability"),
onClick: () => setCurrentTab("availability"),
isActive: currentTab === "availability",
href: `${url}?tabName=availability`,
@@ -93,12 +94,13 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
}`
: formMethods.getValues("scheduleName") ?? `default_schedule_name`
: formMethods.getValues("scheduleName") ?? `default_schedule_name`,
"data-testid": "availability",
});
// If there is a team put this navigation item within the tabs
if (team && tabs.includes("team")) {
navigation.splice(2, 0, {
name: "assignment",
name: t("assignment"),
onClick: () => setCurrentTab("team"),
isActive: currentTab === "team",
href: `${url}?tabName=team`,
@@ -106,6 +108,7 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
isManagedEventType ? ` - ${t("number_member", { count: watchChildrenCount || 0 })}` : ""
}`,
"data-testid": "assignment",
});
}
@@ -146,40 +149,44 @@ function getNavigation({ length, multipleDuration, t, tabs, url, onClick, curren
const tabsNavigation: VerticalTabItemProps[] = [];
tabs.includes("setup") &&
tabsNavigation.push({
name: "event_setup_tab_title",
name: t("event_setup_tab_title"),
onClick: () => onClick("setup"),
isActive: currentTab === "setup",
href: `${url}?tabName=setup`,
icon: "link",
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
"data-testid": `event_setup_tab_title`,
});
tabs.includes("limits") &&
tabsNavigation.push({
name: "event_limit_tab_title",
name: t("event_limit_tab_title"),
onClick: () => onClick("limits"),
isActive: currentTab === "limits",
href: `${url}?tabName=limits`,
icon: "clock",
info: `event_limit_tab_description`,
info: t(`event_limit_tab_description`),
"data-testid": "event_limit_tab_title",
});
tabs.includes("advanced") &&
tabsNavigation.push({
name: "event_advanced_tab_title",
name: t("event_advanced_tab_title"),
onClick: () => onClick("advanced"),
isActive: currentTab === "advanced",
href: `${url}?tabName=advanced`,
icon: "sliders-vertical",
info: `event_advanced_tab_description`,
info: t(`event_advanced_tab_description`),
"data-testid": "event_advanced_tab_title",
});
tabs.includes("payments") &&
tabsNavigation.push({
name: "event_payments_tab_title",
name: t("event_payments_tab_title"),
onClick: () => onClick("payments"),
isActive: currentTab === "payments",
href: `${url}?tabName=payments`,
icon: "credit-card",
info: `event_payments_tab_description`,
info: t(`event_payments_tab_description`),
"data-testid": "event_payments_tab_title",
});
return tabsNavigation;
@@ -83,56 +83,61 @@ export const useTabsNavigations = ({
if (!requirePayment) {
navigation.splice(3, 0, {
name: "recurring",
name: t("recurring"),
href: `/event-types/${formMethods.getValues("id")}?tabName=recurring`,
icon: "repeat",
info: `recurring_event_tab_description`,
info: t(`recurring_event_tab_description`),
"data-testid": "recurring",
});
}
navigation.splice(1, 0, {
name: "availability",
name: t("availability"),
href: `/event-types/${formMethods.getValues("id")}?tabName=availability`,
icon: "calendar",
info:
isManagedEventType || isChildrenManagedEventType
? formMethods.getValues("schedule") === null
? "members_default_schedule"
? t("members_default_schedule")
: isChildrenManagedEventType
? `${
formMethods.getValues("scheduleName")
? `${formMethods.getValues("scheduleName")} - ${t("managed")}`
: `default_schedule_name`
: t(`default_schedule_name`)
}`
: formMethods.getValues("scheduleName") ?? `default_schedule_name`
: formMethods.getValues("scheduleName") ?? `default_schedule_name`,
: formMethods.getValues("scheduleName") ?? t(`default_schedule_name`)
: formMethods.getValues("scheduleName") ?? t(`default_schedule_name`),
"data-testid": "availability",
});
// If there is a team put this navigation item within the tabs
if (team) {
navigation.splice(2, 0, {
name: "assignment",
name: t("assignment"),
href: `/event-types/${formMethods.getValues("id")}?tabName=team`,
icon: "users",
info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
isManagedEventType ? ` - ${t("number_member", { count: watchChildrenCount || 0 })}` : ""
}`,
"data-testid": "assignment",
});
}
const showInstant = !(isManagedEventType || isChildrenManagedEventType);
if (showInstant) {
if (team) {
navigation.push({
name: "instant_tab_title",
name: t("instant_tab_title"),
href: `/event-types/${eventType.id}?tabName=instant`,
icon: "phone-call",
info: `instant_event_tab_description`,
info: t(`instant_event_tab_description`),
"data-testid": "instant_tab_title",
});
}
}
navigation.push({
name: "webhooks",
name: t("webhooks"),
href: `/event-types/${formMethods.getValues("id")}?tabName=webhooks`,
icon: "webhook",
info: `${activeWebhooksNumber} ${t("active")}`,
"data-testid": "webhooks",
});
const hidden = true; // hidden while in alpha trial. you can access it with tabName=ai
if (team && hidden) {
@@ -140,7 +145,8 @@ export const useTabsNavigations = ({
name: "Cal.ai",
href: `/event-types/${eventType.id}?tabName=ai`,
icon: "sparkles",
info: "cal_ai_event_tab_description", // todo `cal_ai_event_tab_description`,
info: t("cal_ai_event_tab_description"), // todo `cal_ai_event_tab_description`,
"data-testid": "Cal.ai",
});
}
return navigation;
@@ -189,35 +195,40 @@ function getNavigation({
return [
{
name: "event_setup_tab_title",
name: t("event_setup_tab_title"),
href: `/event-types/${id}?tabName=setup`,
icon: "link",
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
"data-testid": `event_setup_tab_title`,
},
{
name: "event_limit_tab_title",
name: t("event_limit_tab_title"),
href: `/event-types/${id}?tabName=limits`,
icon: "clock",
info: `event_limit_tab_description`,
info: t(`event_limit_tab_description`),
"data-testid": "event_limit_tab_title",
},
{
name: "event_advanced_tab_title",
name: t("event_advanced_tab_title"),
href: `/event-types/${id}?tabName=advanced`,
icon: "sliders-vertical",
info: `event_advanced_tab_description`,
info: t(`event_advanced_tab_description`),
"data-testid": "event_advanced_tab_title",
},
{
name: "apps",
name: t("apps"),
href: `/event-types/${id}?tabName=apps`,
icon: "grid-3x3",
//TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
"data-testid": "apps",
},
{
name: "workflows",
name: t("workflows"),
href: `/event-types/${id}?tabName=workflows`,
icon: "zap",
info: `${enabledWorkflowsNumber} ${t("active")}`,
"data-testid": "workflows",
},
] satisfies VerticalTabItemProps[];
}
File diff suppressed because one or more lines are too long
+30 -35
View File
@@ -1,3 +1,4 @@
import { cva } from "class-variance-authority";
import classNames from "classnames";
import type { ReactNode } from "react";
import { forwardRef } from "react";
@@ -5,36 +6,36 @@ import { forwardRef } from "react";
import { Icon } from "../icon";
import type { IconName } from "../icon";
export const alertStyles = cva("rounded-[10px] p-3", {
variants: {
severity: {
neutral: "bg-default border-subtle border-[1px] text-default",
info: "bg-semantic-info-subtle text-semantic-info",
warning: "bg-semantic-attention-subtle text-semantic-attention",
error: "bg-semantic-error-subtle text-semantic-error",
},
},
defaultVariants: {
severity: "neutral",
},
});
export interface AlertProps {
title?: ReactNode;
// @TODO: Message should be children, more flexible?
message?: ReactNode;
// @TODO: Provide action buttons so style is always the same.
actions?: ReactNode;
className?: string;
iconClassName?: string;
// @TODO: Success and info shouldn't exist as per design?
severity: "success" | "warning" | "error" | "info" | "neutral" | "green";
severity: "warning" | "error" | "info" | "neutral";
CustomIcon?: IconName;
customIconColor?: string;
}
export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
const { severity, iconClassName, CustomIcon, customIconColor } = props;
return (
<div
data-testid="alert"
ref={ref}
className={classNames(
"rounded-md p-3",
props.className,
severity === "error" && "bg-red-100 text-red-900 dark:bg-red-900 dark:text-red-200",
severity === "warning" && "text-attention bg-attention dark:bg-orange-900 dark:text-orange-200",
severity === "info" && "bg-blue-100 text-blue-900 dark:bg-blue-900 dark:text-blue-200",
severity === "green" && "bg-success text-success",
severity === "success" && "bg-inverted text-inverted",
severity === "neutral" && "bg-subtle text-default"
)}>
<div data-testid="alert" ref={ref} className={alertStyles({ severity, className: props.className })}>
<div className="relative flex md:flex-row">
{CustomIcon ? (
<div className="flex-shrink-0">
@@ -42,16 +43,16 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
name={CustomIcon}
data-testid="custom-icon"
aria-hidden="true"
className={classNames(`h-5 w-5`, iconClassName, customIconColor ?? "text-default")}
className={classNames(`h4 mr-2 w-4`, iconClassName, customIconColor ?? "text-default")}
/>
</div>
) : (
<div className="flex-shrink-0">
<div className={classNames("mr-2 flex-shrink-0", props.title ? "" : "mt-0.5")}>
{severity === "error" && (
<Icon
name="circle-x"
data-testid="circle-x"
className={classNames("h-5 w-5 text-red-900 dark:text-red-200", iconClassName)}
className={classNames("h4 w-4", iconClassName)}
aria-hidden="true"
/>
)}
@@ -59,7 +60,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
<Icon
name="triangle-alert"
data-testid="alert-triangle"
className={classNames("text-attention h-5 w-5 dark:text-orange-200", iconClassName)}
className={classNames("h4 w-4", iconClassName)}
aria-hidden="true"
/>
)}
@@ -67,7 +68,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
<Icon
name="info"
data-testid="info"
className={classNames("h-5 w-5 text-blue-900 dark:text-blue-200", iconClassName)}
className={classNames("h4 w-4", iconClassName)}
aria-hidden="true"
/>
)}
@@ -75,26 +76,20 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
<Icon
name="info"
data-testid="neutral"
className={classNames("text-default h-5 w-5 fill-transparent", iconClassName)}
aria-hidden="true"
/>
)}
{severity === "success" && (
<Icon
name="circle-check"
data-testid="circle-check"
className={classNames("fill-muted text-default h-5 w-5", iconClassName)}
className={classNames("text-default h4 w-4 fill-transparent", iconClassName)}
aria-hidden="true"
/>
)}
</div>
)}
<div className="flex flex-grow flex-col sm:flex-row">
<div className="ltr:ml-3 rtl:mr-3">
<h3 className="text-sm font-medium">{props.title}</h3>
<div className="text-sm">{props.message}</div>
<div className="space-y-1 ltr:ml-3 rtl:mr-3">
{props.title && <h3 className="text-sm font-medium leading-none">{props.title}</h3>}
{props.message && <div className="text-sm leading-5">{props.message}</div>}
</div>
{props.actions && <div className="ml-auto mt-2 text-sm sm:mt-0 md:relative">{props.actions}</div>}
{props.actions && (
<div className="ml-auto mt-auto text-sm sm:mt-0 md:relative">{props.actions}</div>
)}
</div>
</div>
</div>
@@ -33,9 +33,4 @@ describe("Tests for Alert component", () => {
render(<Alert severity="neutral" />);
expect(await screen.findByTestId("neutral")).toBeInTheDocument();
});
test("Should render corresponding icon: success", async () => {
render(<Alert severity="success" />);
expect(await screen.findByTestId("circle-check")).toBeInTheDocument();
});
});
+49 -16
View File
@@ -1,5 +1,6 @@
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { Provider as TooltipPrimitiveProvider } from "@radix-ui/react-tooltip";
import { cva } from "class-variance-authority";
import Link from "next/link";
import classNames from "@calcom/lib/classNames";
@@ -11,7 +12,8 @@ type Maybe<T> = T | null | undefined;
export type AvatarProps = {
className?: string;
size?: "xxs" | "xs" | "xsm" | "sm" | "md" | "mdLg" | "lg" | "xl";
size?: "xs" | "xsm" | "sm" | "md" | "mdLg" | "lg" | "xl";
shape?: "circle" | "square";
imageSrc?: Maybe<string>;
title?: string;
alt: string;
@@ -23,34 +25,65 @@ export type AvatarProps = {
"data-testid"?: string;
};
const sizesPropsBySize = {
xxs: "w-3.5 h-3.5 min-w-3.5 min-h-3.5", // 14px
xs: "w-4 h-4 min-w-4 min-h-4 max-h-4", // 16px
xsm: "w-5 h-5 min-w-5 min-h-5", // 20px
sm: "w-6 h-6 min-w-6 min-h-6", // 24px
md: "w-8 h-8 min-w-8 min-h-8", // 32px
mdLg: "w-10 h-10 min-w-10 min-h-10", //40px
lg: "w-16 h-16 min-w-16 min-h-16", // 64px
xl: "w-24 h-24 min-w-24 min-h-24", // 96px
} as const;
const avatarClasses = cva(
"bg-emphasis border-default relative inline-flex aspect-square items-center justify-center border align-top",
{
variants: {
size: {
xs: "w-4 h-4 min-w-4 min-h-4 max-h-4", // 16px
xsm: "w-5 h-5 min-w-5 min-h-5", // 20px
sm: "w-6 h-6 min-w-6 min-h-6", // 24px
md: "w-8 h-8 min-w-8 min-h-8", // 32px
mdLg: "w-10 h-10 min-w-10 min-h-10", // 40px
lg: "w-16 h-16 min-w-16 min-h-16", // 64px
xl: "w-24 h-24 min-w-24 min-h-24", // 96px
},
shape: {
circle: "rounded-full",
square: "",
},
},
defaultVariants: {
size: "md",
shape: "circle",
},
compoundVariants: [
{
size: ["xs", "xsm", "sm"],
shape: "square",
className: "rounded",
},
{
size: ["md"],
shape: "square",
className: "rounded-md",
},
{
size: ["mdLg", "lg", "xl"],
shape: "square",
className: "rounded-[10px]",
},
],
}
);
export function Avatar(props: AvatarProps) {
const { imageSrc, size = "md", alt, title, href, indicator } = props;
const rootClass = classNames("aspect-square rounded-full", sizesPropsBySize[size]);
const avatarClass = avatarClasses({ size, shape: props.shape });
const rootClass = classNames("aspect-square rounded-full", avatarClass);
let avatar = (
<AvatarPrimitive.Root
data-testid={props?.["data-testid"]}
className={classNames(
"bg-emphasis item-center relative inline-flex aspect-square justify-center rounded-full align-top",
avatarClass,
indicator ? "overflow-visible" : "overflow-hidden",
props.className,
sizesPropsBySize[size]
props.className
)}>
<>
<AvatarPrimitive.Image
src={imageSrc ?? undefined}
alt={alt}
className={classNames("aspect-square rounded-full", sizesPropsBySize[size])}
className={classNames("aspect-square", avatarClass)}
/>
<AvatarPrimitive.Fallback
delayMs={600}
+4 -3
View File
@@ -20,11 +20,12 @@ export const badgeStyles = cva("font-medium inline-flex items-center justify-cen
red: "bg-error text-error",
error: "bg-error text-error",
grayWithoutHover: "bg-gray-100 text-gray-800 dark:bg-darkgray-200 dark:text-darkgray-800",
purple: "bg-purple-50 text-purple-800",
},
size: {
sm: "px-1 py-0.5 text-xs leading-3",
md: "py-1 px-1.5 text-xs leading-3",
lg: "py-1 px-2 text-sm leading-4",
sm: "px-1 py-1 text-[10px] leading-none",
md: "py-1 px-1.5 text-xs leading-none",
lg: "py-1 px-1.5 text-sm leading-none rounded-lg",
},
},
defaultVariants: {
+53 -58
View File
@@ -1,12 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable playwright/missing-playwright-await */
import { render, screen, fireEvent } from "@testing-library/react";
import { vi } from "vitest";
import { Icon } from "../icon";
import { Badge, badgeStyles } from "./Badge";
describe("Tests for Badge component", () => {
describe("Badge", () => {
const variants = [
"default",
"warning",
@@ -18,74 +16,71 @@ describe("Tests for Badge component", () => {
"red",
"error",
"grayWithoutHover",
];
"purple",
] as const;
const sizes = ["sm", "md", "lg"];
const children = "Test Badge";
test.each(variants)("Should apply variant class", (variant) => {
render(<Badge variant={variant as any}>{children}</Badge>);
const badgeClass = screen.getByText(children).className;
const badgeComponentClass = badgeStyles({ variant: variant as any });
expect(badgeClass).toEqual(badgeComponentClass);
});
test.each(sizes)("Should apply size class", (size) => {
render(<Badge size={size as any}>{children}</Badge>);
const badgeClass = screen.getByText(children).className;
const badgeComponentClass = badgeStyles({ size: size as any });
expect(badgeClass).toEqual(badgeComponentClass);
});
test("Should render without errors", () => {
render(<Badge>{children}</Badge>);
expect(screen.getByText(children)).toBeInTheDocument();
});
test("Should render WithDot if the prop is true and shouldn't render if is false", async () => {
const { rerender } = render(<Badge withDot>{children}</Badge>);
expect(await screen.findByTestId("go-primitive-dot")).toBeInTheDocument();
rerender(<Badge>{children}</Badge>);
expect(screen.queryByTestId("go-primitive-dot")).not.toBeInTheDocument();
});
test("Should render with a startIcon when startIcon prop is provided shouldn't render if is false", () => {
const { rerender } = render(<Badge customStartIcon={<svg data-testid="start-icon" />}>{children}</Badge>);
expect(screen.getByTestId("start-icon")).toBeInTheDocument();
rerender(<Badge>{children}</Badge>);
expect(screen.queryByTestId("start-icon")).not.toBeInTheDocument();
});
test("Should render as a button when onClick prop is provided and shouldn't if is not", () => {
const handleClick = vi.fn();
const { rerender } = render(<Badge onClick={handleClick}>{children}</Badge>);
test.each(variants)("renders %s variant correctly", (variant) => {
render(<Badge variant={variant}>{children}</Badge>);
const badge = screen.getByText(children);
expect(badge.tagName).toBe("BUTTON");
fireEvent.click(badge);
expect(handleClick).toHaveBeenCalledTimes(1);
rerender(<Badge>{children}</Badge>);
const updateBadge = screen.getByText(children);
expect(updateBadge.tagName).not.toBe("BUTTON");
expect(badge.className).toContain(badgeStyles({ variant }));
});
test("Should render as a div when onClick prop is not provided", () => {
test("renders with default props", () => {
render(<Badge>{children}</Badge>);
const badge = screen.getByText(children);
expect(badge).toBeInTheDocument();
expect(badge.tagName).toBe("DIV");
});
test("Should render children when provided", () => {
const { getByText } = render(
test("renders with dot when withDot prop is true", () => {
render(<Badge withDot>{children}</Badge>);
expect(screen.getByTestId("go-primitive-dot")).toBeInTheDocument();
});
test("renders with startIcon when provided", () => {
render(<Badge startIcon="info">{children}</Badge>);
expect(screen.getByTestId("start-icon")).toBeInTheDocument();
});
test("renders with customStartIcon when provided", () => {
render(<Badge customStartIcon={<Icon name="info" data-testid="custom-icon" />}>{children}</Badge>);
expect(screen.getByTestId("custom-icon")).toBeInTheDocument();
});
test("renders as button with onClick handler", () => {
const handleClick = vi.fn();
render(<Badge onClick={handleClick}>{children}</Badge>);
const badge = screen.getByText(children);
expect(badge.tagName).toBe("BUTTON");
fireEvent.click(badge);
expect(handleClick).toHaveBeenCalledTimes(1);
});
test("renders with rounded style when rounded prop is true", () => {
render(<Badge rounded>{children}</Badge>);
const badge = screen.getByText(children);
expect(badge.className).toContain("h-5 w-5 rounded-full p-0");
});
test("renders with additional className when provided", () => {
const customClass = "custom-class";
render(<Badge className={customClass}>{children}</Badge>);
const badge = screen.getByText(children);
expect(badge.className).toContain(customClass);
});
test("renders nested children correctly", () => {
render(
<Badge>
<span>Child element 1</span>
<span>Child element 2</span>
<span data-testid="child-1">Child 1</span>
<span data-testid="child-2">Child 2</span>
</Badge>
);
expect(getByText("Child element 1")).toBeInTheDocument();
expect(getByText("Child element 2")).toBeInTheDocument();
expect(screen.getByTestId("child-1")).toBeInTheDocument();
expect(screen.getByTestId("child-2")).toBeInTheDocument();
});
});

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