mirror of
https://github.com/baptisteArno/typebot.io.git
synced 2026-06-13 21:02:56 +08:00
🔧 Migrate biome rules: interactive semantics checks
This commit is contained in:
parent
90ec449168
commit
d1e2781caf
@ -179,16 +179,10 @@ type UnsplashImageProps = {
|
||||
};
|
||||
|
||||
const UnsplashImage = ({ image, onClick }: UnsplashImageProps) => {
|
||||
const [isImageHovered, setIsImageHovered] = useState(false);
|
||||
|
||||
const { user, urls, alt_description } = image;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative h-full"
|
||||
onMouseEnter={() => setIsImageHovered(true)}
|
||||
onMouseLeave={() => setIsImageHovered(false)}
|
||||
>
|
||||
<div className="group relative h-full">
|
||||
<button
|
||||
type="button"
|
||||
className="size-full rounded-md cursor-pointer p-0 border-none bg-transparent"
|
||||
@ -202,8 +196,7 @@ const UnsplashImage = ({ image, onClick }: UnsplashImageProps) => {
|
||||
</button>
|
||||
<div
|
||||
className={cx(
|
||||
"absolute px-2 rounded-md bottom-0 left-0 bg-black/50 opacity-0 transition-opacity duration-200",
|
||||
isImageHovered ? "opacity-100" : "opacity-0",
|
||||
"absolute px-2 rounded-md bottom-0 left-0 bg-black/50 opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-within:opacity-100",
|
||||
)}
|
||||
>
|
||||
<TextLink
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Textarea } from "@typebot.io/ui/components/Textarea";
|
||||
import { cn } from "@typebot.io/ui/lib/cn";
|
||||
import { type HTMLAttributes, useEffect, useRef, useState } from "react";
|
||||
import { type ButtonHTMLAttributes, useEffect, useRef, useState } from "react";
|
||||
import { useOutsideClick } from "@/hooks/useOutsideClick";
|
||||
|
||||
export type MultiLineEditableProps = {
|
||||
@ -11,7 +11,7 @@ export type MultiLineEditableProps = {
|
||||
> & {
|
||||
onValueChange?: (value: string) => void;
|
||||
};
|
||||
preview?: HTMLAttributes<HTMLSpanElement>;
|
||||
preview?: ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
defaultEdit: boolean;
|
||||
value: string;
|
||||
onValueCommit: (value: string) => void;
|
||||
@ -80,14 +80,12 @@ export const MultiLineEditable = ({
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
<button
|
||||
{...preview}
|
||||
onClick={() => setIsEditing(true)}
|
||||
onKeyDown={(event) => {
|
||||
preview?.onKeyDown?.(event);
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
preview?.onClick?.(event);
|
||||
if (event.defaultPrevented) return;
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
event.preventDefault();
|
||||
setIsEditing(true);
|
||||
}}
|
||||
className={cn(
|
||||
@ -96,7 +94,7 @@ export const MultiLineEditable = ({
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -42,7 +42,6 @@ export const PrimitiveList = <T extends number | string | boolean>({
|
||||
onItemsChange,
|
||||
}: Props<T>) => {
|
||||
const [items, setItems] = useState<ItemWithId<T>[]>();
|
||||
const [showDeleteIndex, setShowDeleteIndex] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (items) return;
|
||||
@ -79,14 +78,9 @@ export const PrimitiveList = <T extends number | string | boolean>({
|
||||
onItemsChange(removeIdFromItems([...newItems]));
|
||||
};
|
||||
|
||||
const handleMouseEnter = (itemIndex: number) => () =>
|
||||
setShowDeleteIndex(itemIndex);
|
||||
|
||||
const handleCellChange = (itemIndex: number) => (item: T) =>
|
||||
updateItem(itemIndex, item);
|
||||
|
||||
const handleMouseLeave = () => setShowDeleteIndex(null);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-0">
|
||||
{items?.map((item, itemIndex) => (
|
||||
@ -96,27 +90,23 @@ export const PrimitiveList = <T extends number | string | boolean>({
|
||||
)}
|
||||
<div
|
||||
className={cx(
|
||||
"flex relative justify-center pb-4",
|
||||
"group/item flex relative justify-center pb-4",
|
||||
itemIndex !== 0 && ComponentBetweenItems ? "mt-4" : "mt-0",
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter(itemIndex)}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{children({
|
||||
item: item.value as T,
|
||||
onItemChange: handleCellChange(itemIndex),
|
||||
})}
|
||||
{showDeleteIndex === itemIndex && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="size-6 animate-in fade-in-0 absolute left-[-15px] top-[-15px] z-10"
|
||||
size="icon"
|
||||
aria-label="Remove item"
|
||||
onClick={deleteItem(itemIndex)}
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="size-6 absolute left-[-15px] top-[-15px] z-10 invisible opacity-0 transition-opacity group-hover/item:visible group-hover/item:opacity-100 group-focus-within/item:visible group-focus-within/item:opacity-100"
|
||||
size="icon"
|
||||
aria-label="Remove item"
|
||||
onClick={deleteItem(itemIndex)}
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
import { Input, type InputProps } from "@typebot.io/ui/components/Input";
|
||||
import { cn } from "@typebot.io/ui/lib/cn";
|
||||
import { forwardRef, type HTMLAttributes, useRef, useState } from "react";
|
||||
import {
|
||||
type ButtonHTMLAttributes,
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useOutsideClick } from "@/hooks/useOutsideClick";
|
||||
|
||||
export type SingleLineEditableProps = {
|
||||
className?: string;
|
||||
input?: Omit<InputProps, "value">;
|
||||
preview?: HTMLAttributes<HTMLSpanElement>;
|
||||
common?: HTMLAttributes<HTMLSpanElement | HTMLInputElement>;
|
||||
preview?: ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
common?: HTMLAttributes<HTMLButtonElement | HTMLInputElement>;
|
||||
defaultEdit?: boolean;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
@ -75,17 +81,12 @@ export const SingleLineEditable = forwardRef<
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
<button
|
||||
{...preview}
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
preview?.onClick?.(e);
|
||||
setIsEditing(true);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
preview?.onKeyDown?.(event);
|
||||
if (event.defaultPrevented) return;
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
event.preventDefault();
|
||||
if (e.defaultPrevented) return;
|
||||
setIsEditing(true);
|
||||
}}
|
||||
className={cn(
|
||||
@ -95,7 +96,7 @@ export const SingleLineEditable = forwardRef<
|
||||
)}
|
||||
>
|
||||
{value ?? currentValue}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -132,6 +132,7 @@ const StackWithGhostableItems = ({
|
||||
ghostItemHeight={gapPixel / childrenLength}
|
||||
closeExpanded={onAbort}
|
||||
>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: This hover surface only expands placeholder spacing; the interactive controls are the ghost buttons inside. */}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
|
||||
@ -39,7 +39,6 @@ export const TableList = <T extends object>({
|
||||
addIdsIfMissing(initialItems) ??
|
||||
(hasDefaultItem ? ([defaultItem] as T[]) : []),
|
||||
);
|
||||
const [showDeleteIndex, setShowDeleteIndex] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (items.length && initialItems && initialItems?.length === 0)
|
||||
@ -78,14 +77,9 @@ export const TableList = <T extends object>({
|
||||
onItemsChange([...newItems]);
|
||||
};
|
||||
|
||||
const handleMouseEnter = (itemIndex: number) => () =>
|
||||
setShowDeleteIndex(itemIndex);
|
||||
|
||||
const handleCellChange = (itemIndex: number) => (item: T) =>
|
||||
updateItem(itemIndex, item);
|
||||
|
||||
const handleMouseLeave = () => setShowDeleteIndex(null);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-0">
|
||||
{items.map((item, itemIndex) => (
|
||||
@ -95,32 +89,28 @@ export const TableList = <T extends object>({
|
||||
)}
|
||||
<div
|
||||
className={cx(
|
||||
"flex relative justify-center pb-4",
|
||||
"group/item flex relative justify-center pb-4",
|
||||
itemIndex !== 0 && ComponentBetweenItems ? "mt-4" : "mt-0",
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter(itemIndex)}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{children({ item, onItemChange: handleCellChange(itemIndex) })}
|
||||
{showDeleteIndex === itemIndex && (
|
||||
<Button
|
||||
size="icon"
|
||||
aria-label="Remove cell"
|
||||
onClick={deleteItem(itemIndex)}
|
||||
variant="secondary"
|
||||
className="shadow-md size-6 animate-in fade-in-0 absolute left-[-8px] top-[-8px]"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
)}
|
||||
{isOrdered && showDeleteIndex === itemIndex && (
|
||||
<Button
|
||||
size="icon"
|
||||
aria-label="Remove cell"
|
||||
onClick={deleteItem(itemIndex)}
|
||||
variant="secondary"
|
||||
className="shadow-md size-6 absolute left-[-8px] top-[-8px] invisible opacity-0 transition-opacity group-hover/item:visible group-hover/item:opacity-100 group-focus-within/item:visible group-focus-within/item:opacity-100"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
{isOrdered && (
|
||||
<>
|
||||
<Button
|
||||
size="icon"
|
||||
aria-label={addLabel}
|
||||
onClick={insertItemAt(itemIndex)}
|
||||
variant="secondary"
|
||||
className="shadow-md size-6 animate-in fade-in-0 slide-in-from-bottom-1 absolute top-[-10px]"
|
||||
className="shadow-md size-6 absolute top-[-10px] invisible opacity-0 transition-opacity group-hover/item:visible group-hover/item:opacity-100 group-focus-within/item:visible group-focus-within/item:opacity-100"
|
||||
>
|
||||
<PlusSignIcon />
|
||||
</Button>
|
||||
@ -129,7 +119,7 @@ export const TableList = <T extends object>({
|
||||
aria-label={addLabel}
|
||||
onClick={insertItemAt(itemIndex + 1)}
|
||||
variant="secondary"
|
||||
className="shadow-md size-6 animate-in fade-in-0 slide-in-from-top-1 absolute bottom-2"
|
||||
className="shadow-md size-6 absolute bottom-2 invisible opacity-0 transition-opacity group-hover/item:visible group-hover/item:opacity-100 group-focus-within/item:visible group-focus-within/item:opacity-100"
|
||||
>
|
||||
<PlusSignIcon />
|
||||
</Button>
|
||||
|
||||
@ -81,9 +81,6 @@ export const TagsInput = ({ items, placeholder, onValueChange }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-wrap gap-1 border py-1 px-2 rounded-md data-[focus=true]:outline-none data-[focus=true]:ring-orange-8 data-[focus=true]:ring-2 data-[focus=true]:border-transparent transition-[box-shadow,border-color]"
|
||||
onClick={() => inputRef.current?.focus()}
|
||||
onBlur={addItem}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-focus={isFocused}
|
||||
>
|
||||
{items?.map((item, index) => (
|
||||
@ -101,8 +98,12 @@ export const TagsInput = ({ items, placeholder, onValueChange }: Props) => {
|
||||
value={inputValue}
|
||||
onValueChange={handleInputChange}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
onBlur={() => {
|
||||
setIsFocused(false);
|
||||
addItem();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e);
|
||||
if (e.key === "Enter") addItem();
|
||||
}}
|
||||
placeholder={items && items.length === 0 ? placeholder : undefined}
|
||||
|
||||
@ -224,14 +224,12 @@ const PexelsVideo = ({ video, onClick }: PexelsVideoProps) => {
|
||||
}, [isImageHovered, imageIndex, video_pictures]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setIsImageHovered(true)}
|
||||
onMouseLeave={() => setIsImageHovered(false)}
|
||||
>
|
||||
<div className="group relative">
|
||||
<button
|
||||
type="button"
|
||||
className="size-full rounded-md cursor-pointer p-0 border-none bg-transparent"
|
||||
onMouseEnter={() => setIsImageHovered(true)}
|
||||
onMouseLeave={() => setIsImageHovered(false)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<img
|
||||
@ -243,12 +241,7 @@ const PexelsVideo = ({ video, onClick }: PexelsVideoProps) => {
|
||||
alt={`Pexels Video ${video.id}`}
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
className={cx(
|
||||
"absolute px-2 rounded-md bottom-0 left-0 bg-black/50 opacity-0 transition-opacity",
|
||||
isImageHovered ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<div className="absolute px-2 rounded-md bottom-0 left-0 bg-black/50 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
||||
<TextLink className="text-xs text-white" isExternal href={url}>
|
||||
{user.name}
|
||||
</TextLink>
|
||||
|
||||
@ -53,14 +53,14 @@ export const BlockCard = (
|
||||
tooltip={t("blocks.inputs.fileUpload.blockCard.tooltip")}
|
||||
>
|
||||
<BlockIcon type={props.type} />
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex items-center gap-2">
|
||||
<BlockLabel type={props.type} />
|
||||
{isFreePlan(workspace) && (
|
||||
<Badge colorScheme="orange">
|
||||
<SquareLock01Icon />
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</span>
|
||||
</BlockCardLayout>
|
||||
);
|
||||
case LogicBlockType.SCRIPT:
|
||||
|
||||
@ -32,7 +32,8 @@ export const BlockCardLayout = ({
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<div className="flex relative">
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
className={cx(
|
||||
"flex items-center gap-2 border dark:border-gray-3 rounded-lg flex-1 px-4 py-2 cursor-grab bg-gray-2 hover:shadow-md dark:hover:bg-gray-3 dark:hover:shadow-none transition-[box-shadow,background-color]",
|
||||
isMouseDown ? "opacity-40 min-h-[42px]" : "opacity-100",
|
||||
@ -40,7 +41,7 @@ export const BlockCardLayout = ({
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{!isMouseDown ? children : null}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -7,12 +7,10 @@ export const BlockCardOverlay = ({
|
||||
type,
|
||||
className,
|
||||
style,
|
||||
onMouseUp,
|
||||
}: {
|
||||
type: BlockV6["type"];
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
onMouseUp: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@ -20,7 +18,6 @@ export const BlockCardOverlay = ({
|
||||
"flex items-center gap-2 border rounded-lg w-[147px] px-4 py-2 shadow-xl cursor-grabbing transition-none pointer-events-none z-10 bg-gray-2",
|
||||
className,
|
||||
)}
|
||||
onMouseUp={onMouseUp}
|
||||
style={style}
|
||||
>
|
||||
<BlockIcon type={type} />
|
||||
|
||||
@ -15,7 +15,7 @@ import { SquareLock01Icon } from "@typebot.io/ui/icons/SquareLock01Icon";
|
||||
import { SquareUnlock01Icon } from "@typebot.io/ui/icons/SquareUnlock01Icon";
|
||||
import { cx } from "@typebot.io/ui/lib/cva";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { Portal } from "@/components/Portal";
|
||||
import { useBlockDnd } from "@/features/graph/providers/GraphDndProvider";
|
||||
@ -59,22 +59,36 @@ export const BlocksSideBar = () => {
|
||||
localStorage.getItem(leftSidebarLockedStorageKey) !== "false",
|
||||
);
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
const dockBarRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const closeSideBar = useDebouncedCallback(() => setIsExtended(false), 200);
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!draggedBlockType && !draggedEventType) return;
|
||||
const { clientX, clientY } = event;
|
||||
setPosition({
|
||||
...position,
|
||||
x: clientX - relativeCoordinates.x,
|
||||
y: clientY - relativeCoordinates.y,
|
||||
});
|
||||
if (draggedBlockType || draggedEventType) {
|
||||
setPosition({
|
||||
x: clientX - relativeCoordinates.x,
|
||||
y: clientY - relativeCoordinates.y,
|
||||
});
|
||||
}
|
||||
if (isLocked) return;
|
||||
if (isMouseInElement(sidebarRef.current, clientX, clientY)) {
|
||||
closeSideBar.flush();
|
||||
return;
|
||||
}
|
||||
if (isMouseInElement(dockBarRef.current, clientX, clientY)) {
|
||||
closeSideBar.flush();
|
||||
setIsExtended(true);
|
||||
return;
|
||||
}
|
||||
if (clientX < 100) return;
|
||||
closeSideBar();
|
||||
};
|
||||
useEventListener("mousemove", handleMouseMove);
|
||||
|
||||
const initBlockDragging = (e: React.MouseEvent, type: BlockV6["type"]) => {
|
||||
const element = e.currentTarget as HTMLDivElement;
|
||||
const element = e.currentTarget as HTMLElement;
|
||||
const rect = element.getBoundingClientRect();
|
||||
setPosition({ x: rect.left, y: rect.top });
|
||||
const x = e.clientX - rect.left;
|
||||
@ -87,7 +101,7 @@ export const BlocksSideBar = () => {
|
||||
e: React.MouseEvent,
|
||||
type: TDraggableEvent["type"],
|
||||
) => {
|
||||
const element = e.currentTarget as HTMLDivElement;
|
||||
const element = e.currentTarget as HTMLElement;
|
||||
const rect = element.getBoundingClientRect();
|
||||
setPosition({ x: rect.left, y: rect.top });
|
||||
const x = e.clientX - rect.left;
|
||||
@ -116,16 +130,6 @@ export const BlocksSideBar = () => {
|
||||
setIsLocked(!isLocked);
|
||||
};
|
||||
|
||||
const handleDockBarEnter = () => {
|
||||
closeSideBar.flush();
|
||||
setIsExtended(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (e: React.MouseEvent) => {
|
||||
if (isLocked || e.clientX < 100) return;
|
||||
closeSideBar();
|
||||
};
|
||||
|
||||
const handleSearchInputChange = (event: {
|
||||
target: { value: React.SetStateAction<string> };
|
||||
}) => {
|
||||
@ -187,11 +191,11 @@ export const BlocksSideBar = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className={cx(
|
||||
"flex w-[360px] absolute pl-4 py-4 left-0 transition-transform duration-150 ease-out h-[calc(100vh-var(--header-height))]",
|
||||
isExtended ? "translate-x-0" : "translate-x-[-350px]",
|
||||
)}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className="flex flex-col w-full rounded-lg border pt-4 pb-10 px-4 gap-6 overflow-y-auto bg-gray-1 select-none">
|
||||
<div className="flex justify-between w-full items-center gap-3">
|
||||
@ -303,7 +307,6 @@ export const BlocksSideBar = () => {
|
||||
<Portal>
|
||||
<BlockCardOverlay
|
||||
type={draggedBlockType}
|
||||
onMouseUp={handleMouseUp}
|
||||
className="fixed top-0 left-0"
|
||||
style={{
|
||||
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`,
|
||||
@ -315,7 +318,6 @@ export const BlocksSideBar = () => {
|
||||
<Portal>
|
||||
<EventCardOverlay
|
||||
type={draggedEventType}
|
||||
onMouseUp={handleMouseUp}
|
||||
className="fixed top-0 left-0"
|
||||
style={{
|
||||
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`,
|
||||
@ -325,13 +327,34 @@ export const BlocksSideBar = () => {
|
||||
)}
|
||||
</div>
|
||||
{!isLocked && (
|
||||
<div
|
||||
<button
|
||||
ref={dockBarRef}
|
||||
type="button"
|
||||
aria-label="Open blocks sidebar"
|
||||
className="flex animate-in fade-in-0 absolute h-full w-[450px] justify-end pr-10 items-center -right-[70px] top-0 -z-10"
|
||||
onMouseEnter={handleDockBarEnter}
|
||||
onFocus={() => {
|
||||
closeSideBar.flush();
|
||||
setIsExtended(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex w-[5px] h-[20px] rounded-md bg-gray-7" />
|
||||
</div>
|
||||
<span className="flex w-[5px] h-[20px] rounded-md bg-gray-7" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isMouseInElement = (
|
||||
element: HTMLElement | null,
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
) => {
|
||||
if (!element) return false;
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
clientX >= rect.left &&
|
||||
clientX <= rect.right &&
|
||||
clientY >= rect.top &&
|
||||
clientY <= rect.bottom
|
||||
);
|
||||
};
|
||||
|
||||
@ -36,7 +36,9 @@ export const EventCardLayout = ({
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<div className="flex relative">
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
disabled={isDisabled}
|
||||
className={cx(
|
||||
"flex items-center gap-2 border rounded-lg flex-1 px-4 py-2 bg-gray-1 transition-[box-shadow,background-color]",
|
||||
isMouseDown ? "min-h-[42px]" : undefined,
|
||||
@ -48,7 +50,7 @@ export const EventCardLayout = ({
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{!isMouseDown ? children : null}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -7,12 +7,10 @@ export const EventCardOverlay = ({
|
||||
type,
|
||||
className,
|
||||
style,
|
||||
onMouseUp,
|
||||
}: {
|
||||
type: TDraggableEvent["type"];
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
onMouseUp: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@ -20,7 +18,6 @@ export const EventCardOverlay = ({
|
||||
"flex items-center gap-2 border rounded-lg w-[147px] px-4 py-2 shadow-xl cursor-grabbing transition-none pointer-events-none z-10 border-gray-9 bg-gray-1",
|
||||
className,
|
||||
)}
|
||||
onMouseUp={onMouseUp}
|
||||
style={style}
|
||||
>
|
||||
<EventIcon type={type} />
|
||||
|
||||
@ -201,7 +201,6 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
<Portal>
|
||||
<TypebotButtonOverlay
|
||||
typebot={draggedTypebot}
|
||||
onMouseUp={handleMouseUp}
|
||||
className="fixed top-0 left-0 origin-[0_0_0]"
|
||||
style={{
|
||||
transform: `translate(${draggablePosition.x}px, ${draggablePosition.y}px) rotate(-2deg)`,
|
||||
|
||||
@ -6,23 +6,16 @@ import type { TypebotInDashboard } from "@/features/dashboard/types";
|
||||
type Props = {
|
||||
typebot: TypebotInDashboard;
|
||||
className?: string;
|
||||
onMouseUp?: () => Promise<void>;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const TypebotButtonOverlay = ({
|
||||
typebot,
|
||||
className,
|
||||
onMouseUp,
|
||||
style,
|
||||
}: Props) => {
|
||||
export const TypebotButtonOverlay = ({ typebot, className, style }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col justify-center w-[225px] h-[270px] border rounded-md shadow-md whitespace-normal transition-none pointer-events-none opacity-70 bg-gray-1",
|
||||
className,
|
||||
)}
|
||||
onMouseUp={onMouseUp}
|
||||
style={style}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
|
||||
@ -155,48 +155,33 @@ export const DropOffEdge = ({
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger
|
||||
render={
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-col items-center rounded-md p-2 justify-center w-full h-full gap-0.5 bg-red-9 text-white",
|
||||
isWorkspaceProPlan ? "cursor-auto" : "cursor-pointer",
|
||||
)}
|
||||
data-testid={`dropoff-edge-${blockId}`}
|
||||
role={isWorkspaceProPlan ? undefined : "button"}
|
||||
tabIndex={isWorkspaceProPlan ? undefined : 0}
|
||||
onClick={isWorkspaceProPlan ? undefined : onUnlockProPlanClick}
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
isWorkspaceProPlan ||
|
||||
!onUnlockProPlanClick ||
|
||||
(event.key !== "Enter" && event.key !== " ")
|
||||
)
|
||||
return;
|
||||
event.preventDefault();
|
||||
onUnlockProPlanClick();
|
||||
}}
|
||||
>
|
||||
<p
|
||||
className={cx(
|
||||
"text-sm",
|
||||
isWorkspaceProPlan ? undefined : "blur-[2px]",
|
||||
)}
|
||||
isWorkspaceProPlan ? (
|
||||
<div
|
||||
className="flex flex-col items-center rounded-md p-2 justify-center w-full h-full gap-0.5 bg-red-9 text-white"
|
||||
data-testid={`dropoff-edge-${blockId}`}
|
||||
>
|
||||
{isWorkspaceProPlan ? (
|
||||
dropOffRate
|
||||
) : (
|
||||
<span className="blur-[2px]">X</span>
|
||||
)}
|
||||
%
|
||||
</p>
|
||||
<Badge colorScheme="red">
|
||||
{isWorkspaceProPlan ? (
|
||||
totalDroppedUser
|
||||
) : (
|
||||
<span className="mr-1 blur-[3px]">NN</span>
|
||||
)}{" "}
|
||||
user{(totalDroppedUser ?? 2) > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
</div>
|
||||
<span className="text-sm">{dropOffRate}%</span>
|
||||
<Badge colorScheme="red">
|
||||
{totalDroppedUser} user
|
||||
{(totalDroppedUser ?? 2) > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex flex-col items-center rounded-md p-2 justify-center w-full h-full gap-0.5 bg-red-9 text-white cursor-pointer"
|
||||
data-testid={`dropoff-edge-${blockId}`}
|
||||
onClick={onUnlockProPlanClick}
|
||||
>
|
||||
<span className={cx("text-sm", "blur-[2px]")}>
|
||||
<span className="blur-[2px]">X</span>%
|
||||
</span>
|
||||
<Badge colorScheme="red">
|
||||
<span className="mr-1 blur-[3px]">NN</span> user
|
||||
{(totalDroppedUser ?? 2) > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Tooltip.Popup>
|
||||
|
||||
@ -108,6 +108,7 @@ export const Edge = ({ edge, fromElementId }: Props) => {
|
||||
<ContextMenu.Trigger
|
||||
render={(props) => (
|
||||
<>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: SVG paths are graph hit areas, not HTML controls, and React Flow relies on this shape for edge selection. */}
|
||||
<path
|
||||
{...props}
|
||||
data-testid="clickable-edge"
|
||||
|
||||
@ -43,54 +43,61 @@ export const PlaceholderNode = forwardRef<HTMLDivElement, Props>(
|
||||
enabled: isDefined(onClick),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
const placeholderContent = (
|
||||
<span
|
||||
style={
|
||||
{
|
||||
"--py":
|
||||
"--h":
|
||||
isExpanded || isHoverExpanded
|
||||
? `${expandedPaddingPixel}px`
|
||||
: `${initialPaddingPixel}px`,
|
||||
? `${expandedHeightPixels}px`
|
||||
: `${initialHeightPixels}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"flex font-semibold justify-center items-center relative py-(--py) text-sm",
|
||||
className,
|
||||
className={cx(
|
||||
"flex w-full rounded-lg justify-center items-center bg-gray-3 h-(--h) transition-[opacity,height]",
|
||||
isVisible || isHovered ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
ref={ref}
|
||||
onMouseEnter={onHover}
|
||||
onMouseLeave={onLeave}
|
||||
onMouseUpCapture={onAbort}
|
||||
{...(onClick
|
||||
? {
|
||||
role: "button",
|
||||
tabIndex: 0,
|
||||
onClick,
|
||||
onKeyDown: (event: React.KeyboardEvent) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
event.preventDefault();
|
||||
onClick();
|
||||
},
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{onClick && <span className="absolute left-0 z-1 w-full" />}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--h":
|
||||
isExpanded || isHoverExpanded
|
||||
? `${expandedHeightPixels}px`
|
||||
: `${initialHeightPixels}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cx(
|
||||
"flex w-full rounded-lg justify-center items-center bg-gray-3 h-(--h) transition-[opacity,height]",
|
||||
isVisible || isHovered ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
{isHovered && isHoverExpanded ? children : null}
|
||||
</div>
|
||||
{isHovered && isHoverExpanded ? children : null}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("relative", className)} ref={ref}>
|
||||
{onClick ? (
|
||||
<button
|
||||
type="button"
|
||||
style={
|
||||
{
|
||||
"--py":
|
||||
isExpanded || isHoverExpanded
|
||||
? `${expandedPaddingPixel}px`
|
||||
: `${initialPaddingPixel}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="flex w-full font-semibold justify-center items-center py-(--py) text-sm"
|
||||
onMouseEnter={onHover}
|
||||
onMouseLeave={onLeave}
|
||||
onMouseUpCapture={onAbort}
|
||||
onClick={onClick}
|
||||
>
|
||||
{placeholderContent}
|
||||
</button>
|
||||
) : (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--py":
|
||||
isExpanded || isHoverExpanded
|
||||
? `${expandedPaddingPixel}px`
|
||||
: `${initialPaddingPixel}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="flex w-full font-semibold justify-center items-center py-(--py) text-sm"
|
||||
>
|
||||
{placeholderContent}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@ -227,6 +227,7 @@ export const BlockNode = ({
|
||||
render={(props) => (
|
||||
<ContextMenu.Root onOpenChange={setIsContextMenuOpened}>
|
||||
<ContextMenu.Trigger>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: This node container is a React Flow drag surface with nested controls, not a standalone HTML action. */}
|
||||
<div
|
||||
className="flex relative w-full prevent-group-drag"
|
||||
{...props}
|
||||
@ -286,6 +287,8 @@ export const BlockNode = ({
|
||||
)}
|
||||
/>
|
||||
{/* Prevent triggering parent group context menu */}
|
||||
{/* biome-ignore lint/a11y/noNoninteractiveElementInteractions: This wrapper only stops context-menu propagation for nested popovers. */}
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: This wrapper only stops context-menu propagation for nested popovers. */}
|
||||
<div onContextMenu={(e) => e.stopPropagation()}>
|
||||
{hasSettingsPopover(block) && (
|
||||
<SettingsPopoverContent
|
||||
|
||||
@ -154,6 +154,7 @@ export const GroupNode = ({ group, groupIndex }: Props) => {
|
||||
disabled={isReadOnly}
|
||||
>
|
||||
<ContextMenu.Trigger>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: This group container is a draggable graph surface with nested controls, not a standalone HTML action. */}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
|
||||
@ -80,6 +80,7 @@ export const ItemNode = ({
|
||||
return (
|
||||
<ContextMenu.Root onOpenChange={setIsContextMenuOpened}>
|
||||
<ContextMenu.Trigger>
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions: This item container is a draggable graph surface with nested controls, not a standalone HTML action. */}
|
||||
<div
|
||||
className="flex flex-col gap-2 relative w-full"
|
||||
data-testid="item"
|
||||
|
||||
@ -140,6 +140,7 @@ export const ItemNodesList = ({
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: This wrapper only prevents click propagation.
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: This wrapper only prevents click propagation inside the graph surface.
|
||||
<div
|
||||
className="flex flex-col gap-0 max-w-full flex-1"
|
||||
onClick={stopPropagating}
|
||||
|
||||
@ -27,7 +27,6 @@ export const PreviewDrawer = () => {
|
||||
const { t } = useTranslate();
|
||||
const { setPreviewingBlock } = useGraph();
|
||||
const [width, setWidth] = useState(500);
|
||||
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false);
|
||||
const [selectedRuntime, setSelectedRuntime] = useState<
|
||||
(typeof runtimes)[number]
|
||||
>(getDefaultRuntime(typebot?.id));
|
||||
@ -61,19 +60,13 @@ export const PreviewDrawer = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex absolute border-l shadow-md p-6 right-0 top-0 h-full bg-gray-1 rounded-l-lg z-10"
|
||||
onMouseOver={() => setIsResizeHandleVisible(true)}
|
||||
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
||||
onFocus={() => setIsResizeHandleVisible(true)}
|
||||
onBlur={() => setIsResizeHandleVisible(false)}
|
||||
className="group/drawer flex absolute border-l shadow-md p-6 right-0 top-0 h-full bg-gray-1 rounded-l-lg z-10"
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
{isResizeHandleVisible && (
|
||||
<ResizeHandle
|
||||
{...useResizeHandleDrag()}
|
||||
className="animate-in fade-in-0 absolute left-[-7.5px] top-1/2 -translate-y-1/2"
|
||||
/>
|
||||
)}
|
||||
<ResizeHandle
|
||||
{...useResizeHandleDrag()}
|
||||
className="absolute left-[-7.5px] top-1/2 -translate-y-1/2 opacity-0 pointer-events-none transition-opacity group-hover/drawer:opacity-100 group-hover/drawer:pointer-events-auto group-focus-within/drawer:opacity-100 group-focus-within/drawer:pointer-events-auto"
|
||||
/>
|
||||
<div className="flex flex-col items-center w-full gap-4">
|
||||
<div className="flex items-center gap-2 justify-between w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@ -33,7 +33,6 @@ export const VariablesDrawer = ({ onClose }: Props) => {
|
||||
const { typebot, createVariable, updateVariable, deleteVariable } =
|
||||
useTypebot();
|
||||
const [width, setWidth] = useState(500);
|
||||
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const filteredVariables = typebot?.variables.filter((v) =>
|
||||
isNotEmpty(searchValue)
|
||||
@ -71,20 +70,14 @@ export const VariablesDrawer = ({ onClose }: Props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex absolute border-l shadow-md p-6 right-0 top-0 h-full bg-gray-1 rounded-l-lg"
|
||||
onMouseOver={() => setIsResizeHandleVisible(true)}
|
||||
onFocus={() => setIsResizeHandleVisible(true)}
|
||||
onBlur={() => setIsResizeHandleVisible(false)}
|
||||
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
||||
className="group/drawer flex absolute border-l shadow-md p-6 right-0 top-0 h-full bg-gray-1 rounded-l-lg"
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
{isResizeHandleVisible && (
|
||||
<ResizeHandle
|
||||
{...useResizeHandleDrag()}
|
||||
className="animate-in fade-in-0 absolute left-[-7.5px]"
|
||||
style={{ top: `calc(50% - ${headerHeight}px)` }}
|
||||
/>
|
||||
)}
|
||||
<ResizeHandle
|
||||
{...useResizeHandleDrag()}
|
||||
className="absolute left-[-7.5px] opacity-0 pointer-events-none transition-opacity group-hover/drawer:opacity-100 group-hover/drawer:pointer-events-auto group-focus-within/drawer:opacity-100 group-focus-within/drawer:pointer-events-auto"
|
||||
style={{ top: `calc(50% - ${headerHeight}px)` }}
|
||||
/>
|
||||
<div className="flex flex-col w-full gap-4">
|
||||
<Button
|
||||
className="absolute right-2 top-2"
|
||||
|
||||
@ -5,6 +5,6 @@ export const setLocaleInCookies = async (locale: string) => {
|
||||
name: "NEXT_LOCALE",
|
||||
value: encodeURIComponent(locale),
|
||||
path: "/",
|
||||
expires: new Date(Date.now() + 31_536_000_000),
|
||||
expires: Date.now() + 31_536_000_000,
|
||||
});
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { ArrowDown01Icon } from "@typebot.io/ui/icons/ArrowDown01Icon";
|
||||
import { ArrowUp01Icon } from "@typebot.io/ui/icons/ArrowUp01Icon";
|
||||
import { cn } from "@typebot.io/ui/lib/cn";
|
||||
import { motion } from "motion/react";
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import threeDButton from "./assets/3d-button.png";
|
||||
|
||||
const data = [
|
||||
@ -81,19 +81,18 @@ const Principle = ({
|
||||
isLastItem: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const contentId = useId();
|
||||
|
||||
return (
|
||||
<details
|
||||
className="rounded-xl md:rounded-none md:px-0 bg-white border md:border-0 border-border cursor-pointer"
|
||||
open={isOpened}
|
||||
>
|
||||
<summary
|
||||
className="px-4 py-4 md:py-2 font-display font-medium text-2xl flex flex-col gap-3 list-none"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}}
|
||||
<div className="rounded-xl md:rounded-none md:px-0 bg-white border md:border-0 border-border">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full px-4 py-4 font-display font-medium text-2xl flex flex-col items-stretch gap-3 text-left cursor-pointer"
|
||||
aria-expanded={isOpened}
|
||||
aria-controls={contentId}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<span className="flex justify-between">
|
||||
{title}
|
||||
<span
|
||||
className={cn(
|
||||
@ -107,11 +106,12 @@ const Principle = ({
|
||||
<ArrowDown01Icon />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isLastItem ? null : <hr className="hidden md:block" />}
|
||||
</summary>
|
||||
</span>
|
||||
</button>
|
||||
{isLastItem ? null : <hr className="hidden md:block mx-4" />}
|
||||
<motion.div
|
||||
id={contentId}
|
||||
className="overflow-hidden"
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{
|
||||
height: isOpened ? "auto" : 0,
|
||||
@ -120,8 +120,8 @@ const Principle = ({
|
||||
transition={{ duration: 0.4, type: "spring", bounce: 0.15 }}
|
||||
>
|
||||
<hr className="mb-4 md:hidden mx-4 border-border" />
|
||||
<p className="pb-4 mx-4">{content}</p>
|
||||
<p className="py-4 mx-4">{content}</p>
|
||||
</motion.div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,6 +11,6 @@ export const setCookie = async (consent: "declined" | "accepted") => {
|
||||
value: encodeURIComponent(JSON.stringify({ consent })),
|
||||
domain: DEFAULT_COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
expires: new Date(Date.now() + COOKIE_EXPIRATION * 1000),
|
||||
expires: Date.now() + COOKIE_EXPIRATION * 1000,
|
||||
});
|
||||
};
|
||||
|
||||
@ -31,10 +31,6 @@
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"security": {},
|
||||
"a11y": {
|
||||
"noStaticElementInteractions": "off"
|
||||
},
|
||||
"performance": {
|
||||
"noImgElement": "off"
|
||||
},
|
||||
|
||||
4
bun.lock
4
bun.lock
@ -638,7 +638,7 @@
|
||||
},
|
||||
"packages/embeds/js": {
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.9.20",
|
||||
"version": "0.9.21",
|
||||
"devDependencies": {
|
||||
"@ai-sdk/ui-utils": "^1.2.11",
|
||||
"@ark-ui/solid": "^5.19.0",
|
||||
@ -675,7 +675,7 @@
|
||||
},
|
||||
"packages/embeds/react": {
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.9.20",
|
||||
"version": "0.9.21",
|
||||
"dependencies": {
|
||||
"@typebot.io/js": "workspace:*",
|
||||
"react": "^19.2.3",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.9.20",
|
||||
"version": "0.9.21",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"license": "FSL-1.1-ALv2",
|
||||
"type": "module",
|
||||
|
||||
@ -28,8 +28,9 @@ export const EmailInput = (props: Props) => {
|
||||
else inputRef?.focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@ -50,9 +51,9 @@ export const EmailInput = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div class={"flex typebot-input w-full"}>
|
||||
<ShortTextInput
|
||||
@ -67,9 +68,9 @@ export const EmailInput = (props: Props) => {
|
||||
autocomplete="email"
|
||||
/>
|
||||
</div>
|
||||
<SendButton type="button" class="h-[56px]" on:click={submit}>
|
||||
<SendButton type="submit" class="h-14">
|
||||
{props.block.options?.labels?.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -49,8 +49,9 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
} else numberInput().focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@ -71,9 +72,9 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<ArkNumberInput.RootProvider
|
||||
value={numberInput}
|
||||
@ -89,18 +90,24 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
}
|
||||
/>
|
||||
<ArkNumberInput.Control class="flex flex-col rounded-r-md overflow-hidden divide-y h-[56px]">
|
||||
<ArkNumberInput.IncrementTrigger class="flex items-center justify-center h-7 w-8 border-input-border border-l">
|
||||
<ArkNumberInput.IncrementTrigger
|
||||
type="button"
|
||||
class="flex items-center justify-center h-7 w-8 border-input-border border-l"
|
||||
>
|
||||
<ChevronUpIcon class="size-4" />
|
||||
</ArkNumberInput.IncrementTrigger>
|
||||
<ArkNumberInput.DecrementTrigger class="flex items-center justify-center h-7 w-8 border-input-border border-l">
|
||||
<ArkNumberInput.DecrementTrigger
|
||||
type="button"
|
||||
class="flex items-center justify-center h-7 w-8 border-input-border border-l"
|
||||
>
|
||||
<ChevronDownIcon class="size-4" />
|
||||
</ArkNumberInput.DecrementTrigger>
|
||||
</ArkNumberInput.Control>
|
||||
</ArkNumberInput.RootProvider>
|
||||
<SendButton type="button" class="h-[56px]" on:click={submit}>
|
||||
<SendButton type="submit" class="h-14">
|
||||
{props.block.options?.labels?.button ?? defaultNumberInputButtonLabel}
|
||||
</SendButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -68,8 +68,9 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
} else inputRef?.focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
const selectNewCountryCode = (
|
||||
@ -111,9 +112,9 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div class={"flex typebot-input w-full"}>
|
||||
<div class="relative typebot-country-select flex justify-center items-center">
|
||||
@ -158,9 +159,9 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
autofocus={!guessDeviceIsMobile()}
|
||||
/>
|
||||
</div>
|
||||
<SendButton type="button" class="h-[56px]" on:click={submit}>
|
||||
<SendButton type="submit" class="h-14">
|
||||
{props.labels?.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -102,9 +102,9 @@ export const TextInput = (props: Props) => {
|
||||
} else inputRef?.focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (props.block.options?.isLong) return;
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
const submitIfCtrlEnter = (e: KeyboardEvent) => {
|
||||
@ -261,14 +261,14 @@ export const TextInput = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class={cx(
|
||||
"typebot-input-form flex w-full gap-2 items-end",
|
||||
props.block.options?.isLong && recordingStatus() !== "started"
|
||||
? "max-w-full"
|
||||
: "max-w-[350px]",
|
||||
)}
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
onDrop={handleDropFile}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
@ -365,7 +365,8 @@ export const TextInput = (props: Props) => {
|
||||
}
|
||||
>
|
||||
<Button
|
||||
class="h-[56px] flex items-center"
|
||||
type="button"
|
||||
class="h-14 flex items-center"
|
||||
on:click={recordVoice}
|
||||
aria-label="Record voice"
|
||||
>
|
||||
@ -374,15 +375,14 @@ export const TextInput = (props: Props) => {
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<SendButton
|
||||
type="button"
|
||||
on:click={submit}
|
||||
type="submit"
|
||||
isDisabled={Boolean(uploadProgress())}
|
||||
class="h-[56px]"
|
||||
class="h-14"
|
||||
>
|
||||
{props.block.options?.labels?.button}
|
||||
</SendButton>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -26,8 +26,9 @@ export const TimeForm = (props: Props) => {
|
||||
else inputRef?.focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@ -48,9 +49,9 @@ export const TimeForm = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div class={"flex typebot-input w-full"}>
|
||||
<input
|
||||
@ -67,9 +68,9 @@ export const TimeForm = (props: Props) => {
|
||||
data-testid="time"
|
||||
/>
|
||||
</div>
|
||||
<SendButton type="button" class="h-[56px]" on:click={submit}>
|
||||
<SendButton type="submit" class="h-14">
|
||||
{props.block?.labels?.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -32,8 +32,9 @@ export const UrlInput = (props: Props) => {
|
||||
else inputRef?.focus();
|
||||
};
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") submit();
|
||||
const handleSubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@ -57,9 +58,9 @@ export const UrlInput = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<form
|
||||
class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"
|
||||
onKeyDown={submitWhenEnter}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div class={"flex typebot-input w-full"}>
|
||||
<ShortTextInput
|
||||
@ -74,9 +75,9 @@ export const UrlInput = (props: Props) => {
|
||||
autocomplete="url"
|
||||
/>
|
||||
</div>
|
||||
<SendButton type="button" class="h-[56px]" on:click={submit}>
|
||||
<SendButton type="submit" class="h-14">
|
||||
{props.block.options?.labels?.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.9.20",
|
||||
"version": "0.9.21",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"license": "FSL-1.1-ALv2",
|
||||
"type": "module",
|
||||
|
||||
@ -15,13 +15,13 @@
|
||||
"migrateSubscriptionsToUsageBased": "tsx src/migrateSubscriptionsToUsageBased.ts",
|
||||
"insertUsersInBrevoList": "tsx src/insertUsersInBrevoList.ts",
|
||||
"getUsage": "tsx src/getUsage.ts",
|
||||
"suspendWorkspace": "tsx src/suspendWorkspace.ts",
|
||||
"suspendWorkspace": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/suspendWorkspace.ts",
|
||||
"destroyUser": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/destroyUser.ts",
|
||||
"updateTypebot": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/updateTypebot.ts",
|
||||
"updateWorkspace": "tsx src/updateWorkspace.ts",
|
||||
"updateWorkspace": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/updateWorkspace.ts",
|
||||
"inspectTypebot": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/inspectTypebot.ts",
|
||||
"inspectPublishedTypebot": "tsx src/inspectPublishedTypebot.ts",
|
||||
"inspectWorkspace": "tsx src/inspectWorkspace.ts",
|
||||
"inspectWorkspace": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/inspectWorkspace.ts",
|
||||
"getCoupon": "tsx src/getCoupon.ts",
|
||||
"redeemCoupon": "tsx src/redeemCoupon.ts",
|
||||
"exportResults": "SKIP_ENV_CHECK=true dotenv -e ./.env.production -- tsx src/exportResults.ts",
|
||||
|
||||
@ -143,21 +143,7 @@ Workspaces cibles:
|
||||
|
||||
Pourquoi: demande souvent un vrai choix de markup ou de composition de composant, surtout quand il y a des boutons imbriques.
|
||||
|
||||
### PR 3C - politique image par workspace
|
||||
|
||||
Isoler la regle image dans une PR dediee:
|
||||
|
||||
- `performance/noImgElement`
|
||||
|
||||
Workspaces cibles:
|
||||
|
||||
- `apps/landing-page`
|
||||
- autres apps Next si necessaire
|
||||
- `packages/embeds/js` a traiter a part ou a exclure selon la politique retenue
|
||||
|
||||
Pourquoi: la regle pousse naturellement vers `next/image`, mais ce n'est pas adapte tel quel a tous les workspaces du monorepo.
|
||||
|
||||
### PR 3D - nettoyage boucles / callbacks / mutation de parametres
|
||||
### PR 3C - nettoyage boucles / callbacks / mutation de parametres
|
||||
|
||||
Regrouper les refactors surtout mecaniques et peu lies a React:
|
||||
|
||||
@ -172,7 +158,7 @@ Workspaces cibles:
|
||||
|
||||
Pourquoi: diff assez reviewable si isole, risque produit faible a moyen, et bon rendement sur du code de support.
|
||||
|
||||
### PR 3E - durcissement de typage
|
||||
### PR 3D - durcissement de typage
|
||||
|
||||
Traiter ensuite les raccourcis de typage les plus bruyants:
|
||||
|
||||
@ -186,7 +172,7 @@ Workspaces cibles:
|
||||
|
||||
Pourquoi: risque plus eleve sur les contrats de types partages; mieux vaut ne pas melanger ca avec les changements UI ou hooks.
|
||||
|
||||
### PR 3F - correction des hooks React
|
||||
### PR 3E - correction des hooks React
|
||||
|
||||
Finir par les regles les plus semantiques cote React:
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user