mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 00:47:53 +00:00
Webapp stylings (#1916)
This commit is contained in:
1 parent
d2ad5820e0
commit
60bcfffc8d
156 files changed
+2512
-2753
No files matched your search
@@ -20,12 +20,12 @@ export const ButtonIcon = ({
|
||||
...rest
|
||||
}: ButtonIconProps) => {
|
||||
const className = twMerge(
|
||||
"box-content flex h-8 w-8 items-center justify-center rounded-none transition-colors duration-150",
|
||||
"box-content flex h-8 w-8 items-center justify-center rounded-[3px] transition-colors duration-150",
|
||||
bgFill
|
||||
? "bg-ui-controls-button hover:bg-ui-controls-button/[0.75]"
|
||||
: "bg-transparent hover:bg-ui-panel/[0.4]",
|
||||
disabled && "opacity-50 hover:bg-transparent",
|
||||
propsClassName
|
||||
propsClassName,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ButtonLink = ({
|
||||
}
|
||||
}
|
||||
const baseClassName =
|
||||
"font-mono text-xs font-semibold uppercase tracking-[0.15em] whitespace-nowrap rounded-none px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150";
|
||||
"font-mono text-xs font-semibold uppercase tracking-[0.12em] whitespace-nowrap rounded-[3px] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150";
|
||||
const variantClassNames = getVariantClassNames(variant);
|
||||
const className = twMerge(baseClassName, variantClassNames, propsClassName);
|
||||
// END TODO
|
||||
|
||||
@@ -27,13 +27,15 @@ export const ToggleButton = ({
|
||||
<Button
|
||||
className={twMerge(
|
||||
// 34px matches the sibling toolbar controls (GenerateButton, the
|
||||
// PopoverMenu triggers); the 2px border is inside the box.
|
||||
"flex h-[34px] items-center justify-center rounded-none border-2 border-transparent text-sm text-white backdrop-blur-lg transition-all",
|
||||
// PopoverMenu triggers). Flat control surface + hairline border,
|
||||
// same idiom as the PopoverMenu triggers beside it — the old glassy
|
||||
// backdrop-blur look predates the brutalist system.
|
||||
"flex h-[34px] items-center justify-center rounded-[3px] border border-ui-controls-border bg-ui-controls text-sm text-white transition-colors",
|
||||
hasLabel ? "px-3" : "w-[34px]",
|
||||
isActive
|
||||
? "border-white/20 bg-brand-primary/40 hover:border-white/30 hover:bg-brand-primary/40"
|
||||
: "bg-[#5F5F68]/60 hover:bg-[#5F5F68]/90",
|
||||
className
|
||||
? "border-white/30 bg-brand-primary/40 hover:bg-brand-primary/40"
|
||||
: "hover:border-white/30 hover:bg-ui-controls/80",
|
||||
className,
|
||||
)}
|
||||
variant="secondary"
|
||||
onClick={onClick}
|
||||
|
||||
@@ -39,7 +39,9 @@ export const Button = ({
|
||||
function getVariantClassNames(variant: string) {
|
||||
switch (variant) {
|
||||
case "secondary": {
|
||||
return "bg-transparent text-base-fg border border-ui-controls-border hover:bg-white/10 hover:border-white/30";
|
||||
// Resting bg-white/5 tint (matching the home grid cards) so the
|
||||
// button reads as a surface, not a bare outline on the page bg.
|
||||
return "bg-white/5 text-base-fg border border-ui-controls-border hover:bg-white/10 hover:border-white/30";
|
||||
}
|
||||
case "action": {
|
||||
return "bg-ui-controls text-base-fg border border-ui-controls-border hover:bg-ui-controls/80";
|
||||
@@ -62,7 +64,7 @@ export const Button = ({
|
||||
);
|
||||
|
||||
const className = twMerge(
|
||||
"w-fit rounded-none font-mono text-xs font-semibold uppercase tracking-[0.15em] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150 flex gap-2 items-center justify-center",
|
||||
"w-fit rounded-[3px] font-mono text-xs font-semibold uppercase tracking-[0.12em] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150 flex gap-2 items-center justify-center",
|
||||
getVariantClassNames(propsVariant),
|
||||
propsClassName,
|
||||
disabledClass,
|
||||
|
||||
@@ -97,7 +97,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
<span
|
||||
aria-hidden
|
||||
className={twMerge(
|
||||
"relative inline-block shrink-0 rounded-none border transition-colors",
|
||||
"relative inline-block shrink-0 rounded-[3px] border transition-colors",
|
||||
boxDims,
|
||||
isChecked
|
||||
? "border-white bg-white text-black"
|
||||
|
||||
@@ -22,9 +22,9 @@ export const CloseButton = ({
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={twMerge(
|
||||
"flex items-center justify-center rounded-none bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
|
||||
"flex items-center justify-center rounded-[3px] bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
|
||||
sizeClasses[size],
|
||||
className
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<XIcon />
|
||||
|
||||
@@ -25,7 +25,7 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
|
||||
onClick={() => onSetColor(c)}
|
||||
style={{ backgroundColor: c }}
|
||||
className={twMerge(
|
||||
"h-5 w-5 border border-black/20 transition-transform hover:scale-110",
|
||||
"h-5 w-5 rounded-[3px] border border-black/20 transition-transform hover:scale-110",
|
||||
colorCode === c &&
|
||||
"ring-2 ring-white ring-offset-1 ring-offset-ui-panel",
|
||||
)}
|
||||
@@ -37,16 +37,16 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
|
||||
aria-label="Clear color"
|
||||
onClick={() => onSetColor(null)}
|
||||
className={twMerge(
|
||||
"flex h-5 w-5 items-center justify-center border border-ui-panel-border text-base-fg/50 hover:text-base-fg",
|
||||
"flex h-5 w-5 items-center justify-center rounded-[3px] border border-ui-panel-border text-base-fg/50 hover:text-base-fg",
|
||||
!colorCode && "ring-2 ring-white ring-offset-1 ring-offset-ui-panel",
|
||||
)}
|
||||
>
|
||||
<XIcon className="text-[10px]" />
|
||||
<XIcon className="text-[10px]" />
|
||||
</button>
|
||||
{/* Custom hex (native picker) */}
|
||||
<label
|
||||
title="Custom color"
|
||||
className="relative h-5 w-5 cursor-pointer overflow-hidden border border-ui-panel-border"
|
||||
className="relative h-5 w-5 cursor-pointer overflow-hidden rounded-[3px] border border-ui-panel-border"
|
||||
style={{
|
||||
background:
|
||||
"conic-gradient(red, orange, yellow, lime, cyan, blue, magenta, red)",
|
||||
@@ -55,7 +55,9 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
|
||||
<input
|
||||
type="color"
|
||||
value={
|
||||
colorCode && /^#[0-9a-fA-F]{6}$/.test(colorCode) ? colorCode : "#888888"
|
||||
colorCode && /^#[0-9a-fA-F]{6}$/.test(colorCode)
|
||||
? colorCode
|
||||
: "#888888"
|
||||
}
|
||||
onChange={(e) => onSetColor(e.target.value)}
|
||||
className="absolute -inset-1 h-[150%] w-[150%] cursor-pointer opacity-0"
|
||||
|
||||
@@ -62,7 +62,7 @@ export const FolderNameDialog: React.FC<FolderNameDialogProps> = ({
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") submit();
|
||||
}}
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 px-3 py-1.5 text-sm text-base-fg outline-none focus:ring-1 focus:ring-primary/50"
|
||||
className="w-full rounded-[3px] border border-ui-panel-border bg-ui-controls/40 px-3 py-1.5 text-sm text-base-fg outline-none focus:ring-1 focus:ring-primary/50"
|
||||
autoFocus
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { CheckIcon, EllipsisIcon, MusicIcon, UploadIcon } from "lucide-react";
|
||||
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
|
||||
@@ -273,9 +268,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
<div className="flex h-full w-full flex-col bg-gradient-to-br from-white/[0.08] via-white/[0.04] to-white/[0.02]">
|
||||
<div className="flex min-h-0 flex-1 items-center justify-center">
|
||||
<div className="flex h-11 w-11 items-center justify-center bg-white/10 ring-1 ring-white/15">
|
||||
<MusicIcon
|
||||
|
||||
className="text-lg text-white/70" />
|
||||
<MusicIcon className="text-lg text-white/70" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="line-clamp-2 shrink-0 px-2.5 pb-2.5 text-left text-xs leading-snug text-white/75">
|
||||
@@ -316,8 +309,8 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
</>
|
||||
)}
|
||||
{selected && (
|
||||
<div className="absolute left-2 top-2 flex h-6 w-6 items-center justify-center bg-primary">
|
||||
<CheckIcon className="text-sm" />
|
||||
<div className="absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-primary">
|
||||
<CheckIcon className="text-sm" />
|
||||
</div>
|
||||
)}
|
||||
{disabled && (
|
||||
@@ -356,9 +349,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
position="bottom"
|
||||
align="end"
|
||||
mode="default"
|
||||
triggerIcon={
|
||||
<EllipsisIcon className="text-base-fg" />
|
||||
}
|
||||
triggerIcon={<EllipsisIcon className="text-base-fg" />}
|
||||
buttonClassName="h-7 w-7 p-0 bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg border border-ui-controls-border"
|
||||
panelClassName="w-max min-w-44 p-1"
|
||||
closeOnUnhover
|
||||
@@ -394,7 +385,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
/>
|
||||
<div
|
||||
ref={ctxPanelRef}
|
||||
className="fixed z-[9999] w-max min-w-44 border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
|
||||
className="fixed z-[9999] w-max min-w-44 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
|
||||
style={{ left: ctxMenu.x, top: ctxMenu.y }}
|
||||
>
|
||||
<GalleryItemMenuItems
|
||||
@@ -418,7 +409,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
{mode !== "select" && onBulkSelectToggle && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute left-2 top-2 z-[2] flex h-5 w-5 items-center justify-center border-2 cursor-pointer transition-all duration-100",
|
||||
"absolute left-2 top-2 z-[2] flex h-5 w-5 items-center justify-center rounded-[3px] border-2 cursor-pointer transition-all duration-100",
|
||||
bulkSelected
|
||||
? "bg-primary border-primary"
|
||||
: "border-white/60 bg-black/40 hover:border-white",
|
||||
@@ -432,11 +423,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
onBulkSelectToggle?.();
|
||||
}}
|
||||
>
|
||||
{bulkSelected && (
|
||||
<CheckIcon
|
||||
|
||||
className="text-[10px] text-white" />
|
||||
)}
|
||||
{bulkSelected && <CheckIcon className="text-[10px] text-white" />}
|
||||
</div>
|
||||
)}
|
||||
{/* Media class badge on hover — bottom-left */}
|
||||
@@ -452,7 +439,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
|
||||
{/* Upload badge — bottom-right (always visible, even in select mode) */}
|
||||
{item.isUpload && (
|
||||
<div className="pointer-events-none absolute right-2 bottom-2 z-[1] flex h-5 w-5 items-center justify-center bg-black/50 backdrop-blur-lg text-white">
|
||||
<UploadIcon className="text-[10px]" />
|
||||
<UploadIcon className="text-[10px]" />
|
||||
</div>
|
||||
)}
|
||||
{/* Conditionally wrap with Tooltip — hidden when selecting or in bulk mode */}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
|
||||
}}
|
||||
style={colorCode ? { borderColor: colorCode } : undefined}
|
||||
className={twMerge(
|
||||
"group/chip relative w-full aspect-square overflow-hidden border-2 border-ui-controls/40 bg-ui-controls/20 transition-[transform,border-color,background-color,box-shadow] duration-200 ease-out cursor-pointer hover:border-primary/60",
|
||||
"group/chip relative w-full aspect-square overflow-hidden rounded-[3px] border-2 border-ui-controls/40 bg-ui-controls/20 transition-[transform,border-color,background-color,box-shadow] duration-200 ease-out cursor-pointer hover:border-primary/60",
|
||||
// A dragged item hovering the folder makes it lift and glow — clear,
|
||||
// tactile "drop here" feedback rather than a flat color swap.
|
||||
"[&.folder-drag-over]:scale-[1.04] [&.folder-drag-over]:border-primary [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:ring-2 [&.folder-drag-over]:ring-primary/40",
|
||||
@@ -102,9 +102,9 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<FolderIcon
|
||||
|
||||
className={colorCode ? "text-4xl" : "text-4xl text-primary"}
|
||||
style={colorCode ? { color: colorCode } : undefined} />
|
||||
style={colorCode ? { color: colorCode } : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -115,9 +115,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
|
||||
|
||||
{/* Star (favorite) */}
|
||||
{hasStar && (
|
||||
<StarIcon
|
||||
|
||||
className="absolute left-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
|
||||
<StarIcon className="absolute left-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
|
||||
)}
|
||||
|
||||
{/* Name + subfolder count */}
|
||||
@@ -127,12 +125,12 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
|
||||
still reads as a folder. */}
|
||||
{hasArt && (
|
||||
<FolderIcon
|
||||
|
||||
className={twMerge(
|
||||
"flex-shrink-0 text-sm drop-shadow-[0_1px_2px_rgba(0,0,0,0.7)]",
|
||||
colorCode ? "" : "text-primary",
|
||||
)}
|
||||
style={colorCode ? { color: colorCode } : undefined} />
|
||||
style={colorCode ? { color: colorCode } : undefined}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={twMerge(
|
||||
@@ -168,9 +166,9 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
|
||||
e.stopPropagation();
|
||||
openMenuAt(e.currentTarget as HTMLElement);
|
||||
}}
|
||||
className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
|
||||
className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-[3px] bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
|
||||
>
|
||||
<EllipsisIcon className="text-sm" />
|
||||
<EllipsisIcon className="text-sm" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import React, { useState } from "react";
|
||||
import { ChevronRightIcon, EyeIcon, FolderIcon, FolderMinusIcon, FolderPlusIcon, PencilIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
EyeIcon,
|
||||
FolderIcon,
|
||||
FolderMinusIcon,
|
||||
FolderPlusIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { GalleryItem } from "./gallery-modal";
|
||||
import { GalleryFolder } from "./GalleryDraggableItem";
|
||||
@@ -28,7 +37,7 @@ export interface GalleryItemMenuItemsProps {
|
||||
}
|
||||
|
||||
const ROW =
|
||||
"flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap";
|
||||
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap";
|
||||
|
||||
/**
|
||||
* The gallery-tile menu body (Open / Edit / Add to Folder ▸ / Remove from
|
||||
@@ -60,7 +69,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
<EyeIcon className="text-base-fg w-4" />
|
||||
<EyeIcon className="text-base-fg w-4" />
|
||||
<span>
|
||||
{item.mediaClass === "video"
|
||||
? "View video"
|
||||
@@ -85,7 +94,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<PencilIcon className="text-base-fg w-4" />
|
||||
<PencilIcon className="text-base-fg w-4" />
|
||||
<span>Edit image</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -103,22 +112,22 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between gap-2 px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap"
|
||||
className="flex w-full items-center justify-between gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setFolderSubmenuOpen((v) => !v);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderPlusIcon className="text-base-fg w-4" />
|
||||
<FolderPlusIcon className="text-base-fg w-4" />
|
||||
<span>Add to Folder</span>
|
||||
</div>
|
||||
<ChevronRightIcon
|
||||
|
||||
className={twMerge(
|
||||
"text-[10px] text-base-fg/50 transition-transform",
|
||||
COARSE_POINTER && folderSubmenuOpen && "rotate-90",
|
||||
)} />
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{folderSubmenuOpen &&
|
||||
(COARSE_POINTER ? (
|
||||
@@ -138,7 +147,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute left-full top-0 -ml-1 pl-2 z-50">
|
||||
<div className="max-h-64 overflow-y-auto w-max min-w-36 border border-ui-panel-border bg-ui-panel p-1 shadow-xl">
|
||||
<div className="max-h-64 overflow-y-auto w-max min-w-36 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl">
|
||||
<FolderList
|
||||
item={item}
|
||||
folders={folders}
|
||||
@@ -165,7 +174,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<FolderMinusIcon className="text-base-fg w-4" />
|
||||
<FolderMinusIcon className="text-base-fg w-4" />
|
||||
<span>Remove from folder</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -173,14 +182,14 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
|
||||
{onDelete && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm whitespace-nowrap"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm whitespace-nowrap"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
close();
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2Icon className="text-red w-4" />
|
||||
<Trash2Icon className="text-red w-4" />
|
||||
<span className="text-red">Delete</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -209,7 +218,7 @@ const FolderList = ({
|
||||
<button
|
||||
key={folder.id}
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg text-sm"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg text-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAddToFolder?.([item.id], folder.id);
|
||||
@@ -217,9 +226,9 @@ const FolderList = ({
|
||||
}}
|
||||
>
|
||||
<FolderIcon
|
||||
|
||||
className={folder.colorCode ? "text-xs" : "text-primary text-xs"}
|
||||
style={folder.colorCode ? { color: folder.colorCode } : undefined} />
|
||||
style={folder.colorCode ? { color: folder.colorCode } : undefined}
|
||||
/>
|
||||
<span className="truncate">{folder.name}</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -228,14 +237,14 @@ const FolderList = ({
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg/70 text-sm whitespace-nowrap"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg/70 text-sm whitespace-nowrap"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
closeAll();
|
||||
onCreateFolderFromMenu?.();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="text-xs w-4" />
|
||||
<PlusIcon className="text-xs w-4" />
|
||||
<span>New Folder</span>
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -523,14 +523,14 @@ const FolderContextMenuItems = ({
|
||||
}) => (
|
||||
<div
|
||||
className={twMerge(
|
||||
"min-w-44 border border-ui-panel-border bg-ui-panel p-1 shadow-xl z-[59]",
|
||||
"min-w-44 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl z-[59]",
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
onClick={() => onToggleStar(folderId, !hasStar)}
|
||||
>
|
||||
<StarIcon
|
||||
@@ -548,7 +548,7 @@ const FolderContextMenuItems = ({
|
||||
<div className="mx-1.5 my-1 border-t border-ui-panel-border" />
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
onClick={() => onNewSubfolder(folderId)}
|
||||
>
|
||||
<FolderPlusIcon className="w-4" />
|
||||
@@ -556,7 +556,7 @@ const FolderContextMenuItems = ({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
onClick={() => onRename(folderId)}
|
||||
>
|
||||
<PencilIcon className="w-4" />
|
||||
@@ -564,7 +564,7 @@ const FolderContextMenuItems = ({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-red"
|
||||
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-red"
|
||||
onClick={() => onDelete(folderId)}
|
||||
>
|
||||
<Trash2Icon className="w-4" />
|
||||
@@ -2601,14 +2601,14 @@ export const GalleryModal = React.memo(
|
||||
<div className="flex flex-wrap justify-between items-center gap-y-2">
|
||||
<div className="flex items-center gap-3 flex-wrap gap-y-1">
|
||||
{(mode === "view" || mode === "select") && (
|
||||
<div className="flex items-center gap-0.5 bg-ui-controls/40 p-0.5 relative z-[51]">
|
||||
<div className="flex items-center gap-0.5 rounded-[3px] bg-ui-controls/40 p-0.5 relative z-[51]">
|
||||
{(["unsorted", "folders"] as const).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
type="button"
|
||||
onClick={() => switchGalleryTab(t)}
|
||||
className={twMerge(
|
||||
" px-3 py-1 text-sm font-medium transition-colors",
|
||||
"rounded-[3px] px-3 py-1 text-sm font-medium transition-colors",
|
||||
galleryTab === t
|
||||
? "bg-ui-controls text-base-fg"
|
||||
: "text-base-fg/60 hover:text-base-fg",
|
||||
@@ -2625,7 +2625,7 @@ export const GalleryModal = React.memo(
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenNewFolderModal(null)}
|
||||
className="flex items-center gap-2 bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-base-fg hover:bg-ui-controls/90 transition-colors relative z-[51]"
|
||||
className="flex items-center gap-2 rounded-[3px] bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-base-fg hover:bg-ui-controls/90 transition-colors relative z-[51]"
|
||||
>
|
||||
<FolderPlusIcon className="text-xs" />
|
||||
New folder
|
||||
@@ -2654,7 +2654,7 @@ export const GalleryModal = React.memo(
|
||||
type="button"
|
||||
onClick={handleCloseTagView}
|
||||
aria-label="Close tag view"
|
||||
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
>
|
||||
<XIcon className="text-sm" />
|
||||
</button>
|
||||
@@ -2668,7 +2668,7 @@ export const GalleryModal = React.memo(
|
||||
type="button"
|
||||
onClick={handleCloseTagView}
|
||||
aria-label="Close tags"
|
||||
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
>
|
||||
<XIcon className="text-sm" />
|
||||
</button>
|
||||
@@ -2714,7 +2714,7 @@ export const GalleryModal = React.memo(
|
||||
/>
|
||||
) : (
|
||||
<h2
|
||||
className="text-lg font-semibold cursor-pointer hover:bg-ui-controls/30 px-1.5 py-0 transition-colors truncate max-w-[20rem]"
|
||||
className="text-lg font-semibold cursor-pointer rounded-[3px] hover:bg-ui-controls/30 px-1.5 py-0 transition-colors truncate max-w-[20rem]"
|
||||
onClick={() => handleStartRename(activeFolderId!)}
|
||||
>
|
||||
{activeFolder.name}
|
||||
@@ -2732,7 +2732,7 @@ export const GalleryModal = React.memo(
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFolderMenuOpen((v) => !v)}
|
||||
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
|
||||
>
|
||||
<EllipsisIcon className="text-sm" />
|
||||
</button>
|
||||
@@ -2916,7 +2916,7 @@ export const GalleryModal = React.memo(
|
||||
}
|
||||
}}
|
||||
className={twMerge(
|
||||
"flex items-center justify-between gap-2 px-2.5 py-1.5 text-sm transition-colors",
|
||||
"flex items-center justify-between gap-2 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
|
||||
isActive &&
|
||||
!activeFolderId &&
|
||||
!activeTagToken &&
|
||||
@@ -2949,7 +2949,7 @@ export const GalleryModal = React.memo(
|
||||
type="button"
|
||||
onClick={() => handleOpenNewFolderModal(null)}
|
||||
aria-label="New folder"
|
||||
className="flex h-5 w-5 items-center justify-center text-base-fg/50 hover:bg-ui-controls/60 hover:text-base-fg transition-colors"
|
||||
className="flex h-5 w-5 items-center justify-center rounded-[3px] text-base-fg/50 hover:bg-ui-controls/60 hover:text-base-fg transition-colors"
|
||||
>
|
||||
<PlusIcon className="text-[10px]" />
|
||||
</button>
|
||||
@@ -2982,7 +2982,7 @@ export const GalleryModal = React.memo(
|
||||
: undefined
|
||||
}
|
||||
className={twMerge(
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-base-fg",
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-base-fg",
|
||||
activeRootFolderId === folder.id
|
||||
? "bg-ui-controls/60 text-base-fg font-medium"
|
||||
: "text-base-fg/70 hover:bg-ui-controls/30 hover:text-base-fg",
|
||||
@@ -3025,7 +3025,7 @@ export const GalleryModal = React.memo(
|
||||
handleOpenTagFromSidebar(tag.tag_token)
|
||||
}
|
||||
className={twMerge(
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors",
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
|
||||
activeTagToken === tag.tag_token
|
||||
? "bg-ui-controls/60 text-base-fg font-medium"
|
||||
: "text-base-fg/70 hover:bg-ui-controls/30 hover:text-base-fg",
|
||||
@@ -3042,7 +3042,7 @@ export const GalleryModal = React.memo(
|
||||
type="button"
|
||||
onClick={handleOpenTagBrowser}
|
||||
className={twMerge(
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors",
|
||||
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
|
||||
tagBrowserOpen && !activeTagToken
|
||||
? "bg-ui-controls/60 text-base-fg font-medium"
|
||||
: "text-base-fg/50 hover:bg-ui-controls/30 hover:text-base-fg",
|
||||
@@ -3341,7 +3341,7 @@ export const GalleryModal = React.memo(
|
||||
</div>
|
||||
{onLoginClick ? (
|
||||
<button
|
||||
className=" bg-primary px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
className="rounded-[3px] bg-primary px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
||||
onClick={() => onLoginClick()}
|
||||
>
|
||||
Log in
|
||||
@@ -3576,7 +3576,7 @@ export const GalleryModal = React.memo(
|
||||
className="fixed inset-0 z-[59]"
|
||||
onClick={() => setBulkFolderPopoverOpen(false)}
|
||||
/>
|
||||
<div className="absolute bottom-full mb-2 right-0 w-56 border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
|
||||
<div className="absolute bottom-full mb-2 right-0 w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
|
||||
{/* Folders */}
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wider text-base-fg/40 px-2 py-1">
|
||||
Folders
|
||||
@@ -3592,7 +3592,7 @@ export const GalleryModal = React.memo(
|
||||
<button
|
||||
key={folder.id}
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2.5 px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg transition-colors"
|
||||
className="flex w-full items-center gap-2.5 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg transition-colors"
|
||||
onClick={() => {
|
||||
const ids = Array.from(bulkSelectedIds);
|
||||
requestFolderDrop(ids, folder.id);
|
||||
@@ -3607,7 +3607,7 @@ export const GalleryModal = React.memo(
|
||||
<div className="mx-1.5 my-1 border-t border-ui-panel-border" />
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2.5 px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg/70 transition-colors"
|
||||
className="flex w-full items-center gap-2.5 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg/70 transition-colors"
|
||||
onClick={() => {
|
||||
const ids = Array.from(bulkSelectedIds);
|
||||
setBulkFolderPopoverOpen(false);
|
||||
@@ -3633,7 +3633,7 @@ export const GalleryModal = React.memo(
|
||||
)}
|
||||
<button
|
||||
onClick={clearBulkSelection}
|
||||
className="flex h-8 w-8 items-center justify-center bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg transition-colors"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-[3px] bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg transition-colors"
|
||||
aria-label="Clear selection"
|
||||
>
|
||||
<XIcon className="text-base" />
|
||||
|
||||
@@ -37,7 +37,7 @@ export function CopyPromptButton({
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy prompt"
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-white/40 transition-colors hover:bg-white/10 hover:text-white"
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[3px] text-white/40 transition-colors hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
<DynamicIcon icon={copied ? CheckIcon : CopyIcon} className="text-sm" />
|
||||
</button>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const FailedCard = memo(function FailedCard({
|
||||
const iconPath = modelId ? getCreatorIconPathForModelId(modelId) : null;
|
||||
|
||||
return (
|
||||
<div className="relative aspect-square w-full overflow-hidden rounded-lg bg-red-500/10">
|
||||
<div className="relative aspect-square w-full overflow-hidden rounded-[3px] bg-red-500/10">
|
||||
{refImageUrl && (
|
||||
<img
|
||||
src={refImageUrl}
|
||||
@@ -42,9 +42,7 @@ export const FailedCard = memo(function FailedCard({
|
||||
/>
|
||||
)}
|
||||
<div className="relative flex h-full flex-col items-center justify-center gap-2 px-4 sm:px-6">
|
||||
<CircleAlertIcon
|
||||
|
||||
className="text-2xl text-red-400" />
|
||||
<CircleAlertIcon className="text-2xl text-red-400" />
|
||||
<span className="text-center text-xs font-medium text-red-400">
|
||||
{failureReason || "Generation failed"}
|
||||
</span>
|
||||
@@ -58,7 +56,7 @@ export const FailedCard = memo(function FailedCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDismiss(id)}
|
||||
className="flex items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
|
||||
className="flex items-center gap-1.5 rounded-[3px] bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
|
||||
>
|
||||
<XIcon />
|
||||
Dismiss
|
||||
|
||||
@@ -35,9 +35,9 @@ export const FailedRow = memo(function FailedRow({
|
||||
const iconPath = modelId ? getCreatorIconPathForModelId(modelId) : null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 rounded-lg px-2.5 py-2">
|
||||
<div className="flex items-center gap-3 px-2.5 py-2">
|
||||
{/* Error thumbnail — ref image (if any) faded behind the warning icon */}
|
||||
<div className="relative flex size-[100px] shrink-0 items-center justify-center overflow-hidden rounded-md bg-red-500/10 leading-none">
|
||||
<div className="relative flex size-[100px] shrink-0 items-center justify-center overflow-hidden bg-red-500/10 leading-none">
|
||||
{refImageUrl && (
|
||||
<img
|
||||
src={refImageUrl}
|
||||
@@ -46,9 +46,7 @@ export const FailedRow = memo(function FailedRow({
|
||||
className="pointer-events-none absolute inset-0 h-full w-full object-cover opacity-10"
|
||||
/>
|
||||
)}
|
||||
<CircleAlertIcon
|
||||
|
||||
className="relative text-2xl text-red-400" />
|
||||
<CircleAlertIcon className="relative text-2xl text-red-400" />
|
||||
</div>
|
||||
|
||||
{/* Reason + prompt + model */}
|
||||
@@ -87,7 +85,7 @@ export const FailedRow = memo(function FailedRow({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDismiss(id)}
|
||||
className="flex shrink-0 items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
|
||||
className="flex shrink-0 items-center gap-1.5 rounded-[3px] bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
|
||||
>
|
||||
<XIcon />
|
||||
Dismiss
|
||||
|
||||
@@ -24,7 +24,7 @@ export function GalleryAutoplayToggle() {
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Video preview playback"
|
||||
className="flex items-center gap-0.5 rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5"
|
||||
className="flex items-center gap-0.5 rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5"
|
||||
>
|
||||
{OPTIONS.map(({ autoplay, icon, label }) => {
|
||||
const active = autoplayVideos === autoplay;
|
||||
@@ -35,7 +35,7 @@ export function GalleryAutoplayToggle() {
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
onClick={() => setAutoplayVideos(autoplay)}
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
|
||||
active
|
||||
? "bg-white/10 text-white"
|
||||
: "text-white/45 hover:text-white/80"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { memo, useCallback, useState, type ReactNode } from "react";
|
||||
import { BoxIcon, CheckIcon, ImageIcon, MusicIcon, VideoIcon } from "lucide-react";
|
||||
import {
|
||||
BoxIcon,
|
||||
CheckIcon,
|
||||
ImageIcon,
|
||||
MusicIcon,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import {
|
||||
getCreatorIconPathForModelId,
|
||||
@@ -162,7 +168,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`group relative block w-full rounded-lg bg-ui-controls/40 leading-none transition-shadow hover:ring-2 hover:ring-primary-400/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 cursor-pointer ${isSquare ? "aspect-square" : ""} ${selected ? "ring-2 ring-primary-400" : ""}`}
|
||||
className={`group relative block w-full rounded-[3px] bg-ui-controls/40 leading-none transition-shadow hover:ring-2 hover:ring-primary-400/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 cursor-pointer ${isSquare ? "aspect-square" : ""} ${selected ? "ring-2 ring-primary-400" : ""}`}
|
||||
style={outerStyle}
|
||||
onClick={handleCardClick}
|
||||
onKeyDown={handleCardKeyDown}
|
||||
@@ -184,9 +190,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
</p>
|
||||
<div className="flex min-h-0 flex-1 items-center justify-center">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-white/10 ring-1 ring-white/15">
|
||||
<MusicIcon
|
||||
|
||||
className="text-xl text-white/70" />
|
||||
<MusicIcon className="text-xl text-white/70" />
|
||||
</div>
|
||||
</div>
|
||||
{item.fullImage && (
|
||||
@@ -213,25 +217,25 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
{/* Selection checkbox chip (select mode only) */}
|
||||
{selectMode && item.fullImage && (
|
||||
<div
|
||||
className={`pointer-events-none absolute left-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-md border transition-colors ${
|
||||
className={`pointer-events-none absolute left-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-[3px] border transition-colors ${
|
||||
selected
|
||||
? "border-primary-400 bg-primary-400 text-white"
|
||||
: "border-white/60 bg-black/40 text-transparent"
|
||||
}`}
|
||||
>
|
||||
<CheckIcon className="text-[10px]" />
|
||||
<CheckIcon className="text-[10px]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hover overlay with media type + model badges and quick actions */}
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t rounded-b-lg from-black/70 to-transparent px-2 pb-2 pt-6 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t rounded-b-[3px] from-black/70 to-transparent px-2 pb-2 pt-6 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="pointer-events-auto flex min-w-0 flex-wrap items-center gap-1.5">
|
||||
<div className="flex items-center gap-1.5 rounded-lg bg-black/60 px-2.5 py-1 text-xs font-medium text-white/90">
|
||||
<div className="flex items-center gap-1.5 bg-black/60 px-2.5 py-1 text-xs font-medium text-white/90">
|
||||
<DynamicIcon icon={mediaIcon} className="text-[10px]" />
|
||||
{mediaLabel}
|
||||
</div>
|
||||
{modelDisplayName && modelIconPath && (
|
||||
<div className="flex items-center gap-1 rounded-lg bg-black/60 px-2 py-1 text-[10px] text-white/80">
|
||||
<div className="flex items-center gap-1 bg-black/60 px-2 py-1 text-[10px] text-white/80">
|
||||
<img
|
||||
src={modelIconPath}
|
||||
alt=""
|
||||
@@ -243,7 +247,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
</div>
|
||||
|
||||
{actionsSlot && !selectMode && (
|
||||
<div className="pointer-events-auto flex shrink-0 items-center gap-0.5 rounded-lg bg-black/60 p-1 backdrop-blur-sm">
|
||||
<div className="pointer-events-auto flex shrink-0 items-center gap-0.5 rounded-[3px] bg-black/60 p-1 backdrop-blur-sm">
|
||||
{actionsSlot}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { memo, useCallback, type ReactNode } from "react";
|
||||
import { BoxIcon, CheckIcon, ImageIcon, MusicIcon, PlayIcon, VideoIcon } from "lucide-react";
|
||||
import {
|
||||
BoxIcon,
|
||||
CheckIcon,
|
||||
ImageIcon,
|
||||
MusicIcon,
|
||||
PlayIcon,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
getCreatorIconPathForModelId,
|
||||
getModelDisplayName,
|
||||
@@ -51,7 +58,13 @@ export const GalleryRow = memo(function GalleryRow({
|
||||
: isAudio
|
||||
? MusicIcon
|
||||
: ImageIcon;
|
||||
const mediaLabel = isVideo ? "Video" : is3D ? "3D" : isAudio ? "Audio" : "Image";
|
||||
const mediaLabel = isVideo
|
||||
? "Video"
|
||||
: is3D
|
||||
? "3D"
|
||||
: isAudio
|
||||
? "Audio"
|
||||
: "Image";
|
||||
|
||||
const effectiveModelId = modelId ?? item.modelId;
|
||||
const modelDisplayName = effectiveModelId
|
||||
@@ -87,23 +100,23 @@ export const GalleryRow = memo(function GalleryRow({
|
||||
tabIndex={0}
|
||||
onClick={handleRowClick}
|
||||
onKeyDown={handleRowKeyDown}
|
||||
className={`group flex cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 transition-colors hover:bg-white/[0.04] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/60 ${selected ? "bg-primary-400/10" : ""}`}
|
||||
className={`group flex cursor-pointer items-center gap-3 rounded-[3px] px-2.5 py-2 transition-colors hover:bg-white/[0.04] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/60 ${selected ? "bg-primary-400/10" : ""}`}
|
||||
>
|
||||
{/* Selection checkbox (select mode only) */}
|
||||
{selectMode && (
|
||||
<div
|
||||
className={`pointer-events-none flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors ${
|
||||
className={`pointer-events-none flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] border transition-colors ${
|
||||
selected
|
||||
? "border-primary-400 bg-primary-400 text-white"
|
||||
: "border-white/60 bg-black/40 text-transparent"
|
||||
} ${item.fullImage ? "" : "invisible"}`}
|
||||
>
|
||||
<CheckIcon className="text-[10px]" />
|
||||
<CheckIcon className="text-[10px]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Thumbnail */}
|
||||
<div className="relative size-[100px] shrink-0 overflow-hidden rounded-md bg-ui-controls/40 leading-none">
|
||||
<div className="relative size-[100px] shrink-0 overflow-hidden bg-ui-controls/40 leading-none">
|
||||
<GalleryThumbnail
|
||||
thumbnail={item.thumbnail}
|
||||
stillThumbnail={item.stillThumbnail}
|
||||
@@ -116,9 +129,7 @@ export const GalleryRow = memo(function GalleryRow({
|
||||
{isVideo && item.thumbnail && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-black/55 backdrop-blur-sm">
|
||||
<PlayIcon
|
||||
|
||||
className="ml-0.5 text-[9px] text-white/90" />
|
||||
<PlayIcon className="ml-0.5 text-[9px] text-white/90" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -127,7 +138,7 @@ export const GalleryRow = memo(function GalleryRow({
|
||||
{/* Title + model / media meta */}
|
||||
<div className="min-w-0 flex-1">
|
||||
{loading ? (
|
||||
<div className="relative h-4 w-2/3 max-w-[280px] overflow-hidden rounded bg-white/[0.06]">
|
||||
<div className="relative h-4 w-2/3 max-w-[280px] overflow-hidden bg-white/[0.06]">
|
||||
<div className="animate-shimmer absolute inset-0" />
|
||||
</div>
|
||||
) : (
|
||||
@@ -144,7 +155,7 @@ export const GalleryRow = memo(function GalleryRow({
|
||||
</div>
|
||||
)}
|
||||
{isAudio && item.fullImage && (
|
||||
<div className="mt-1.5 max-w-md rounded-md bg-white/[0.04]">
|
||||
<div className="mt-1.5 max-w-md bg-white/[0.04]">
|
||||
<WaveformAudioPlayer
|
||||
src={item.fullImage}
|
||||
durationMillis={item.durationMillis}
|
||||
|
||||
@@ -11,14 +11,14 @@ export function GallerySelectToggle() {
|
||||
const label = active ? "Exit selection" : "Select items";
|
||||
|
||||
return (
|
||||
<div className="flex items-center rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5">
|
||||
<div className="flex items-center rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5">
|
||||
<Tooltip content={label} position="bottom" delay={300}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
onClick={() => setActive(!active)}
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
|
||||
active
|
||||
? "bg-white/10 text-white"
|
||||
: "text-white/45 hover:text-white/80"
|
||||
|
||||
@@ -26,7 +26,7 @@ export function GalleryViewToggle() {
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Gallery layout"
|
||||
className="flex items-center gap-0.5 rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5"
|
||||
className="flex items-center gap-0.5 rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5"
|
||||
>
|
||||
{OPTIONS.map(({ mode, icon, label }) => {
|
||||
const active = viewMode === mode;
|
||||
@@ -37,7 +37,7 @@ export function GalleryViewToggle() {
|
||||
aria-label={label}
|
||||
aria-pressed={active}
|
||||
onClick={() => setViewMode(mode)}
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
|
||||
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
|
||||
active
|
||||
? "bg-white/10 text-white"
|
||||
: "text-white/45 hover:text-white/80"
|
||||
|
||||
@@ -36,18 +36,16 @@ export const PendingCard = memo(function PendingCard({
|
||||
const iconPath = getCreatorIconPathForModelId(modelId);
|
||||
|
||||
return (
|
||||
<div className="group relative aspect-square w-full overflow-hidden rounded-lg bg-white/[0.03]">
|
||||
<div className="group relative aspect-square w-full overflow-hidden rounded-[3px] bg-white/[0.03]">
|
||||
<div className="animate-shimmer h-full w-full" />
|
||||
{batchCount != null && batchCount > 1 && (
|
||||
<div className="absolute left-2 right-2 top-2 z-10 rounded-md bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
|
||||
<div className="absolute left-2 right-2 top-2 z-10 bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
|
||||
Generating {batchCount} {batchNoun(mediaClass)} · Results may appear
|
||||
one at a time
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-2xl text-white/20" />
|
||||
<LoaderCircleIcon className="animate-spin text-2xl text-white/20" />
|
||||
{progressPercent != null && (
|
||||
<span className="text-xs tabular-nums text-white/40">
|
||||
{progressPercent}%
|
||||
|
||||
@@ -38,9 +38,9 @@ export const PendingRow = memo(function PendingRow({
|
||||
const iconPath = getCreatorIconPathForModelId(modelId);
|
||||
|
||||
return (
|
||||
<div className="group flex items-center gap-3 rounded-lg px-2.5 py-2">
|
||||
<div className="group flex items-center gap-3 px-2.5 py-2">
|
||||
{/* Thumbnail placeholder */}
|
||||
<div className="relative size-[100px] shrink-0 overflow-hidden rounded-md bg-white/[0.03] leading-none">
|
||||
<div className="relative size-[100px] shrink-0 overflow-hidden bg-white/[0.03] leading-none">
|
||||
<div className="animate-shimmer h-full w-full" />
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<LoaderCircleIcon
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Input = React.forwardRef(
|
||||
errorMessage,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref: React.ForwardedRef<HTMLInputElement>
|
||||
ref: React.ForwardedRef<HTMLInputElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
@@ -45,13 +45,13 @@ export const Input = React.forwardRef(
|
||||
ref={ref}
|
||||
id={id ? id : label ? label : undefined}
|
||||
className={twMerge(
|
||||
"h-10 w-full rounded-none px-3 py-2.5 outline-none",
|
||||
"h-10 w-full rounded-[3px] px-3 py-2.5 outline-none",
|
||||
"bg-ui-panel text-base-fg placeholder-base-fg/40",
|
||||
"border border-ui-panel-border transition-colors duration-150 ease-in-out hover:border-white/40 focus:border-white focus:!outline-none",
|
||||
"disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-ui-panel-border",
|
||||
icon && "pl-10",
|
||||
isError && "border-red-500 focus:border-red-500",
|
||||
inputClassName
|
||||
inputClassName,
|
||||
)}
|
||||
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onFocus) {
|
||||
@@ -71,7 +71,7 @@ export const Input = React.forwardRef(
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Input.displayName = "Input";
|
||||
|
||||
@@ -147,19 +147,13 @@ const ExpandButton = ({ className, size = "md" }: ExpandButtonProps) => {
|
||||
aria-label={expanded ? "Restore modal size" : "Expand modal"}
|
||||
onClick={toggleExpanded}
|
||||
className={twMerge(
|
||||
"flex items-center justify-center rounded-none bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
|
||||
"flex items-center justify-center rounded-[3px] bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
|
||||
sizeClasses[size],
|
||||
"relative z-[70]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DynamicIcon
|
||||
icon={
|
||||
expanded
|
||||
? Minimize2Icon
|
||||
: Maximize2Icon
|
||||
}
|
||||
/>
|
||||
<DynamicIcon icon={expanded ? Minimize2Icon : Maximize2Icon} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ function ProviderTooltipContent({
|
||||
onFinished?.();
|
||||
}}
|
||||
type="button"
|
||||
className={`group flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 transition-all ${
|
||||
className={`group flex cursor-pointer items-center justify-between rounded-[3px] px-2 py-2 transition-all ${
|
||||
selectedProvider === p
|
||||
? "bg-ui-controls/70 border-l-4 border-primary"
|
||||
: "hover:bg-ui-controls/50"
|
||||
@@ -229,7 +229,7 @@ export function ClassyModelSelector({
|
||||
: undefined;
|
||||
const iconProvider = prov ?? allowedProviders[0];
|
||||
return iconProvider ? (
|
||||
<div className="mr-1 rounded-md p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
|
||||
<div className="mr-1 p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
|
||||
<span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
|
||||
{getProviderIcon(iconProvider)}
|
||||
</span>
|
||||
@@ -241,7 +241,7 @@ export function ClassyModelSelector({
|
||||
isSameModel(item.model as Model | undefined, selectedModel) &&
|
||||
selectedProvider &&
|
||||
hasMultipleProviders ? (
|
||||
<div className="mr-1 rounded-md p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
|
||||
<div className="mr-1 p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
|
||||
<span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
|
||||
{getProviderIcon(selectedProvider)}
|
||||
</span>
|
||||
@@ -313,7 +313,7 @@ export function ClassyModelSelector({
|
||||
mode="hoverSelect"
|
||||
maxListHeight={maxListHeight}
|
||||
{...popoverProps}
|
||||
buttonClassName="rounded-xl bg-ui-controls/90 hover:bg-ui-controls text-left shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
|
||||
buttonClassName="rounded-[3px] bg-ui-controls/90 hover:bg-ui-controls text-left shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
|
||||
renderTrigger={(selectedItem) => {
|
||||
const modelTitle =
|
||||
selectedItem?.label ?? selectedModel?.selectorName ?? "";
|
||||
@@ -340,9 +340,7 @@ export function ClassyModelSelector({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronUpIcon
|
||||
|
||||
className="text-base text-base-fg/70 self-center" />
|
||||
<ChevronUpIcon className="text-base text-base-fg/70 self-center" />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -81,9 +81,11 @@ export const BoardGrid = ({
|
||||
const [viewportH, setViewportH] = useState(0);
|
||||
const [marquee, setMarquee] = useState<Marquee | null>(null);
|
||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||
const marqueeRef = useRef<{ active: boolean; moved: boolean; additive: boolean }>(
|
||||
{ active: false, moved: false, additive: false },
|
||||
);
|
||||
const marqueeRef = useRef<{
|
||||
active: boolean;
|
||||
moved: boolean;
|
||||
additive: boolean;
|
||||
}>({ active: false, moved: false, additive: false });
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
@@ -103,7 +105,10 @@ export const BoardGrid = ({
|
||||
const groups = useMemo<SectionGroup[] | null>(() => {
|
||||
if (!sections || sections.length === 0) return null;
|
||||
const known = new Set(sections.map((s) => s.id));
|
||||
const bySection = new Map<string | null, Array<{ id: string; aspect: number }>>();
|
||||
const bySection = new Map<
|
||||
string | null,
|
||||
Array<{ id: string; aspect: number }>
|
||||
>();
|
||||
for (const it of items) {
|
||||
const key = it.sectionId && known.has(it.sectionId) ? it.sectionId : null;
|
||||
const entry = { id: it.id, aspect: it.aspect };
|
||||
@@ -119,7 +124,10 @@ export const BoardGrid = ({
|
||||
}));
|
||||
const ungrouped = bySection.get(null) ?? [];
|
||||
return ungrouped.length > 0
|
||||
? [{ id: null, name: "Ungrouped", collapsed: false, items: ungrouped }, ...named]
|
||||
? [
|
||||
{ id: null, name: "Ungrouped", collapsed: false, items: ungrouped },
|
||||
...named,
|
||||
]
|
||||
: named;
|
||||
}, [items, sections]);
|
||||
|
||||
@@ -313,7 +321,7 @@ export const BoardGrid = ({
|
||||
})}
|
||||
{marquee && marqueeRef.current.moved && (
|
||||
<div
|
||||
className="pointer-events-none absolute rounded-[4px] border border-primary bg-primary/10"
|
||||
className="pointer-events-none absolute rounded-[3px] border border-primary bg-primary/10"
|
||||
style={{
|
||||
left: Math.min(marquee.x0, marquee.x1),
|
||||
top: Math.min(marquee.y0, marquee.y1),
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { FilmIcon, PlusIcon, RepeatIcon, SplineIcon, XIcon } from "lucide-react";
|
||||
import {
|
||||
FilmIcon,
|
||||
PlusIcon,
|
||||
RepeatIcon,
|
||||
SplineIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { EngineContext } from "../../contexts/EngineContext/EngineContext";
|
||||
import {
|
||||
addBakedClipToObject,
|
||||
@@ -11,16 +17,17 @@ import {
|
||||
setClipLoop,
|
||||
setClipTransitionEasing,
|
||||
} from "../../actions";
|
||||
import {
|
||||
usePageSceneStore,
|
||||
type ClipLane,
|
||||
} from "../../PageSceneStore";
|
||||
import { usePageSceneStore, type ClipLane } from "../../PageSceneStore";
|
||||
import {
|
||||
DEFAULT_EASING,
|
||||
DEFAULT_TIMELINE_FPS,
|
||||
} from "../../engine/timeline/types";
|
||||
import { ToastTypes } from "../../enums";
|
||||
import { fractionToTime, quantizeToFrame, timeToFraction } from "./timelineUtils";
|
||||
import {
|
||||
fractionToTime,
|
||||
quantizeToFrame,
|
||||
timeToFraction,
|
||||
} from "./timelineUtils";
|
||||
|
||||
interface TimelineClipRowProps {
|
||||
objectUuid: string;
|
||||
@@ -62,9 +69,7 @@ export const TimelineClipRow = ({
|
||||
// effectively invisible.
|
||||
const [pickerAnchor, setPickerAnchor] = useState<DOMRect | null>(null);
|
||||
const pickerMenuRef = useRef<HTMLDivElement>(null);
|
||||
const selectedLaneId = usePageSceneStore(
|
||||
(s) => s.timelineSelectedClipLaneId,
|
||||
);
|
||||
const selectedLaneId = usePageSceneStore((s) => s.timelineSelectedClipLaneId);
|
||||
|
||||
// A fixed-position popup can't follow its button, so any outside scroll
|
||||
// or resize closes it (scrolling INSIDE the popup's own list is exempt).
|
||||
@@ -154,14 +159,14 @@ export const TimelineClipRow = ({
|
||||
return (
|
||||
<div className="flex items-center gap-3 py-0.5">
|
||||
<div className="flex w-32 shrink-0 items-center gap-2 truncate ps-6 text-xs text-base-fg/50">
|
||||
<FilmIcon className="h-3 w-3 opacity-60" />
|
||||
<FilmIcon className="h-3 w-3 opacity-60" />
|
||||
<span className="truncate">Animation</span>
|
||||
{bakedClips && bakedClips.length > 0 && (
|
||||
<div className="shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
title="Add one of this object's baked animations"
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-[4px] transition-colors ${
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-[3px] transition-colors ${
|
||||
pickerAnchor
|
||||
? "bg-brand-primary text-white"
|
||||
: "bg-white/10 text-base-fg/70 hover:bg-white/20 hover:text-base-fg"
|
||||
@@ -172,7 +177,7 @@ export const TimelineClipRow = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
<PlusIcon className="h-2.5 w-2.5" />
|
||||
<PlusIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
{pickerAnchor &&
|
||||
ReactDOM.createPortal(
|
||||
@@ -283,7 +288,7 @@ export const TimelineClipRow = ({
|
||||
editor && setClipLoop(editor, lane.id, !strip.loop)
|
||||
}
|
||||
>
|
||||
<RepeatIcon className="h-2.5 w-2.5" />
|
||||
<RepeatIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
|
||||
<span className="min-w-0 flex-1 truncate">{strip.name}</span>
|
||||
@@ -305,7 +310,7 @@ export const TimelineClipRow = ({
|
||||
.setTimelineSelectedClipLane(null);
|
||||
}}
|
||||
>
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -359,7 +364,7 @@ export const TimelineClipRow = ({
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={() => toggleTransition(lane)}
|
||||
>
|
||||
<SplineIcon className="h-2 w-2" />
|
||||
<SplineIcon className="h-2 w-2" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -15,7 +15,14 @@ import {
|
||||
} from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CircleCheckIcon, InfoIcon } from "lucide-react";
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronUpIcon,
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
} from "lucide-react";
|
||||
import { Model, ModelInfo } from "@storyteller/model-list";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
@@ -136,7 +143,7 @@ function PortalTooltip({
|
||||
zIndex: 9999,
|
||||
}}
|
||||
className={twMerge(
|
||||
"pointer-events-auto rounded-none bg-ui-panel p-3 border border-ui-panel-border text-base-fg",
|
||||
"pointer-events-auto rounded-[3px] bg-ui-panel p-3 border border-ui-panel-border text-base-fg",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
@@ -237,7 +244,7 @@ function InfoHint({ content }: { content: ReactNode }) {
|
||||
if (!pinnedRef.current) setOpen(false);
|
||||
}}
|
||||
>
|
||||
<InfoIcon className="h-3.5 w-3.5" />
|
||||
<InfoIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{open &&
|
||||
createPortal(
|
||||
@@ -253,7 +260,7 @@ function InfoHint({ content }: { content: ReactNode }) {
|
||||
transform: "translate(-50%, -100%)",
|
||||
zIndex: 10000,
|
||||
}}
|
||||
className="pointer-events-auto rounded-none border border-ui-panel-border bg-ui-panel px-3 py-1.5 text-center text-xs leading-relaxed text-base-fg"
|
||||
className="pointer-events-auto rounded-[3px] border border-ui-panel-border bg-ui-panel px-3 py-1.5 text-center text-xs leading-relaxed text-base-fg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseEnter={() => {
|
||||
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
|
||||
@@ -349,7 +356,7 @@ function RichListRow({
|
||||
if (!item.disabled) onClick?.();
|
||||
}}
|
||||
className={twMerge(
|
||||
"group flex cursor-pointer items-center gap-3 rounded-none px-2 py-2 transition-colors",
|
||||
"group flex cursor-pointer items-center gap-3 rounded-[3px] px-2 py-2 transition-colors",
|
||||
item.selected ? "bg-ui-controls/70" : "hover:bg-ui-controls/50",
|
||||
!item.selected && active ? "bg-ui-controls/50" : "",
|
||||
item.disabled ? "!cursor-not-allowed opacity-50" : "",
|
||||
@@ -404,10 +411,8 @@ function RichListRow({
|
||||
{rightNode ??
|
||||
(item.selected &&
|
||||
(item.selectedRight ?? (
|
||||
<span className="ml-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-none bg-white">
|
||||
<CheckIcon
|
||||
|
||||
className="text-[11px] font-bold text-black" />
|
||||
<span className="ml-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] bg-white">
|
||||
<CheckIcon className="text-[11px] font-bold text-black" />
|
||||
</span>
|
||||
)))}
|
||||
</div>
|
||||
@@ -629,7 +634,7 @@ function SubmenuFlyout({
|
||||
transform: "translateY(-50%)",
|
||||
zIndex: 9999,
|
||||
}}
|
||||
className="pointer-events-auto w-[340px] rounded-none border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg"
|
||||
className="pointer-events-auto w-[340px] rounded-[3px] border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg"
|
||||
>
|
||||
<div className="mb-1 mt-0.5 px-1.5 text-sm font-normal text-base-fg opacity-70">
|
||||
{item.label}
|
||||
@@ -643,9 +648,7 @@ function SubmenuFlyout({
|
||||
canScrollUp ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<ChevronUpIcon
|
||||
|
||||
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
<ChevronUpIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
</div>
|
||||
<div
|
||||
ref={scrollRef}
|
||||
@@ -695,9 +698,7 @@ function SubmenuFlyout({
|
||||
canScrollDown ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
|
||||
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
<ChevronDownIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
@@ -850,7 +851,7 @@ export const PopoverMenu = ({
|
||||
};
|
||||
|
||||
const className = twMerge(
|
||||
"text-sm font-medium rounded-none px-2.5 py-1.5",
|
||||
"text-sm font-medium rounded-[3px] px-2.5 py-1.5",
|
||||
"flex gap-2 items-center justify-center outline-none",
|
||||
"transition-colors duration-150",
|
||||
"bg-ui-controls px-3 text-base-fg hover:bg-ui-controls/80 border border-ui-controls-border",
|
||||
@@ -1051,9 +1052,7 @@ export const PopoverMenu = ({
|
||||
<span className="opacity-70">{triggerLabel}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate">{selectedItem.label}</span>
|
||||
<ChevronUpIcon
|
||||
|
||||
className="text-sm" />
|
||||
<ChevronUpIcon className="text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1091,7 +1090,7 @@ export const PopoverMenu = ({
|
||||
<div
|
||||
ref={panelContentRef}
|
||||
className={twMerge(
|
||||
"z-10 min-w-48 mt-2 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border overflow-visible",
|
||||
"z-10 min-w-48 mt-2 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border overflow-visible",
|
||||
position === "top" ? "mb-2" : "mt-2",
|
||||
panelClassName,
|
||||
)}
|
||||
@@ -1130,9 +1129,7 @@ export const PopoverMenu = ({
|
||||
canScrollUp ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<ChevronUpIcon
|
||||
|
||||
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
<ChevronUpIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
</div>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
@@ -1164,9 +1161,7 @@ export const PopoverMenu = ({
|
||||
item={item}
|
||||
active={openTooltipIdx === index}
|
||||
rightNode={
|
||||
<ChevronRightIcon
|
||||
|
||||
className="ml-1 shrink-0 text-xs text-base-fg/50" />
|
||||
<ChevronRightIcon className="ml-1 shrink-0 text-xs text-base-fg/50" />
|
||||
}
|
||||
/>
|
||||
</SubmenuFlyout>
|
||||
@@ -1231,18 +1226,14 @@ export const PopoverMenu = ({
|
||||
canScrollDown ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
|
||||
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
<ChevronDownIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
|
||||
</div>
|
||||
</div>
|
||||
) : mode === "hoverSelect" ? (
|
||||
<div className="relative flex flex-col text-sm text-base-fg overflow-visible">
|
||||
{maxListHeight && canScrollUp && (
|
||||
<div className="absolute top-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-b from-ui-panel via-ui-panel/80 to-transparent py-1.5 pointer-events-none">
|
||||
<ChevronUpIcon
|
||||
|
||||
className="text-base-fg/60 text-xs animate-bounce" />
|
||||
<ChevronUpIcon className="text-base-fg/60 text-xs animate-bounce" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
@@ -1280,7 +1271,7 @@ export const PopoverMenu = ({
|
||||
}
|
||||
}}
|
||||
className={twMerge(
|
||||
"group flex cursor-pointer items-start gap-2 rounded-none px-2 py-2 transition-all",
|
||||
"group flex cursor-pointer items-start gap-2 rounded-[3px] px-2 py-2 transition-all",
|
||||
item.selected
|
||||
? "bg-white/10 border-l-2 border-white"
|
||||
: "hover:bg-ui-controls/50",
|
||||
@@ -1375,7 +1366,7 @@ export const PopoverMenu = ({
|
||||
position="right"
|
||||
delay={item.tooltipDelayMs ?? 300}
|
||||
interactive
|
||||
className="!pointer-events-auto z-50 min-w-48 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border"
|
||||
className="!pointer-events-auto z-50 min-w-48 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border"
|
||||
onOpenChange={(open) =>
|
||||
setOpenTooltipIdx((prev) =>
|
||||
open
|
||||
@@ -1401,9 +1392,7 @@ export const PopoverMenu = ({
|
||||
</div>
|
||||
{maxListHeight && canScrollDown && (
|
||||
<div className="absolute bottom-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-t from-ui-panel via-ui-panel/80 to-transparent py-1.5 pointer-events-none">
|
||||
<ChevronDownIcon
|
||||
|
||||
className="text-base-fg/60 text-xs animate-bounce" />
|
||||
<ChevronDownIcon className="text-base-fg/60 text-xs animate-bounce" />
|
||||
</div>
|
||||
)}
|
||||
{showAddButton && onAdd && (
|
||||
@@ -1440,7 +1429,7 @@ export const PopoverMenu = ({
|
||||
position="right"
|
||||
delay={item.tooltipDelayMs ?? 1000}
|
||||
interactive
|
||||
className="!pointer-events-auto z-50 min-w-48 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border"
|
||||
className="!pointer-events-auto z-50 min-w-48 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border"
|
||||
onOpenChange={(open) =>
|
||||
setOpenTooltipIdx((prev) =>
|
||||
open ? index : prev === index ? null : prev,
|
||||
@@ -1450,6 +1439,7 @@ export const PopoverMenu = ({
|
||||
<Button
|
||||
className={twMerge(
|
||||
"flex w-full items-center shadow-none justify-between px-1.5",
|
||||
"font-sans text-sm font-medium normal-case tracking-normal",
|
||||
"bg-transparent hover:bg-ui-controls/60",
|
||||
openTooltipIdx === index
|
||||
? "bg-ui-controls/60"
|
||||
@@ -1504,16 +1494,14 @@ export const PopoverMenu = ({
|
||||
{mode === "toggle" && (
|
||||
<span
|
||||
className={twMerge(
|
||||
"ml-2 h-5 w-5 rounded-none border flex items-center justify-center transition-colors",
|
||||
"ml-2 h-5 w-5 rounded-[3px] border flex items-center justify-center transition-colors",
|
||||
item.selected
|
||||
? "border-white bg-white"
|
||||
: "border-transparent bg-transparent",
|
||||
)}
|
||||
>
|
||||
{item.selected && (
|
||||
<CheckIcon
|
||||
|
||||
className="text-black text-xs font-bold" />
|
||||
<CheckIcon className="text-black text-xs font-bold" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
@@ -1523,6 +1511,7 @@ export const PopoverMenu = ({
|
||||
<Button
|
||||
className={twMerge(
|
||||
"flex w-full items-center shadow-none justify-between px-1.5",
|
||||
"font-sans text-sm font-medium normal-case tracking-normal",
|
||||
"bg-transparent hover:bg-ui-controls/60",
|
||||
mode === "toggle" && item.selected
|
||||
? "hover:bg-ui-controls/80"
|
||||
@@ -1567,16 +1556,14 @@ export const PopoverMenu = ({
|
||||
{mode === "toggle" && (
|
||||
<span
|
||||
className={twMerge(
|
||||
"ml-2 h-5 w-5 rounded-none border flex items-center justify-center transition-colors",
|
||||
"ml-2 h-5 w-5 rounded-[3px] border flex items-center justify-center transition-colors",
|
||||
item.selected
|
||||
? "border-white bg-white"
|
||||
: "border-transparent bg-transparent",
|
||||
)}
|
||||
>
|
||||
{item.selected && (
|
||||
<CheckIcon
|
||||
|
||||
className="text-black text-xs font-bold" />
|
||||
<CheckIcon className="text-black text-xs font-bold" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,8 @@ interface PricingContentProps {
|
||||
export function PricingContent({ title, subtitle }: PricingContentProps) {
|
||||
const subscriptionStore = useSubscriptionState();
|
||||
const hasActiveSub = subscriptionStore.hasPaidPlan();
|
||||
const activePlanId = subscriptionStore.subscriptionInfo?.productSlug || "free";
|
||||
const activePlanId =
|
||||
subscriptionStore.subscriptionInfo?.productSlug || "free";
|
||||
|
||||
const [billingType, setBillingType] = useState("yearly");
|
||||
const isYearly = billingType === "yearly";
|
||||
@@ -58,7 +59,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
|
||||
|
||||
const handleUpdatePaymentMethod = async () => {
|
||||
await invoke(
|
||||
"storyteller_open_customer_portal_update_payment_method_command"
|
||||
"storyteller_open_customer_portal_update_payment_method_command",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -170,7 +171,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
|
||||
tabs={billingTabs}
|
||||
activeTab={billingType}
|
||||
onTabChange={setBillingType}
|
||||
className="w-fit rounded-none border border-white/15 bg-white/5"
|
||||
className="w-fit rounded-[3px] border border-white/15 bg-white/5"
|
||||
tabClassName="w-24 text-md"
|
||||
/>
|
||||
<span className="pointer-events-none absolute -left-6 -top-3 rounded-none bg-primary px-3 py-0.5 text-sm font-medium text-white">
|
||||
@@ -403,7 +404,7 @@ const Feature = ({
|
||||
: "border border-white/20 text-transparent",
|
||||
)}
|
||||
>
|
||||
{included && <CheckIcon className="text-xs" />}
|
||||
{included && <CheckIcon className="text-xs" />}
|
||||
</div>
|
||||
<span
|
||||
className={twMerge(
|
||||
|
||||
@@ -320,7 +320,7 @@ const CharacterListView = ({
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05]"
|
||||
className="flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05]"
|
||||
>
|
||||
<div className="aspect-square w-full overflow-hidden">
|
||||
<div
|
||||
@@ -332,7 +332,7 @@ const CharacterListView = ({
|
||||
</div>
|
||||
<div className="px-2 py-1.5 flex justify-center bg-base-fg/[0.04]">
|
||||
<div
|
||||
className="h-3 w-2/3 rounded bg-base-fg/[0.08]"
|
||||
className="h-3 w-2/3 bg-base-fg/[0.08]"
|
||||
style={{
|
||||
animation: `charPulse 1.8s ease-in-out ${i * 0.07 + 0.1}s infinite`,
|
||||
}}
|
||||
@@ -352,7 +352,7 @@ const CharacterListView = ({
|
||||
{/* Create New card */}
|
||||
<button
|
||||
onClick={onCreateClick}
|
||||
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-base-fg/10 bg-base-fg/[0.05] text-base-fg/60 transition-colors hover:border-base-fg/25 hover:text-base-fg/80"
|
||||
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-[3px] border-2 border-dashed border-base-fg/10 bg-base-fg/[0.05] text-base-fg/60 transition-colors hover:border-base-fg/25 hover:text-base-fg/80"
|
||||
>
|
||||
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
|
||||
<PlusIcon className="text-lg" />
|
||||
@@ -364,7 +364,7 @@ const CharacterListView = ({
|
||||
{pendingCharacters.map((pending) => (
|
||||
<div
|
||||
key={`pending-${pending.name}`}
|
||||
className="relative flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05]"
|
||||
className="relative flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05]"
|
||||
>
|
||||
<div className="aspect-square w-full overflow-hidden bg-base-fg/[0.05]">
|
||||
{pending.previewUrl ? (
|
||||
@@ -401,7 +401,7 @@ const CharacterListView = ({
|
||||
return (
|
||||
<div
|
||||
key={character.token}
|
||||
className="group relative flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05] transition-colors hover:border-base-fg/25 hover:bg-base-fg/10"
|
||||
className="group relative flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05] transition-colors hover:border-base-fg/25 hover:bg-base-fg/10"
|
||||
>
|
||||
<button
|
||||
onClick={() => onSelectCharacter?.(character)}
|
||||
@@ -570,7 +570,7 @@ const EditCharacterView = ({
|
||||
|
||||
{/* Avatar preview */}
|
||||
{character.maybe_avatar?.cdn_url && (
|
||||
<div className="flex h-56 max-h-56 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-base-fg/[0.05]">
|
||||
<div className="flex h-56 max-h-56 shrink-0 items-center justify-center overflow-hidden bg-base-fg/[0.05]">
|
||||
<img
|
||||
src={character.maybe_avatar.cdn_url}
|
||||
alt={character.name}
|
||||
@@ -605,7 +605,7 @@ const EditCharacterView = ({
|
||||
placeholder="Description..."
|
||||
rows={3}
|
||||
autoComplete="off"
|
||||
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
|
||||
className="w-full resize-none rounded-[3px] px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -829,7 +829,7 @@ const NewCharacterView = ({
|
||||
<div
|
||||
ref={dropZoneRef}
|
||||
className={twMerge(
|
||||
"flex h-56 max-h-56 shrink-0 cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
|
||||
"flex h-56 max-h-56 shrink-0 cursor-pointer flex-col items-center justify-center rounded-[3px] border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
|
||||
isDragging && "border-blue-400 bg-blue-500/10",
|
||||
)}
|
||||
onDragEnter={handleDragEnter}
|
||||
@@ -880,14 +880,14 @@ const NewCharacterView = ({
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsGalleryOpen(true)}
|
||||
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
|
||||
className="flex items-center gap-2 rounded-[3px] bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
|
||||
>
|
||||
<ImagesIcon className="text-xs" />
|
||||
Choose from Library
|
||||
</button>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
|
||||
className="flex items-center gap-2 rounded-[3px] bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
|
||||
>
|
||||
<UploadIcon className="text-xs" />
|
||||
Upload Image
|
||||
@@ -931,7 +931,7 @@ const NewCharacterView = ({
|
||||
placeholder="Description..."
|
||||
rows={3}
|
||||
autoComplete="off"
|
||||
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
|
||||
className="w-full resize-none rounded-[3px] px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,7 +115,7 @@ const AudioRefTile = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer flex items-center justify-center">
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer flex items-center justify-center">
|
||||
<button
|
||||
onClick={handleTogglePlay}
|
||||
className="flex items-center justify-center w-full h-full"
|
||||
@@ -255,7 +255,7 @@ export const ImagePromptRow = ({
|
||||
style={style}
|
||||
{...(allowReorder ? { ...attributes, ...listeners } : {})}
|
||||
className={twMerge(
|
||||
"glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 transition-opacity group",
|
||||
"glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 transition-opacity group",
|
||||
allowReorder
|
||||
? "cursor-move hover:border-white/80"
|
||||
: "cursor-pointer hover:border-white/80",
|
||||
@@ -845,7 +845,7 @@ export const ImagePromptRow = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute left-0 glass w-full rounded-t-2xl flex",
|
||||
"absolute left-0 glass w-full rounded-t-[3px] flex",
|
||||
showVideoReferenceSection ? "-top-[144px]" : "-top-[72px]",
|
||||
className,
|
||||
)}
|
||||
@@ -916,7 +916,7 @@ export const ImagePromptRow = ({
|
||||
.map((image, index) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
onClick={() => onImageClick?.(image)}
|
||||
>
|
||||
<img
|
||||
@@ -953,7 +953,7 @@ export const ImagePromptRow = ({
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30"
|
||||
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30"
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
@@ -1006,7 +1006,7 @@ export const ImagePromptRow = ({
|
||||
>
|
||||
<Button
|
||||
variant="action"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
onClick={() => {
|
||||
if (allowUpload) handleUploadClickStart();
|
||||
else {
|
||||
@@ -1045,7 +1045,7 @@ export const ImagePromptRow = ({
|
||||
<div className="flex gap-2 items-center">
|
||||
{endFrameImage ? (
|
||||
<div
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
onClick={() => onImageClick?.(endFrameImage)}
|
||||
>
|
||||
<img
|
||||
@@ -1066,7 +1066,7 @@ export const ImagePromptRow = ({
|
||||
</button>
|
||||
</div>
|
||||
) : uploadingEnd ? (
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30">
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30">
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={URL.createObjectURL(uploadingEnd.file)}
|
||||
@@ -1115,7 +1115,7 @@ export const ImagePromptRow = ({
|
||||
>
|
||||
<Button
|
||||
variant="action"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
onClick={() => {
|
||||
if (allowUploadEnd) handleUploadClickEnd();
|
||||
else {
|
||||
@@ -1156,7 +1156,7 @@ export const ImagePromptRow = ({
|
||||
{referenceVideos.map((video) => (
|
||||
<div
|
||||
key={video.id}
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer"
|
||||
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer"
|
||||
>
|
||||
<video
|
||||
src={video.url}
|
||||
@@ -1181,7 +1181,7 @@ export const ImagePromptRow = ({
|
||||
</div>
|
||||
))}
|
||||
{uploadingVideo && (
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30">
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30">
|
||||
<div className="absolute inset-0">
|
||||
<video
|
||||
src={URL.createObjectURL(uploadingVideo.file)}
|
||||
@@ -1231,7 +1231,7 @@ export const ImagePromptRow = ({
|
||||
>
|
||||
<Button
|
||||
variant="action"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
onClick={handleUploadClickVideo}
|
||||
>
|
||||
<PlusIcon
|
||||
@@ -1271,7 +1271,7 @@ export const ImagePromptRow = ({
|
||||
/>
|
||||
))}
|
||||
{uploadingAudio && (
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 flex items-center justify-center">
|
||||
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 flex items-center justify-center">
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="h-6 w-6 animate-spin text-white" />
|
||||
@@ -1281,7 +1281,7 @@ export const ImagePromptRow = ({
|
||||
!uploadingAudio && (
|
||||
<Button
|
||||
variant="action"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
|
||||
onClick={handleUploadClickAudio}
|
||||
>
|
||||
<PlusIcon
|
||||
@@ -1296,7 +1296,7 @@ export const ImagePromptRow = ({
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-[1px] h-full bg-white/10" />
|
||||
<div className="flex items-center gap-2 w-[1px] h-full bg-base-fg/20 dark:bg-base-fg/10 rounded-lg" />
|
||||
<div className="flex items-center gap-2 w-[1px] h-full bg-base-fg/20 dark:bg-base-fg/10" />
|
||||
<div className="p-2">
|
||||
<Button
|
||||
variant="action"
|
||||
|
||||
@@ -20,7 +20,7 @@ const ANCHOR_GAP = 4;
|
||||
const OPEN_GRACE_MS = 700;
|
||||
|
||||
const MENU_ROW =
|
||||
"flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-base-fg transition-colors hover:bg-ui-controls/60 cursor-pointer";
|
||||
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 text-sm text-base-fg transition-colors hover:bg-ui-controls/60 cursor-pointer";
|
||||
|
||||
// Header subtitle and empty-replace-list copy per chip type.
|
||||
const TYPE_COPY: Record<MentionItem["type"], { title: string; empty: string }> =
|
||||
@@ -182,7 +182,7 @@ export function MentionChipMenu({
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
onTouchMove={(e) => e.stopPropagation()}
|
||||
className={twMerge(
|
||||
"fixed z-[9999] w-56 rounded-lg border border-ui-panel-border bg-ui-panel p-1 shadow-xl",
|
||||
"fixed z-[9999] w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl",
|
||||
"transform-gpu transition duration-75 ease-out",
|
||||
entered
|
||||
? "translate-y-0 opacity-100"
|
||||
@@ -210,7 +210,7 @@ export function MentionChipMenu({
|
||||
onClick={onPreview}
|
||||
title="View full size"
|
||||
aria-label={`View ${currentName}`}
|
||||
className="group/avatar relative shrink-0 cursor-pointer overflow-hidden rounded-md"
|
||||
className="group/avatar relative shrink-0 cursor-pointer overflow-hidden rounded-[3px]"
|
||||
>
|
||||
<ChipAvatar
|
||||
preview={currentPreview}
|
||||
@@ -255,7 +255,7 @@ export function MentionChipMenu({
|
||||
<div className="flex items-center gap-2 px-2 py-1.5">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-base-fg/60 transition-colors hover:bg-ui-controls/60 hover:text-base-fg"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-[3px] text-base-fg/60 transition-colors hover:bg-ui-controls/60 hover:text-base-fg"
|
||||
onClick={() => setView("menu")}
|
||||
aria-label="Back"
|
||||
>
|
||||
@@ -312,7 +312,7 @@ function ChipAvatar({
|
||||
}) {
|
||||
const FallbackIcon = type === "character" ? UserIcon : ImageIcon;
|
||||
return (
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-white/10 bg-black/20">
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden border border-white/10 bg-black/20">
|
||||
{preview ? (
|
||||
<img src={preview} alt={name} className="h-full w-full object-cover" />
|
||||
) : (
|
||||
|
||||
@@ -83,9 +83,9 @@ interface ChipMenuState {
|
||||
// itself bg-ui-controls, so the default bg-ui-controls/80 chip disappears
|
||||
// against it.
|
||||
const CHIP_CLASS =
|
||||
"mention-chip mx-0.5 inline-flex max-w-[10rem] cursor-pointer items-center gap-1 rounded-md border border-white/10 bg-ui-controls/80 px-1.5 py-0.5 align-middle leading-tight transition-colors hover:border-white/25 hover:bg-white/15 [@media(hover:none)]:bg-black/30 [@media(hover:none)]:border-white/15";
|
||||
"mention-chip mx-0.5 inline-flex max-w-[10rem] cursor-pointer items-center gap-1 rounded-[3px] border border-white/10 bg-ui-controls/80 px-1.5 py-0.5 align-middle leading-tight transition-colors hover:border-white/25 hover:bg-white/15 [@media(hover:none)]:bg-black/30 [@media(hover:none)]:border-white/15";
|
||||
const CHIP_IMG_CLASS =
|
||||
"pointer-events-none h-4 w-4 shrink-0 rounded object-cover select-none";
|
||||
"pointer-events-none h-4 w-4 shrink-0 object-cover select-none";
|
||||
const CHIP_NAME_CLASS = "pointer-events-none truncate";
|
||||
|
||||
/** Coarse-pointer (touch) device — no hover, on-screen keyboard. */
|
||||
@@ -496,7 +496,7 @@ function MentionDropdown({
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
onTouchMove={(e) => e.stopPropagation()}
|
||||
onScroll={() => (lastScrollTs.current = performance.now())}
|
||||
className="fixed z-[9999] w-64 max-w-[calc(100vw-2rem)] max-h-72 overflow-y-auto rounded-lg border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
|
||||
className="fixed z-[9999] w-64 max-w-[calc(100vw-2rem)] max-h-72 overflow-y-auto rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
|
||||
style={{
|
||||
pointerEvents: "auto",
|
||||
...(placement
|
||||
@@ -523,7 +523,7 @@ function MentionDropdown({
|
||||
key={item.token ?? `${item.label}-${i}`}
|
||||
type="button"
|
||||
className={twMerge(
|
||||
"flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-base-fg transition-colors cursor-pointer",
|
||||
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 text-sm text-base-fg transition-colors cursor-pointer",
|
||||
i === activeIndex ? "bg-ui-controls/80" : "hover:bg-ui-controls/60",
|
||||
)}
|
||||
// Selection happens on pointerup, not click: on mobile the
|
||||
@@ -570,7 +570,7 @@ function MentionDropdown({
|
||||
onClick={() => onSelect(item)}
|
||||
onMouseEnter={() => onHover(i)}
|
||||
>
|
||||
<div className="h-8 w-8 flex-shrink-0 overflow-hidden rounded-md border border-white/10 flex items-center justify-center bg-black/20">
|
||||
<div className="h-8 w-8 flex-shrink-0 overflow-hidden border border-white/10 flex items-center justify-center bg-black/20">
|
||||
{item.type === "character" && item.preview ? (
|
||||
<img
|
||||
src={item.preview}
|
||||
|
||||
@@ -388,7 +388,7 @@ export const PromptBox2D = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
"glass relative w-full rounded-[3px] p-4",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"rounded-t-none",
|
||||
@@ -433,7 +433,7 @@ export const PromptBox2D = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe your image..."
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -580,14 +580,14 @@ export const PromptBox2D = ({
|
||||
referenceImages={referenceImages}
|
||||
setReferenceImages={setReferenceImages}
|
||||
uploadImage={uploadImage as any}
|
||||
className="relative top-auto rounded-2xl"
|
||||
className="relative top-auto rounded-[3px]"
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<textarea
|
||||
placeholder="Describe your image..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -704,7 +704,7 @@ export const PromptBox3D = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
"glass relative w-full rounded-[3px] p-4",
|
||||
isPromptBoxFocused ? "!border !border-primary" : "",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
@@ -755,7 +755,7 @@ export const PromptBox3D = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe your image..."
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -1052,14 +1052,14 @@ export const PromptBox3D = ({
|
||||
referenceImages={referenceImages}
|
||||
setReferenceImages={setReferenceImages}
|
||||
uploadImage={uploadImage as any}
|
||||
className="relative top-auto rounded-2xl"
|
||||
className="relative top-auto rounded-[3px]"
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<textarea
|
||||
placeholder="Describe your image..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -544,7 +544,7 @@ export const PromptBoxAudio = ({
|
||||
<div className="relative z-20 flex flex-col">
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
"glass relative w-full rounded-[3px] p-4",
|
||||
isFocused
|
||||
? "ring-1 ring-primary border-primary"
|
||||
: "ring-1 ring-transparent",
|
||||
@@ -565,7 +565,7 @@ export const PromptBoxAudio = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe the music or sound you want..."
|
||||
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
|
||||
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -666,7 +666,7 @@ export const PromptBoxAudio = ({
|
||||
>
|
||||
<textarea
|
||||
placeholder="Describe the music or sound you want..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -330,7 +330,7 @@ export const PromptBoxEdit = ({
|
||||
{(supportsMaskedInpainting || isNanoBananaModel) && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-fit mx-auto rounded-xl px-2 py-2 flex items-center gap-3",
|
||||
"glass w-fit mx-auto rounded-[3px] px-2 py-2 flex items-center gap-3",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"mb-[72px]",
|
||||
@@ -346,7 +346,7 @@ export const PromptBoxEdit = ({
|
||||
<Tooltip content="Undo" position="top" delay={200}>
|
||||
<button
|
||||
onClick={onUndo}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-[3px] text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
disabled={!onUndo}
|
||||
>
|
||||
<RotateCcwIcon className="h-4 w-4" />
|
||||
@@ -355,7 +355,7 @@ export const PromptBoxEdit = ({
|
||||
<Tooltip content="Redo" position="top" delay={200}>
|
||||
<button
|
||||
onClick={onRedo}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-[3px] text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
disabled={!onRedo}
|
||||
>
|
||||
<Redo2Icon className="h-4 w-4" />
|
||||
@@ -383,7 +383,7 @@ export const PromptBoxEdit = ({
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-[860px] rounded-xl p-4",
|
||||
"glass relative w-[860px] rounded-[3px] p-4",
|
||||
isFocused && "ring-1 ring-primary border-primary",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
@@ -428,7 +428,7 @@ export const PromptBoxEdit = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Write what you want to change in your image and click generate..."
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
|
||||
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -583,14 +583,14 @@ export const PromptBoxEdit = ({
|
||||
referenceImages={referenceImages}
|
||||
setReferenceImages={setReferenceImages}
|
||||
uploadImage={uploadImage}
|
||||
className="relative top-auto rounded-2xl"
|
||||
className="relative top-auto rounded-[3px]"
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<textarea
|
||||
placeholder="Write what you want to change in your image and click generate..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class PromptBoxErrorBoundary extends Component<Props, State> {
|
||||
override render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-xl border border-red-500/30 bg-red-500/10 px-6 py-4 text-sm text-red-300">
|
||||
<div className="flex items-center justify-center rounded-[3px] border border-red-500/30 bg-red-500/10 px-6 py-4 text-sm text-red-300">
|
||||
<span>Something went wrong with the prompt box. </span>
|
||||
<button
|
||||
className="underline hover:text-red-200"
|
||||
|
||||
@@ -455,7 +455,7 @@ export const PromptBoxImage = ({
|
||||
<div className="relative z-20 flex flex-col">
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
"glass relative w-full rounded-[3px] p-4",
|
||||
isFocused
|
||||
? "ring-1 ring-primary border-primary"
|
||||
: "ring-1 ring-transparent",
|
||||
@@ -476,7 +476,7 @@ export const PromptBoxImage = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe what you want in the image..."
|
||||
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
|
||||
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -613,7 +613,7 @@ export const PromptBoxImage = ({
|
||||
>
|
||||
<textarea
|
||||
placeholder="Describe what you want in the image..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -1307,7 +1307,7 @@ export const PromptBoxVideo = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCharactersModalOpen(true)}
|
||||
className="flex h-9 items-center justify-center gap-1 rounded-lg border border-ui-controls-border bg-ui-controls px-3 text-sm font-medium text-base-fg transition-all duration-150 hover:bg-ui-controls/80 active:scale-95"
|
||||
className="flex h-9 items-center justify-center gap-1 rounded-[3px] border border-ui-controls-border bg-ui-controls px-3 text-sm font-medium text-base-fg transition-all duration-150 hover:bg-ui-controls/80 active:scale-95"
|
||||
>
|
||||
@Characters
|
||||
</button>
|
||||
@@ -1332,7 +1332,7 @@ export const PromptBoxVideo = ({
|
||||
<div className="relative z-20 flex flex-col gap-3">
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass relative w-full rounded-2xl p-4",
|
||||
"glass relative w-full rounded-[3px] p-4",
|
||||
isFocused
|
||||
? "ring-1 ring-primary border-primary"
|
||||
: "ring-1 ring-transparent",
|
||||
@@ -1347,7 +1347,7 @@ export const PromptBoxVideo = ({
|
||||
keyframeMode={!isReferenceMode}
|
||||
/>
|
||||
{selectedModel?.textToVideoSupported === false && (
|
||||
<div className="mb-2 flex items-center gap-1.5 rounded-md bg-ui-controls/60 px-2.5 py-1.5 text-xs text-base-fg/70">
|
||||
<div className="mb-2 flex items-center gap-1.5 bg-ui-controls/60 px-2.5 py-1.5 text-xs text-base-fg/70">
|
||||
<InfoIcon
|
||||
|
||||
className="h-3 w-3 shrink-0" />
|
||||
@@ -1374,7 +1374,7 @@ export const PromptBoxVideo = ({
|
||||
? "Use @Image1, @Video1, @Audio1... to reference uploads in prompt..."
|
||||
: "Describe what you want to happen in the video..."
|
||||
}
|
||||
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg"
|
||||
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const isSubmitCombo = enterToGenerate && !e.shiftKey;
|
||||
@@ -1397,7 +1397,7 @@ export const PromptBoxVideo = ({
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Describe what you want to happen in the video..."
|
||||
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
@@ -1638,13 +1638,13 @@ export const PromptBoxVideo = ({
|
||||
? "Use @Image1, @Video1, @Audio1... to reference uploads in prompt..."
|
||||
: "Describe what you want to happen in the video..."
|
||||
}
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg"
|
||||
style={{ resize: "none" }}
|
||||
/>
|
||||
) : (
|
||||
<textarea
|
||||
placeholder="Describe what you want to happen in the video..."
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { twMerge } from "tailwind-merge";
|
||||
// `buttonClassName` to PopoverMenu triggers (e.g. the generation count
|
||||
// picker) so they match this button.
|
||||
export const PROMPT_TOOLBAR_ICON_BUTTON_CLASSES =
|
||||
"flex h-9 w-9 items-center justify-center rounded-lg border-0 bg-transparent px-0 text-base-fg/50 shadow-none transition-colors hover:bg-base-fg/10 hover:text-base-fg/90";
|
||||
"flex h-9 w-9 items-center justify-center rounded-[3px] border-0 bg-transparent px-0 text-base-fg/50 shadow-none transition-colors hover:bg-base-fg/10 hover:text-base-fg/90";
|
||||
|
||||
interface PromptClearAllButtonProps {
|
||||
onClick: () => void;
|
||||
|
||||
@@ -242,7 +242,7 @@ export const AudioReferenceRow = forwardRef<
|
||||
type="button"
|
||||
onClick={() => audioInputRef.current?.click()}
|
||||
disabled={isUploadingAudio}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
{isUploadingAudio ? "Uploading…" : "Add audio"}
|
||||
@@ -252,7 +252,7 @@ export const AudioReferenceRow = forwardRef<
|
||||
type="button"
|
||||
onClick={onPickAudioFromLibrary}
|
||||
disabled={isUploadingAudio}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
>
|
||||
<FolderOpenIcon className="h-3 w-3" />
|
||||
From library
|
||||
@@ -270,7 +270,7 @@ export const AudioReferenceRow = forwardRef<
|
||||
{referenceImages.map((image) => (
|
||||
<div
|
||||
key={image.id}
|
||||
className="group relative h-9 w-9 overflow-hidden rounded-lg border border-ui-controls-border"
|
||||
className="group relative h-9 w-9 overflow-hidden rounded-[3px] border border-ui-controls-border"
|
||||
>
|
||||
<img
|
||||
src={image.url}
|
||||
@@ -294,7 +294,7 @@ export const AudioReferenceRow = forwardRef<
|
||||
type="button"
|
||||
onClick={() => imageInputRef.current?.click()}
|
||||
disabled={isUploadingImage}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
{isUploadingImage ? "Uploading…" : "Add image"}
|
||||
@@ -337,7 +337,7 @@ function AudioRefTile({
|
||||
}, [audio.url, isPlaying]);
|
||||
|
||||
return (
|
||||
<div className="group flex h-9 items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-2.5">
|
||||
<div className="group flex h-9 items-center gap-2 rounded-[3px] border border-ui-controls-border bg-ui-controls px-2.5">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={isPlaying ? "Stop" : "Play"}
|
||||
@@ -356,7 +356,7 @@ function AudioRefTile({
|
||||
type="button"
|
||||
aria-label="Remove audio"
|
||||
onClick={() => onRemove(audio.id)}
|
||||
className="flex h-4 w-4 items-center justify-center rounded text-base-fg/40 transition-colors hover:text-base-fg"
|
||||
className="flex h-4 w-4 items-center justify-center rounded-[3px] text-base-fg/40 transition-colors hover:text-base-fg"
|
||||
>
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
|
||||
@@ -64,7 +64,7 @@ export function AudioTuningPopover({
|
||||
key={hz}
|
||||
type="button"
|
||||
onClick={() => onSampleRateChange?.(hz)}
|
||||
className={`rounded-md border px-2 py-1 text-xs font-medium transition-colors ${
|
||||
className={`rounded-[3px] border px-2 py-1 text-xs font-medium transition-colors ${
|
||||
hz === sampleRateHz
|
||||
? "border-white/30 bg-white/15 text-base-fg"
|
||||
: "border-transparent bg-white/5 text-base-fg/60 hover:bg-white/10 hover:text-base-fg"
|
||||
|
||||
@@ -77,7 +77,7 @@ export function SoundsSettingsPopover({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onBpmChange?.(null)}
|
||||
className="rounded bg-white/5 px-1.5 py-0.5 text-[10px] font-medium text-base-fg/60 transition-colors hover:bg-white/10 hover:text-base-fg"
|
||||
className="rounded-[3px] bg-white/5 px-1.5 py-0.5 text-[10px] font-medium text-base-fg/60 transition-colors hover:bg-white/10 hover:text-base-fg"
|
||||
>
|
||||
Auto
|
||||
</button>
|
||||
@@ -107,7 +107,7 @@ export function SoundsSettingsPopover({
|
||||
type="button"
|
||||
title={entry.label}
|
||||
onClick={() => onMusicalKeyChange?.(entry.value)}
|
||||
className={`rounded-md border px-1.5 py-1 text-xs font-medium transition-colors ${
|
||||
className={`rounded-[3px] border px-1.5 py-1 text-xs font-medium transition-colors ${
|
||||
entry.value === musicalKey
|
||||
? "border-white/30 bg-white/15 text-base-fg"
|
||||
: "border-transparent bg-white/5 text-base-fg/60 hover:bg-white/10 hover:text-base-fg"
|
||||
|
||||
@@ -43,7 +43,7 @@ export function StylePromptRow({
|
||||
type="button"
|
||||
aria-label="Clear style"
|
||||
onClick={() => onChange("")}
|
||||
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-base-fg/40 transition-colors hover:bg-white/10 hover:text-base-fg"
|
||||
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] text-base-fg/40 transition-colors hover:bg-white/10 hover:text-base-fg"
|
||||
>
|
||||
<XIcon className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
@@ -88,7 +88,7 @@ export const DeckCard = ({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass group relative aspect-square w-14 shrink-0 overflow-hidden rounded-lg border-2 border-white/30 transition-all duration-200",
|
||||
"glass group relative aspect-square w-14 shrink-0 overflow-hidden rounded-[3px] border-2 border-white/30 transition-all duration-200",
|
||||
!item.uploading &&
|
||||
"cursor-pointer hover:border-white/80 hover:cursor-zoom-in",
|
||||
item.kind === "audio" && "hover:cursor-pointer",
|
||||
@@ -143,12 +143,12 @@ export const DeckCard = ({
|
||||
)}
|
||||
|
||||
{item.kind === "video" && (
|
||||
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center rounded bg-black/60 text-white">
|
||||
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
|
||||
<VideoIcon className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
{item.kind === "audio" && (
|
||||
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center rounded bg-black/60 text-white">
|
||||
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
|
||||
<MusicIcon className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
@@ -235,7 +235,7 @@ export const DeckAddMenu = ({
|
||||
key={action.key}
|
||||
type="button"
|
||||
onClick={action.onSelect}
|
||||
className="flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
|
||||
className="flex w-full items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-left text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
|
||||
>
|
||||
<DynamicIcon
|
||||
icon={
|
||||
@@ -274,13 +274,13 @@ export const DeckPreviewModal = ({ item, onClose }: DeckPreviewModalProps) => (
|
||||
src={item.previewUrl ?? item.url}
|
||||
controls
|
||||
autoPlay
|
||||
className="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
|
||||
className="max-h-[90vh] max-w-[90vw] object-contain"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={item.previewUrl ?? item.url}
|
||||
alt={item.name}
|
||||
className="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
|
||||
className="max-h-[90vh] max-w-[90vw] object-contain"
|
||||
/>
|
||||
))}
|
||||
</Modal>
|
||||
|
||||
@@ -142,7 +142,7 @@ export const DeckSlotCard = ({
|
||||
type="button"
|
||||
onClick={() => enabledActions[0]?.onSelect()}
|
||||
className={twMerge(
|
||||
"glass flex aspect-square w-14 flex-col items-center justify-center gap-0.5 rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:z-10 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25",
|
||||
"glass flex aspect-square w-14 flex-col items-center justify-center gap-0.5 rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:z-10 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25",
|
||||
tiltClass,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -252,7 +252,7 @@ export const ReferenceDeck = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddClick}
|
||||
className="glass flex aspect-square w-14 -rotate-6 flex-col items-center justify-center gap-0.5 rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25"
|
||||
className="glass flex aspect-square w-14 -rotate-6 flex-col items-center justify-center gap-0.5 rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25"
|
||||
>
|
||||
<PlusIcon className="text-lg opacity-80" />
|
||||
<span className="text-[9px] font-medium leading-none opacity-70">
|
||||
@@ -270,7 +270,7 @@ export const ReferenceDeck = ({
|
||||
const expandedPanel = (
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-max rounded-xl border border-white/10 p-2 shadow-2xl",
|
||||
"glass w-max rounded-[3px] border border-white/10 p-2 shadow-2xl",
|
||||
// In-flow (fullscreen modal): cap at the container width so the
|
||||
// cards wrap into rows instead of overflowing the modal.
|
||||
alwaysExpanded
|
||||
@@ -296,7 +296,7 @@ export const ReferenceDeck = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearAll}
|
||||
className="flex items-center gap-1 rounded px-1 py-0.5 text-[11px] text-base-fg/60 transition-colors hover:text-red-400"
|
||||
className="flex items-center gap-1 rounded-[3px] px-1 py-0.5 text-[11px] text-base-fg/60 transition-colors hover:text-red-400"
|
||||
>
|
||||
<Trash2Icon className="h-2.5 w-2.5" />
|
||||
Clear all
|
||||
@@ -362,7 +362,7 @@ export const ReferenceDeck = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddClick}
|
||||
className="glass flex aspect-square w-14 shrink-0 items-center justify-center rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all hover:bg-ui-controls/60 dark:border-white/25"
|
||||
className="glass flex aspect-square w-14 shrink-0 items-center justify-center rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all hover:bg-ui-controls/60 dark:border-white/25"
|
||||
>
|
||||
<PlusIcon className="text-xl opacity-80" />
|
||||
</button>,
|
||||
|
||||
@@ -444,7 +444,7 @@ export function PromptBoxDropOverlay({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"promptbox-drop-overlay pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed bg-[#161618]/85 backdrop-blur-sm",
|
||||
"promptbox-drop-overlay pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 rounded-[3px] border-2 border-dashed bg-[#161618]/85 backdrop-blur-sm",
|
||||
rejected ? "border-red-400/80" : "border-primary",
|
||||
)}
|
||||
>
|
||||
@@ -452,7 +452,7 @@ export function PromptBoxDropOverlay({
|
||||
{kinds.map((kind, i) => (
|
||||
<div
|
||||
key={kind.label}
|
||||
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
|
||||
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center bg-white/10 text-base-fg/90"
|
||||
style={{ animationDelay: `${i * 50}ms` }}
|
||||
>
|
||||
<DynamicIcon icon={kind.icon} className="text-sm" />
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Select = ({
|
||||
className,
|
||||
}: ListDropdownProps) => {
|
||||
const selectedOption = options.find(
|
||||
(option: SelectOption) => option.value === value
|
||||
(option: SelectOption) => option.value === value,
|
||||
) || { label: placeholder || "", value: "" };
|
||||
|
||||
return (
|
||||
@@ -38,7 +38,7 @@ export const Select = ({
|
||||
<>
|
||||
<ListboxButton
|
||||
id={id}
|
||||
className="relative h-10 w-full cursor-pointer rounded-none bg-ui-controls text-base-fg border border-ui-controls-border py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
|
||||
className="relative h-10 w-full cursor-pointer rounded-[3px] bg-ui-controls text-base-fg border border-ui-controls-border py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
|
||||
>
|
||||
<span
|
||||
className={twMerge("block truncate", !value && "opacity-50")}
|
||||
@@ -46,7 +46,7 @@ export const Select = ({
|
||||
{selectedOption.label}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
|
||||
<ChevronDownIcon aria-hidden="true" />
|
||||
<ChevronDownIcon aria-hidden="true" />
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
@@ -57,7 +57,7 @@ export const Select = ({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-none bg-ui-panel border border-ui-panel-border text-base focus:outline-none sm:text-sm">
|
||||
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-[3px] bg-ui-panel border border-ui-panel-border text-base focus:outline-none sm:text-sm">
|
||||
{options.map((option, itemIdx) => (
|
||||
<ListboxOption
|
||||
key={itemIdx}
|
||||
@@ -66,7 +66,7 @@ export const Select = ({
|
||||
"relative cursor-pointer select-none py-2 pl-7 pr-2 transition-colors duration-150 ease-in-out",
|
||||
!selected ? "text-base-fg/90" : "text-base-fg",
|
||||
selected && "bg-white/10",
|
||||
focus && "bg-white text-black"
|
||||
focus && "bg-white text-black",
|
||||
)
|
||||
}
|
||||
value={option.value}
|
||||
@@ -82,10 +82,7 @@ export const Select = ({
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2.5">
|
||||
<CheckIcon
|
||||
|
||||
aria-hidden="true"
|
||||
className="text-xs" />
|
||||
<CheckIcon aria-hidden="true" className="text-xs" />
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -9,13 +9,18 @@ interface SwitchProps {
|
||||
offClassName?: string;
|
||||
}
|
||||
|
||||
export function Switch({ enabled, setEnabled, className, offClassName }: SwitchProps) {
|
||||
export function Switch({
|
||||
enabled,
|
||||
setEnabled,
|
||||
className,
|
||||
offClassName,
|
||||
}: SwitchProps) {
|
||||
return (
|
||||
<HeadlessSwitch checked={enabled} onChange={setEnabled} as={Fragment}>
|
||||
{({ checked, disabled }) => (
|
||||
<button
|
||||
className={clsx(
|
||||
"group inline-flex h-6 w-11 items-center rounded-none border transition-colors",
|
||||
"group inline-flex h-6 w-11 items-center rounded-[3px] border transition-colors",
|
||||
checked
|
||||
? "border-white bg-white"
|
||||
: (offClassName ?? "border-ui-controls-border bg-transparent"),
|
||||
@@ -26,8 +31,10 @@ export function Switch({ enabled, setEnabled, className, offClassName }: SwitchP
|
||||
<span className="sr-only">Enable notifications</span>
|
||||
<span
|
||||
className={clsx(
|
||||
"size-4 rounded-none transition",
|
||||
checked ? "translate-x-[22px] bg-black" : "translate-x-1 bg-white/70",
|
||||
"size-4 rounded-[2px] transition",
|
||||
checked
|
||||
? "translate-x-[22px] bg-black"
|
||||
: "translate-x-1 bg-white/70",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -72,14 +72,14 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
<TabGroup selectedIndex={selectedIndex} onChange={handleTabChange}>
|
||||
<TabList
|
||||
className={twMerge(
|
||||
"glass glass-no-hover relative inline-flex min-w-fit overflow-x-auto rounded-none p-0.5 py-1 !shadow-none",
|
||||
"glass glass-no-hover relative inline-flex min-w-fit overflow-x-auto rounded-[3px] p-0.5 py-1 !shadow-none",
|
||||
listClassName,
|
||||
)}
|
||||
>
|
||||
{/* Animated indicator */}
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute top-1 z-10 h-[calc(100%-8px)] rounded-none bg-white transition-all duration-200 ease-in-out",
|
||||
"absolute top-1 z-10 h-[calc(100%-8px)] rounded-[2px] bg-white transition-all duration-200 ease-in-out",
|
||||
indicatorClassName,
|
||||
)}
|
||||
style={{
|
||||
@@ -99,7 +99,7 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
disabled={disabled}
|
||||
className={({ selected }) =>
|
||||
twMerge(
|
||||
"relative z-20 mx-0.5 min-w-max rounded-none border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
|
||||
"relative z-20 mx-0.5 min-w-max rounded-[2px] border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
|
||||
"focus-visible:outline-none focus-visible:ring-0",
|
||||
selected
|
||||
? twMerge("text-black", selectedTabClassName)
|
||||
|
||||
@@ -57,6 +57,9 @@ export function Toaster({
|
||||
<RHToaster
|
||||
position={position}
|
||||
toastOptions={{
|
||||
style: {
|
||||
borderRadius: "3px",
|
||||
},
|
||||
success: {
|
||||
style: {
|
||||
background: "#ffffff",
|
||||
|
||||
@@ -175,7 +175,8 @@ export const Tooltip = ({
|
||||
// in Chromium/WebKit (our glass surfaces use backdrop-blur), so a fixed
|
||||
// tooltip inside one would otherwise be positioned/clipped incorrectly.
|
||||
const backdropFilter =
|
||||
cs.backdropFilter || (cs as unknown as Record<string, string>)["webkitBackdropFilter"];
|
||||
cs.backdropFilter ||
|
||||
(cs as unknown as Record<string, string>)["webkitBackdropFilter"];
|
||||
if (
|
||||
cs.transform !== "none" ||
|
||||
cs.willChange === "transform" ||
|
||||
@@ -342,54 +343,54 @@ export const Tooltip = ({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={setTooltipRef}
|
||||
{...(portal ? { "data-modal-outside-safe": "" } : {})}
|
||||
onPointerEnter={() => interactive && setIsHoveringTooltip(true)}
|
||||
onPointerLeave={() => interactive && setIsHoveringTooltip(false)}
|
||||
onClick={() => {
|
||||
if (closeOnClick) {
|
||||
setIsShowing(false);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...getStyleForPosition(),
|
||||
transitionDelay: `${delay}ms`,
|
||||
transitionProperty: "opacity",
|
||||
transitionDuration: "200ms",
|
||||
transitionTimingFunction: "ease-out",
|
||||
zIndex,
|
||||
}}
|
||||
className={twMerge(
|
||||
isFixed ? "fixed" : "absolute",
|
||||
"w-max rounded-none bg-ui-panel border border-ui-panel-border",
|
||||
interactive
|
||||
? "pointer-events-auto p-3"
|
||||
: "px-2.5 py-1.5 font-mono text-[11px] font-medium uppercase tracking-[0.1em] pointer-events-none",
|
||||
"text-base-fg",
|
||||
className ? className : "",
|
||||
)}
|
||||
>
|
||||
{interactive ? (
|
||||
content
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
{content}
|
||||
{imageSrc && (
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt="tooltip"
|
||||
className="mb-1 aspect-square w-56 rounded-none"
|
||||
/>
|
||||
)}
|
||||
{description && (
|
||||
<p className="font-sans text-xs font-normal normal-case tracking-normal text-base-fg/80">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
ref={setTooltipRef}
|
||||
{...(portal ? { "data-modal-outside-safe": "" } : {})}
|
||||
onPointerEnter={() => interactive && setIsHoveringTooltip(true)}
|
||||
onPointerLeave={() => interactive && setIsHoveringTooltip(false)}
|
||||
onClick={() => {
|
||||
if (closeOnClick) {
|
||||
setIsShowing(false);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...getStyleForPosition(),
|
||||
transitionDelay: `${delay}ms`,
|
||||
transitionProperty: "opacity",
|
||||
transitionDuration: "200ms",
|
||||
transitionTimingFunction: "ease-out",
|
||||
zIndex,
|
||||
}}
|
||||
className={twMerge(
|
||||
isFixed ? "fixed" : "absolute",
|
||||
"w-max rounded-[3px] bg-ui-panel border border-ui-panel-border",
|
||||
interactive
|
||||
? "pointer-events-auto p-3"
|
||||
: "px-2.5 py-1.5 font-mono text-[11px] font-medium uppercase tracking-[0.1em] pointer-events-none",
|
||||
"text-base-fg",
|
||||
className ? className : "",
|
||||
)}
|
||||
>
|
||||
{interactive ? (
|
||||
content
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
{content}
|
||||
{imageSrc && (
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt="tooltip"
|
||||
className="mb-1 aspect-square w-56 rounded-none"
|
||||
/>
|
||||
)}
|
||||
{description && (
|
||||
<p className="font-sans text-xs font-normal normal-case tracking-normal text-base-fg/80">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user