From 25fa46fd3417f81cec03375eb0bd1e0b9de166bb Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Fri, 23 Jan 2026 12:19:00 +0100 Subject: [PATCH] fix(coss-ui): convert /settings/my-account/general to coss-ui (#26053) * fix(coss-ui): convert /settings/my-account/general to coss-ui * update guide * fix: add menuPosition fixed to TimezoneSelect in TravelScheduleModal Co-Authored-By: eunjae@cal.com * fix: use menuPortalTarget to render TimezoneSelect dropdown above dialog footer Co-Authored-By: eunjae@cal.com * fix: use Object.assign for type-safe styles in TimezoneSelect Co-Authored-By: eunjae@cal.com * revert comment * fix: guard document access for SSR in TravelScheduleModal Addresses Cubic AI review feedback: menuPortalTarget={document.body} will throw during server rendering where document is undefined. Added typeof check to guard against SSR. Co-Authored-By: unknown <> * change dialog backdrop from blur to dim * fix: add menuPlacement auto to open dropdown upward on mobile Co-Authored-By: eunjae@cal.com * fix: use menuPlacement top on mobile for TimezoneSelect dropdown Co-Authored-By: eunjae@cal.com * fix: make timezone button stack vertically on mobile to prevent overflow Co-Authored-By: eunjae@cal.com * fix: make timezone select and button 50/50 width on larger viewports Co-Authored-By: eunjae@cal.com * fix date range picker issue on mobile * prevent travel schedule dialog dismissal with date picker --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../settings/TravelScheduleModal.tsx | 163 +-- .../settings/my-account/general-skeleton.tsx | 17 +- .../settings/my-account/general-view.tsx | 7 +- packages/coss-ui/migration_guide.md | 109 ++ .../coss-ui/radix_shadcn_migration_guide.md | 965 ++++++++++++++++++ .../date-range-picker/DateRangePicker.tsx | 16 +- packages/ui/package.json | 1 + 7 files changed, 1195 insertions(+), 83 deletions(-) create mode 100644 packages/coss-ui/migration_guide.md create mode 100644 packages/coss-ui/radix_shadcn_migration_guide.md diff --git a/apps/web/components/settings/TravelScheduleModal.tsx b/apps/web/components/settings/TravelScheduleModal.tsx index e4ae2c6fc2..e9dae924df 100644 --- a/apps/web/components/settings/TravelScheduleModal.tsx +++ b/apps/web/components/settings/TravelScheduleModal.tsx @@ -1,20 +1,29 @@ -import { useState } from "react"; -import type { UseFormSetValue } from "react-hook-form"; - import dayjs from "@calcom/dayjs"; import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences"; -import { Dialog } from "@calcom/features/components/controlled-dialog"; import { TimezoneSelect } from "@calcom/features/components/timezone-select"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button } from "@calcom/ui/components/button"; -import { DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog"; -import { Label, SettingsToggle, DateRangePicker, DatePicker } from "@calcom/ui/components/form"; - +import { DatePicker, SettingsToggle } from "@calcom/ui/components/form"; +import { DatePickerWithRange as DateRangePicker } from "@calcom/ui/components/form/date-range-picker/DateRangePicker"; +import { Button } from "@coss/ui/components/button"; +import { + Dialog, + DialogClose, + DialogDescription, + DialogFooter, + DialogHeader, + DialogPanel, + DialogPopup, + DialogTitle, +} from "@coss/ui/components/dialog"; +import { Label } from "@coss/ui/components/label"; +import { useIsMobile } from "@coss/ui/hooks/use-mobile"; +import { useState } from "react"; +import type { UseFormSetValue } from "react-hook-form"; import type { FormValues } from "~/settings/my-account/general-view"; interface TravelScheduleModalProps { open: boolean; - onOpenChange: () => void; + onOpenChange: (open: boolean) => void; setValue: UseFormSetValue; existingSchedules: FormValues["travelSchedules"]; } @@ -27,12 +36,14 @@ const TravelScheduleModal = ({ }: TravelScheduleModalProps) => { const { t } = useLocale(); const { timezone: preferredTimezone } = useTimePreferences(); + const isMobile = useIsMobile(); const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const [selectedTimeZone, setSelectedTimeZone] = useState(preferredTimezone); const [isNoEndDate, setIsNoEndDate] = useState(false); + const [isDateRangeOpen, setIsDateRangeOpen] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const isOverlapping = (newSchedule: { startDate: Date; endDate?: Date }) => { @@ -64,6 +75,7 @@ const TravelScheduleModal = ({ setEndDate(new Date()); setSelectedTimeZone(preferredTimezone); setIsNoEndDate(false); + setIsDateRangeOpen(false); }; const createNewSchedule = () => { @@ -75,7 +87,7 @@ const TravelScheduleModal = ({ if (!isOverlapping(newSchedule)) { setValue("travelSchedules", existingSchedules.concat(newSchedule), { shouldDirty: true }); - onOpenChange(); + onOpenChange(false); resetValues(); } else { setErrorMessage(t("overlaps_with_existing_schedule")); @@ -83,65 +95,86 @@ const TravelScheduleModal = ({ }; return ( - - -
- {!isNoEndDate ? ( - <> - - { - // If newStartDate does become undefined - we resort back to to-todays date - setStartDate(newStartDate ?? new Date()); - setEndDate(newEndDate); + { + onOpenChange(nextOpen); + if (!nextOpen) { + setIsDateRangeOpen(false); + } + }}> + + + {t("travel_schedule")} + {t("travel_schedule_description")} + + +
+ {!isNoEndDate ? ( + <> + + { + // If newStartDate does become undefined - we resort back to to-todays date + setStartDate(newStartDate ?? new Date()); + setEndDate(newEndDate); + setErrorMessage(""); + }} + /> + + ) : ( + <> + + { + setStartDate(newDate); + setErrorMessage(""); + }} + /> + + )} +
{errorMessage}
+ +
+ { + setEndDate(!checked ? startDate : undefined); + setIsNoEndDate(checked); + if (checked) { + setIsDateRangeOpen(false); + } setErrorMessage(""); }} /> - - ) : ( - <> - - { - setStartDate(newDate); - setErrorMessage(""); - }} - /> - - )} -
{errorMessage}
-
- { - setEndDate(!e ? startDate : undefined); - setIsNoEndDate(e); - setErrorMessage(""); - }} +
+ + + setSelectedTimeZone(value)} + menuPortalTarget={typeof document === "undefined" ? undefined : document.body} + menuPlacement={isMobile ? "top" : "auto"} + styles={{ menuPortal: (base) => Object.assign({}, base, { zIndex: 9999 }) }} + className="mb-11 mt-2 w-full rounded-md text-sm" />
- - setSelectedTimeZone(value)} - className="mb-11 mt-2 w-full rounded-md text-sm" - /> -
- - +
+ + }>{t("cancel")} - +
); }; diff --git a/apps/web/modules/settings/my-account/general-skeleton.tsx b/apps/web/modules/settings/my-account/general-skeleton.tsx index f3400a4602..d402ab7da1 100644 --- a/apps/web/modules/settings/my-account/general-skeleton.tsx +++ b/apps/web/modules/settings/my-account/general-skeleton.tsx @@ -2,22 +2,21 @@ import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; +import { Skeleton } from "@coss/ui/components/skeleton"; export const SkeletonLoader = () => { const { t } = useLocale(); return ( - +
- - - - - - + + + + +
- +
); }; diff --git a/apps/web/modules/settings/my-account/general-view.tsx b/apps/web/modules/settings/my-account/general-view.tsx index 66c0b329bb..f9a82214b3 100644 --- a/apps/web/modules/settings/my-account/general-view.tsx +++ b/apps/web/modules/settings/my-account/general-view.tsx @@ -204,8 +204,8 @@ const GeneralView = ({ user, travelSchedules }: GeneralViewProps) => { -
-
+
+
{
{!watchedTzSchedules.length && ( + +``` + + + +```tsx title="coss ui" +// [!code word:render] +}>Edit profile +``` + + +### Component Naming Conventions + +Some components have updated names for clarity and consistency: + +- `*Content` → `*Popup` or `*Panel` (e.g., `DialogContent` → `DialogPopup`) +- Legacy names are often kept for backward compatibility + +## Component Migration Guides + +### Accordion + +**Quick Checklist:** +- Replace `type="multiple"` → `multiple={true}` on `Accordion` +- Remove `type="single"` from `Accordion` +- Remove `collapsible` from `Accordion` +- Always use arrays for `defaultValue` +- Use `AccordionPanel` going forward; `AccordionContent` remains for legacy +- If you used `asChild` on parts, switch to the `render` prop + +**Prop Mapping:** + +| Component | Radix UI Prop | Base UI Prop | +| ----------- | ----------------------------------------- | ------------------------------------- | +| `Accordion` | `type` (enum, `"single"` or `"multiple"`) | `multiple` (boolean, default: `false`) | +| `Accordion` | `collapsible` | _removed_ | + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:type="multiple"] +// [!code word:collapsible] +// [!code word:"item-1":1] + + + Title + Content + + +``` + + + +```tsx title="coss ui" +// [!code word:multiple={true}] +// [!code word:\{\["item-1"\]\}] + + + Title + Content + + +``` + + +### Alert + +**New Variants:** + +We've added new colored variants for better semantic meaning: + +| Variant | Description | +| --------- | --------------------------------- | +| `info` | Displays an info alert (blue) | +| `success` | Displays a success alert (green) | +| `warning` | Displays a warning alert (yellow) | +| `error` | Displays a error alert (red) | + +Ensure you have the following variables imported in your CSS file: + +- `--destructive-foreground` +- `--info` +- `--info-foreground` +- `--success` +- `--success-foreground` +- `--warning` +- `--warning-foreground` + +### Alert Dialog + +**Quick Checklist:** +- Replace `asChild` → `render` on `AlertDialogTrigger` and closing buttons +- Replace `AlertDialogAction` and `AlertDialogCancel` → `AlertDialogClose` +- Prefer `AlertDialogPopup`; `AlertDialogContent` remains for legacy +- Use `AlertDialogPanel` to wrap main content between `AlertDialogHeader` and `AlertDialogFooter` +- If you used `asChild` on any other parts, switch to the `render` prop + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] +// [!code word:AlertDialogCancel] +// [!code word:AlertDialogAction] + + + + + + + Are you absolutely sure? + + This action cannot be undone. + + + + Cancel + + + + + + +``` + + + +```tsx title="coss ui" +// [!code word:render] +// [!code word:AlertDialogClose] +// [!code word:Content] + + }> + Show Alert Dialog + + + + Are you absolutely sure? + + This action cannot be undone. + + + Content + + }> + Cancel + + }> + Continue + + + + +``` + + +### Avatar + +**Quick Checklist:** +- Replace `asChild` → `render` on `Avatar` + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + U + + +``` + + + +```tsx title="coss ui" +// [!code word:render] +}> + + U + +``` + + +### Badge + +**Quick Checklist:** +- Replace `asChild` → `render` on `Badge` + +**Size Comparison:** + +Compared to shadcn/ui, our `Badge` component includes size variants for better density control. shadcn/ui badges have a fixed size, while our component offers flexible sizing with `sm`, `default`, and `lg` options. + +So, if you want to preserve the original shadcn/ui badge size, you should use the `lg` size in coss ui. + +**New Variants:** + +We've added new colored variants to the existing ones (`default`, `destructive`, `outline`, `secondary`) for better semantic meaning and visual communication: + +| Variant | Description | +| --------- | ---------------------------------- | +| `info` | Blue badge for information | +| `success` | Green badge for success states | +| `warning` | Yellow badge for warnings | +| `error` | Red badge for errors | + +Ensure you have the following variables imported in your CSS file: + +- `--destructive-foreground` +- `--info` +- `--info-foreground` +- `--success` +- `--success-foreground` +- `--warning` +- `--warning-foreground` + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + New + +``` + + + +```tsx title="coss ui" +// [!code word:render] +}>New +``` + + +### Button + +**Quick Checklist:** +- Replace `asChild` → `render` on `Button` + +**Size Comparison:** + +coss ui button sizes are more compact compared to shadcn/ui, making them better suited for dense applications. We also introduce new sizes (`xs`, `xl`, `icon-sm`, `icon-lg`) for more granular control: + +| Size | Height (shadcn/ui) | Height (coss ui) | +| --------- | ------------------ | ---------------- | +| `xs` | - | 24px | +| `sm` | 32px | 28px | +| `default` | 36px | 32px | +| `lg` | 40px | 36px | +| `xl` | - | 40px | +| `icon` | 36px | 32px | +| `icon-sm` | - | 28px | +| `icon-lg` | - | 36px | + +So, for example, if you were using the `default` size in shadcn/ui and you want to preserve the original height, you should use the `lg` size in coss ui. + +**New Variants:** + +We've added a new `destructive-outline` variant for better UX patterns: + +- **Primary actions**: Use `destructive` (solid red) for the main destructive action +- **Secondary triggers**: Use `destructive-outline` (outline red) to avoid alarming red buttons in the main interface + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + +``` + + + +```tsx title="coss ui" +// [!code word:render] + +``` + + +### Card + +**Quick Checklist:** +- Use `CardPanel` going forward; `CardContent` remains for legacy + +### Checkbox + +**Quick Checklist:** +- Replace `asChild` → `render` on `Checkbox` + + +### Collapsible + +**Quick Checklist:** +- Replace `asChild` → `render` on `CollapsibleTrigger` +- Prefer `CollapsiblePanel`; `CollapsibleContent` remains for legacy + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + Content here + +``` + + + +```tsx title="coss ui" +// [!code word:render] +// [!code word:CollapsiblePanel] + + }>Toggle + Content here + +``` + + +### Command + +The API is significantly different from shadcn/ui (cmdk). Please review both docs before migrating: [cmdk Docs](https://cmdk.paco.me/) and [shadcn/ui Command](https://ui.shadcn.com/docs/components/command), and our Base UI Autocomplete docs. + +**Key Differences:** + +- No `cmdk` dependency - built entirely with Base UI's Autocomplete and Dialog components +- Data-driven approach - pass an `items` array to `Command` and use render functions instead of manually composing `CommandItem` children +- Use `CommandCollection` within `CommandGroup` when rendering grouped data with the items pattern +- Use `CommandDialog`, `CommandDialogTrigger`, and `CommandDialogPopup` for dialog functionality instead of composing separate Dialog components +- `CommandGroup` uses `` as a child instead of a `heading` prop + +### Dialog + +**Quick Checklist:** +- Replace `asChild` → `render` on `DialogTrigger` and closing buttons +- Prefer `DialogPopup`; `DialogContent` remains for legacy +- Use `DialogPanel` to wrap main content between `DialogHeader` and `DialogFooter` +- If you used `asChild` on any other parts, switch to the `render` prop + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + + + Dialog Title + Dialog Description + + + + + + + + +``` + + + +```tsx title="coss ui" +// [!code word:render] +// [!code word:Content] + + }> + Show Dialog + + + + Dialog Title + Dialog Description + + Content + + }>Cancel + + + +``` + + +### Group (Button Group) + +**Quick Checklist:** +- Prefer `Group*` component names; `ButtonGroup*` remain for compatibility +- `GroupSeparator` is **always required** between controls, including outline buttons (unlike shadcn where separators are optional for outline buttons). This ensures consistent focus state handling and better accessibility +- If you used `asChild` on `ButtonGroupText`, switch to the `render` prop for custom components + +### Input + +Compared to shadcn/ui, our `Input` component includes size variants for better density control. shadcn/ui inputs have a fixed height of 36px, while our component offers flexible sizing with `sm` (28px), `default` (32px), and `lg` (36px) options. + +So, if you want to preserve the original shadcn/ui input height (36px), you should use the `lg` size in coss ui. + +### Input Group + +**Quick Checklist:** +- No `InputGroupButton` component - use the regular `Button` component directly inside `InputGroupAddon` instead +- To disable an input group, disable the `InputGroupInput` or `InputGroupTextarea` directly (and any Button inside it) - no need to add a `data-disabled` attribute on `InputGroup`. + +### Menu + +**Prop Mapping:** + +| Component | Radix UI Prop | Base UI Prop | +| ---------- | ------------- | ------------ | +| `MenuItem` | `onSelect` | `onClick` | + +**Quick Checklist:** +- Replace `asChild` → `render` on `MenuTrigger` and `MenuItem` +- Replace `onSelect` → `onClick` on menu items +- Update import path from `@/components/ui/dropdown-menu` → `@/components/ui/menu` +- Prefer `Menu*` component names; `DropdownMenu*` remain for legacy +- Prefer `MenuGroupLabel` instead of `DropdownMenuLabel` +- Prefer `MenuPopup` instead of `DropdownMenuContent` +- Prefer `MenuSubPopup` instead of `DropdownMenuSubContent` +- If you used `asChild` on any other parts, switch to the `render` prop + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:onSelect] + + Open menu + + { + console.log("Dashboard") + }} + > + Dashboard + + Settings + Sign out + + +``` + + + +```tsx title="coss ui" +// [!code word:onClick] + + Open menu + + { + console.log("Dashboard") + }} + > + Dashboard + + Settings + Sign out + + +``` + + +### Popover + +**Quick Checklist:** +- Replace `asChild` → `render` on `PopoverTrigger` and closing buttons +- Prefer `PopoverPopup`; `PopoverContent` remains for legacy +- If you used `asChild` on any other parts, switch to the `render` prop + +**Additional Notes:** + +Base UI introduces `PopoverTitle` and `PopoverDescription` to structure headings and helper text inside the popup. Base UI also introduces a `PopoverClose` component for adding close buttons to the popup. + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + +

Popover Title

+

Popover Description

+
+
+``` +
+ + +```tsx title="coss ui" +// [!code word:PopoverTitle] +// [!code word:PopoverDescription] +// [!code word:PopoverClose] +// [!code word:render:2] + + }> + Open Popover + + + Popover Title + Popover Description + }>Close + + +``` + + +### Preview Card + +**Quick Checklist:** +- Update import path from `@/components/ui/hover-card` → `@/components/ui/preview-card` +- Prefer `PreviewCard*` component names; `HoverCard*` remain for legacy +- Prefer `PreviewCardPopup` instead of `HoverCardContent` +- If you used `asChild` on parts, switch to the `render` prop + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + Preview Card Content + +``` + + + +```tsx title="coss ui" +// [!code word:render] + + }> + Open Preview Card + + Preview Card Content + +``` + + +### Progress + +**Quick Checklist:** +- Prefer `ProgressLabel` and `ProgressValue` for label/value instead of inline elements +- If you render children inside `Progress`, you must include `ProgressTrack` and `ProgressIndicator` (otherwise the bar will not display). Without children, a default bar is rendered for you +- If you used `asChild`, switch to the `render` prop + +**Additional Notes:** + +Base UI introduces separate parts — `ProgressLabel`, `ProgressValue`, `ProgressTrack`, and `ProgressIndicator` — which you compose inside `Progress` for greater flexibility. + +### Radio Group + +**Quick Checklist:** +- Use `Radio` going forward; `RadioGroupItem` remains for legacy +- Replace `asChild` → `render` on parts if used + +### Scroll Area + +**Quick Checklist:** +- If you used `asChild` on parts, switch to the `render` prop + +### Select + +**Important:** Base UI changes how options are provided. Instead of deriving options from children only (Radix pattern), you should pass an `items` prop (array or record) so values and labels are known before hydration. This avoids SSR pitfalls and improves mount performance. Alternatively, provide a function child to `SelectValue` to format the label. See the [Base UI Select docs](https://base-ui.com/react/components/select). + +**Prop Mapping:** + +| Component | Radix UI Prop | Base UI Prop | +| ------------- | ---------------------- | --------------- | +| `Select` | `items` | `items` | +| `SelectValue` | `placeholder` | _removed_ | +| `SelectPopup` | `alignItemWithTrigger` | _no equivalent_ | + +**Quick Checklist:** +- Set `items` prop on `Select` +- Remove `placeholder` from `Select` +- Prefer `SelectPopup` instead of `SelectContent` +- If you used `asChild` on parts, switch to the `render` prop + +**Size Comparison:** + +coss ui select sizes are more compact compared to shadcn/ui, making them better suited for dense applications: + +| Size | Height (shadcn/ui) | Height (coss ui) | +| --------- | ------------------ | ---------------- | +| `sm` | 32px | 28px | +| `default` | 36px | 32px | +| `lg` | - | 36px | + +So, for example, if you were using the `default` size in shadcn/ui and you want to preserve the original height, you should use the `lg` size in coss ui. + +**Additional Notes:** + +Base UI introduces the `alignItemWithTrigger` prop to control whether the `SelectContent` overlaps the `SelectTrigger` so the selected item's text is aligned with the trigger's value text. + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:placeholder="Select a framework"] + +``` + + + +```tsx title="coss ui" +// [!code word:alignItemWithTrigger={false}] +// [!code word:items:2] + +``` + + +### Sheet + +**Quick Checklist:** +- Replace `asChild` → `render` on `SheetTrigger` and closing buttons +- Prefer `SheetPopup`; `SheetContent` remains for legacy +- Use `SheetPanel` to wrap main content +- If you used `asChild` on any other parts, switch to the `render` prop + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + + + Sheet Title + + Content here + + + + + + + +``` + + + +```tsx title="coss ui" +// [!code word:render] +// [!code word:Content here] + + }> + Open Sheet + + + + Sheet Title + + Content here + + }>Close + + + +``` + + +### Slider + +**Quick Checklist:** +- coss ui `Slider` uses Base UI's multiple value approach +- Always pass values as arrays (e.g., `value={[50]}` instead of `value={50}`) +- `onValueChange` receives an array of numbers +- Multiple thumbs are supported natively via array length +- Replace `asChild` → `render` on parts if used + +### Switch + +**Quick Checklist:** +- Replace `asChild` → `render` on `Switch` if used + +### Tabs + +**Quick Checklist:** +- Replace `asChild` → `render` on parts if used +- Use `TabsTab` going forward; `TabsTrigger` remains for legacy +- Prefer `TabsPanel`; `TabsContent` remains for legacy + +**Additional Notes:** + +Compared to shadcn/ui, our `TabsList` component adds `variant` prop, which allows you to choose between `default` and `underline` styles. + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:TabsContent] + + + Tab 1 + Tab 2 + Tab 3 + + Tab 1 content + Tab 2 content + Tab 3 content + +``` + + + +```tsx title="coss ui" +// [!code word:TabsPanel] + + + Tab 1 + Tab 2 + Tab 3 + + Tab 1 content + Tab 2 content + Tab 3 content + +``` + + +### Textarea + +Compared to shadcn/ui, our `Textarea` component includes size variants (`sm`, `default`, `lg`) for better density control. For visual consistency, if you're using `size="lg"` on other form elements like inputs, you should add the same size to textareas as well. + +### Toast + +The API is significantly different from shadcn/ui (Sonner). Please review both docs before migrating: [Sonner Docs](https://sonner.emilkowal.ski/) and [shadcn/ui Sonner](https://ui.shadcn.com/docs/components/sonner), and our Base UI toast docs. + +**Quick Checklist:** +- Replace `` component in layout → `` wrapper +- Toast API calls differ significantly - see comparison below +- Toast actions use different patterns + +**Comparison Examples:** + +**shadcn/ui (Sonner)** + +```tsx title="app/layout.tsx" +import { Toaster } from "@/components/ui/sonner" + +export default function RootLayout({ children }) { + return ( + + + +
{children}
+ + + + ) +} +``` + +```tsx +toast("Event has been created", { + description: "Sunday, December 03, 2023 at 9:00 AM", + cancel: { + label: "Undo", + }, +}) +``` + +**coss ui (Base UI)** + +```tsx title="app/layout.tsx" +import { ToastProvider } from "@/components/ui/toast" + +export default function RootLayout({ children }) { + return ( + + + + +
{children}
+
+ + + ) +} +``` + +```tsx +onClick={() => { + const id = toastManager.add({ + title: "Event has been created", + description: "Sunday, December 03, 2023 at 9:00 AM", + type: "success", + actionProps: { + children: "Undo", + onClick: () => toastManager.close(id), + }, + }) +}} +``` + +### Toggle + +**Quick Checklist:** +- Replace `asChild` → `render` on `Toggle` if used + +### Toggle Group + +**Prop Mapping:** + +| Component | Radix UI Prop | Base UI Prop | +| ------------- | ----------------------------------------- | -------------------------------------- | +| `ToggleGroup` | `type` (enum, `"single"` or `"multiple"`) | `multiple` (boolean, default: `false`) | + +**Quick Checklist:** +- Replace `type="multiple"` → `multiple` on `ToggleGroup` +- Remove `type="single"` from `ToggleGroup` +- Always use arrays for `defaultValue` +- Use `Toggle` going forward; `ToggleGroupItem` remains for legacy +- Replace `asChild` → `render` on parts if used + +**Size Comparison:** + +coss ui toggle group sizes are more compact compared to shadcn/ui, making them better suited for dense applications: + +| Size | Height (shadcn/ui) | Height (coss ui) | +| --------- | ------------------ | ---------------- | +| `sm` | 32px | 28px | +| `default` | 36px | 32px | +| `lg` | - | 36px | + +So, for example, if you were using the `default` size in shadcn/ui and you want to preserve the original height, you should use the `lg` size in coss ui. + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:type="multiple"] + + B + I + U + +``` + + + +```tsx title="coss ui" +// [!code word:multiple] + + B + I + U + +``` + + +### Tooltip + +**Quick Checklist:** +- Replace `asChild` → `render` on `TooltipTrigger` +- Prefer `TooltipPopup`; `TooltipContent` remains for legacy + +**Comparison Example:** + + +```tsx title="shadcn/ui" +// [!code word:asChild] + + + + + Tooltip content + +``` + + + +```tsx title="coss ui" +// [!code word:render] + + }> + Hover me + + Tooltip content + +``` + + +## Additional Resources + +- [Base UI Documentation](https://base-ui.com/) - Official Base UI docs +- [Component Documentation](/ui/docs/components) - Individual component docs with examples +- [Styling Guide](/ui/docs/styling) - Learn about our color system and theming + +## Need Help? + +If you encounter issues during migration or have questions about specific components, please: + +- Check the individual component documentation pages +- Review the Base UI documentation for deeper understanding +- Open an issue on our GitHub repository diff --git a/packages/ui/components/form/date-range-picker/DateRangePicker.tsx b/packages/ui/components/form/date-range-picker/DateRangePicker.tsx index 5933cdb184..f2d087090c 100644 --- a/packages/ui/components/form/date-range-picker/DateRangePicker.tsx +++ b/packages/ui/components/form/date-range-picker/DateRangePicker.tsx @@ -1,11 +1,9 @@ "use client"; +import classNames from "@calcom/ui/classNames"; import * as Popover from "@radix-ui/react-popover"; import { format, isBefore, isSameDay } from "date-fns"; -import { useState, useMemo, type HTMLAttributes } from "react"; - -import classNames from "@calcom/ui/classNames"; - +import { type HTMLAttributes, useMemo, useState } from "react"; import { Button } from "../../button"; import { Calendar } from "./Calendar"; import { calculateNewDateRange } from "./dateRangeLogic"; @@ -17,6 +15,9 @@ type DatePickerWithRangeProps = { minDate?: Date | null; maxDate?: Date; withoutPopover?: boolean; + popoverModal?: boolean; + popoverOpen?: boolean; + onPopoverOpenChange?: (open: boolean) => void; "data-testid"?: string; strictlyBottom?: boolean; allowPastDates?: boolean; @@ -30,6 +31,9 @@ export function DatePickerWithRange({ onDatesChange, disabled, withoutPopover, + popoverModal = true, + popoverOpen, + onPopoverOpenChange, "data-testid": testId, strictlyBottom, allowPastDates = false, @@ -56,7 +60,7 @@ export function DatePickerWithRange({ setHoveredDate(undefined); } - const fromDate = allowPastDates && minDate === null ? undefined : minDate ?? new Date(); + const fromDate = allowPastDates && minDate === null ? undefined : (minDate ?? new Date()); const hoverRangeModifier = useMemo(() => { if (!dates.startDate || dates.endDate || !hoveredDate) { @@ -97,7 +101,7 @@ export function DatePickerWithRange({ return (
{/* modal prop required for iOS compatibility when nested inside Dialog modals */} - +