Files
cal.diy/packages/app-store/giphy/components/SearchDialog.tsx
T
d6546c3107 feat: upgrade tailwind v4 (#24598)
* chore: refactor config files to prevent migration tool errors

* refactor: upgrade with the tailwind migration tool

* chore: restore pre-commit command + mc

* refactor(wip): update dependencies and migrate to Tailwind CSS v4 (mainly web)

* chore: resolve Tailwind v4 migration conflicts from merging main

* chore: remove unused Tailwind packages from config and update dependencies for v4 migration

* chore: uncomment Tailwind CSS utility classes in globals.css

* fix: resolve token conflicts between calcom and coss ui

* fix: textarea scrollbar

* Fix CUI-16

* fix: added @tailwindcss/forms plugin and cleaned up CSS classes in various components to remove unnecessary dark mode styles

* fix: selects and inputs of different sizes

* fix: remove unnecessary leading-20 class from modal titles in various components

* fix: update Checkbox component styles to remove unnecessary border on checked state

* fix: clean up styles in RequiresConfirmationController, Checkbox, and Radio components to enhance consistency

* fix: update button and filter component styles to remove unnecessary rounded classes for consistency

* fix: calendar

* fix: update KBarSearch

* fix: refine styles in Empty and Checkbox components for improved consistency

* Fix focus state email input

* fix: update button hover and active states to use 'not-disabled' instead of 'enabled'

* fix: line-height issues

* fix: update class name for muted background in BookingListItem component

* fix: sidebar spacing

* chore: update class names to use new Tailwind CSS color utilities

* fix embed

* chore: upgrade Tailwind CSS to version 4.1.16 and update related dependencies

* Map css variables and add a playground test for heavy css customization

* suggestion for coss-ui

* refactor: update CSS variable usage and clean up styles

- Replace instances of `--cal-brand-color` with `--cal-brand` in embed-related HTML files.
- Remove the now-unnecessary `addAppCssVars` function from the embed core.
- Import theme tokens in the embed core styles for better consistency.
- Clean up whitespace and formatting in CSS files for improved readability.
- Add a comment in `tokens.css` regarding its usage in both embed and webapp contexts.

* Handle within tokens.css instead of fixing coss-ui

* Remove initial, not needed. Also, remove tailwind.config.js as tailwidn scans the html automaically

* fix: examples app breaking

* fix: modal not resizing correctly

* feat: upgrade atoms to tailwind v4

* fix: atoms build breaking

* fix: atoms build breaking

* chore: upgrate examples/base to tailwind 4

* chore: update globals.css

* fix: add missing scheduler css variables

* fix: PlatformAdditionalCalendarSelector

* chore: update global styles

* chore: update tailwindcss and postcss dependencies to stable versions

* chore: remove unneeded class

* fix: dialog and toast animation

* fix: replace flex-shrink-0 with shrink-0 for consistent styling in various components

* fix: dialog modal for Apple connect

* add margin in SaveFilterSegmentButton

* Fix radix button nested states

* add cursor pointer to buttons but keep dsabled state

* Fix commandK selectors and adds cursor pointer

* Fix teams filter

* fix - round checkboxes

* fix filter checkbox

* fix select indicator's margin

* command group font size

* style: fix badge and tooltip radius

* chore: remove unneeded files

* Delete PR_REVIEW_MANAGED_EVENT_REASSIGNMENT.md

* remove ui-playground leftover

* fix: add missing react phone input styles in atoms

* Delete managed-event-reassignment-flow-and-architecture.mermaid

* fix: inter font not loading

* Add theme to skeleton container so that it can support dark mode

* fix: create custom stack-y-* utilities post tw4 upgrade

* fix: typo

* fix: atoms stack class + remove unused css file

* fix default radius valiue

* fix space-y in embed

* fix skeleton background

* Hardcode radius values to match production

* fix border in embed

* add missing externalThemeClass

* feat: create a custom stack-y-* utility

* fix: add stack utility to atom global css

* fix: Skeleton loader class modalbox

* Add stack-y utility in embed

* fix: add missing stack utilities in atoms globals.css

* update yarn.lock

* add popover portla

* update

---------

Co-authored-by: Sean Brydon <sean@cal.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Ryukemeister <sahalrajiv6900@gmail.com>
Co-authored-by: cal.com <morgan@cal.com>
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Anik Dhabal Babu <adhabal2002@gmail.com>
2025-11-25 17:32:28 -03:00

223 lines
7.4 KiB
TypeScript

import type { Dispatch, SetStateAction } from "react";
import { useState } from "react";
import { Dialog } from "@calcom/features/components/controlled-dialog";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import { Input } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import type { IconName } from "@calcom/ui/components/icon";
interface ISearchDialog {
isOpenDialog: boolean;
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
onSave: (url: string) => void;
}
const MODE_SEARCH = "search" as const;
const MODE_URL = "url" as const;
type Mode = typeof MODE_SEARCH | typeof MODE_URL;
export const SearchDialog = (props: ISearchDialog) => {
const { t } = useLocale();
const [gifImage, setGifImage] = useState<string>("");
const [nextOffset, setNextOffset] = useState<number>(0);
const [keyword, setKeyword] = useState<string>("");
const { isOpenDialog, setIsOpenDialog } = props;
const [isPending, setIsPending] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [selectedMode, setSelectedMode] = useState<Mode>(MODE_SEARCH);
const searchGiphy = async (keyword: string, offset: number) => {
if (isPending) {
return;
}
setIsPending(true);
setErrorMessage("");
const res = await fetch("/api/integrations/giphy/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
keyword,
offset,
}),
});
const json = await res.json();
if (!res.ok) {
setErrorMessage(json?.message || "Something went wrong");
} else {
setGifImage(json.image || "");
setNextOffset(json.nextOffset);
if (!json.image) {
setErrorMessage("No Result found");
}
}
setIsPending(false);
return null;
};
const getGiphyByUrl = async (url: string) => {
if (isPending) {
return;
}
setIsPending(true);
setErrorMessage("");
const res = await fetch("/api/integrations/giphy/get", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
url,
}),
});
const json = await res.json();
if (!res.ok) {
setErrorMessage(json?.message || json?.[0]?.message || "Something went wrong");
} else {
setGifImage(json.image || "");
if (!json.image) {
setErrorMessage("No Result found");
}
}
setIsPending(false);
return null;
};
const renderTab = (iconName: IconName, text: string, mode: Mode) => (
<div
className={classNames(
"flex cursor-pointer items-center border-b-2 p-2 text-sm ",
selectedMode === mode ? "text-default border-emphasis" : "text-subtle border-transparent"
)}
onClick={() => {
setKeyword("");
setGifImage("");
setSelectedMode(mode);
}}>
<Icon name={iconName} className="h-4 w-4 ltr:mr-2 rtl:ml-2" />
{text}
</div>
);
const handleFormSubmit = async (event: React.SyntheticEvent) => {
event.stopPropagation();
event.preventDefault();
if (selectedMode === MODE_SEARCH) {
searchGiphy(keyword, 0);
} else if (selectedMode === MODE_URL) {
getGiphyByUrl(keyword);
}
};
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
<h3 className="font-cal text-emphasis text-xl" id="modal-title">
{t("add_gif_to_confirmation")}
</h3>
<p className="text-subtle mb-3 text-sm font-light">{t("find_gif_spice_confirmation")}</p>
<div className="border-emphasis flex items-center border-b border-solid">
{renderTab("search", t("search_giphy"), MODE_SEARCH)}
{renderTab("link", t("add_link_from_giphy"), MODE_URL)}
</div>
<form
className="flex w-full justify-center space-x-2 stack-y-2 rtl:space-x-reverse"
onSubmit={handleFormSubmit}>
<div className="relative block w-full pt-2">
<Input
type="text"
placeholder={
selectedMode === MODE_SEARCH
? t("search_giphy")
: "https://media.giphy.com/media/some-id/giphy.gif"
}
value={keyword}
onChange={(event) => {
setKeyword(event.target.value);
}}
/>
</div>
<Button type="submit" tabIndex={-1} color="secondary" loading={isPending}>
{t("search")}
</Button>
</form>
{gifImage && (
<div className="flex flex-col items-center space-x-2 stack-y-2 pt-3 rtl:space-x-reverse">
<div className="bg-subtle flex w-full items-center justify-center">
{isPending ? (
<div className="flex h-[200px] w-full items-center justify-center bg-gray-400 pb-3 pt-3">
<svg
className={classNames("mx-4 h-5 w-5 animate-spin", "text-inverted dark:text-emphasis")}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
) : (
<img className="h-[200px] pb-3 pt-3" src={gifImage} alt={`Gif from Giphy for ${keyword}`} />
)}
</div>
</div>
)}
{errorMessage && <Alert severity="error" title={errorMessage} className="my-4" />}
{gifImage && selectedMode === MODE_SEARCH && (
<div className="mt-4 flex items-center justify-between space-x-2 rtl:space-x-reverse">
<div className="text-subtle text-sm font-light">Not the perfect GIF?</div>
<Button
size="sm"
color="secondary"
type="button"
loading={isPending}
onClick={() => searchGiphy(keyword, nextOffset)}>
Shuffle
</Button>
</div>
)}
<DialogFooter noSticky>
<DialogClose
color="minimal"
tabIndex={-1}
onClick={() => {
props.setIsOpenDialog(false);
}}>
{t("cancel")}
</DialogClose>
<Button
type="button"
disabled={!gifImage}
onClick={() => {
props.setIsOpenDialog(false);
props.onSave(gifImage);
setNextOffset(0);
setGifImage("");
setKeyword("");
return false;
}}>
{t("add_gif")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};