Webapp stylings (#1916)
This commit is contained in:
1 parent
d2ad5820e0
commit
60bcfffc8d
156 files changed
+2512
-2753
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user