0462e3dc3f
Reorganizes control placement in the playground interfaces and improves form interactions for better UX, particularly on mobile devices. ## Key Changes - **AudioInterface & ImageInterface**: Moved "Clear" buttons from the top control bar into the action button group below the form inputs for better visual hierarchy and logical grouping - **ImageInterface**: - Added prompt clearing to the `clearImage()` function so the input field is reset when clearing generated images - Updated Clear button disabled state to also check if prompt is empty, allowing users to clear an empty prompt - Added responsive flex styling (`flex-1 md:flex-none`) to the Clear button for better mobile layout - **ExpandableTextarea**: - Imported `untrack` from Svelte to properly handle reactive dependencies - Wrapped `expandedValue.length` in `untrack()` to prevent unnecessary reactivity when setting cursor position - Improved button visibility on mobile by changing opacity from `opacity-0` to `opacity-60` with `md:opacity-0` breakpoint, making the expand button more discoverable on touch devices ## Implementation Details The `untrack()` usage in ExpandableTextarea ensures that reading the text length doesn't create a reactive dependency, preventing potential infinite loops while still allowing the effect to run when `isExpanded` changes.
122 lines
3.3 KiB
Svelte
122 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from "svelte";
|
|
import { Maximize2, X } from "lucide-svelte";
|
|
|
|
interface Props {
|
|
value: string;
|
|
placeholder?: string;
|
|
rows?: number;
|
|
disabled?: boolean;
|
|
onkeydown?: (event: KeyboardEvent) => void;
|
|
}
|
|
|
|
let {
|
|
value = $bindable(),
|
|
placeholder = "",
|
|
rows = 3,
|
|
disabled = false,
|
|
onkeydown,
|
|
}: Props = $props();
|
|
|
|
let isExpanded = $state(false);
|
|
let expandedValue = $state("");
|
|
let expandedTextarea: HTMLTextAreaElement | undefined = $state();
|
|
|
|
function openExpanded() {
|
|
expandedValue = value;
|
|
isExpanded = true;
|
|
}
|
|
|
|
function closeExpanded() {
|
|
isExpanded = false;
|
|
}
|
|
|
|
function saveExpanded() {
|
|
value = expandedValue;
|
|
isExpanded = false;
|
|
}
|
|
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
if (event.key === "Escape") {
|
|
closeExpanded();
|
|
}
|
|
}
|
|
|
|
// Focus the textarea when expanded view opens
|
|
$effect(() => {
|
|
if (isExpanded && expandedTextarea) {
|
|
expandedTextarea.focus();
|
|
const len = untrack(() => expandedValue.length);
|
|
expandedTextarea.setSelectionRange(len, len);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="flex-1 relative group flex items-stretch min-h-0">
|
|
<textarea
|
|
class="w-full px-3 py-2 pr-10 rounded border border-gray-200 dark:border-white/10 bg-surface focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary resize-none"
|
|
{placeholder}
|
|
{rows}
|
|
bind:value
|
|
{onkeydown}
|
|
{disabled}
|
|
></textarea>
|
|
<button
|
|
class="absolute top-2 right-2 p-1.5 rounded-lg opacity-60 md:opacity-0 group-hover:opacity-100 transition-opacity bg-surface/90 hover:bg-surface border border-gray-200 dark:border-white/10 shadow-sm"
|
|
onclick={openExpanded}
|
|
title="Expand to edit"
|
|
type="button"
|
|
{disabled}
|
|
>
|
|
<Maximize2 class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{#if isExpanded}
|
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
|
<div class="w-full max-w-4xl h-[80vh] flex flex-col bg-surface rounded-lg shadow-xl border border-gray-200 dark:border-white/10">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center p-4 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="font-medium">Edit Text</h3>
|
|
<button
|
|
class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-white/10"
|
|
onclick={closeExpanded}
|
|
title="Close"
|
|
type="button"
|
|
>
|
|
<X class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Textarea -->
|
|
<div class="flex-1 p-4">
|
|
<textarea
|
|
bind:this={expandedTextarea}
|
|
class="w-full h-full px-4 py-3 rounded border border-gray-200 dark:border-white/10 bg-card focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
placeholder={placeholder}
|
|
bind:value={expandedValue}
|
|
onkeydown={handleKeyDown}
|
|
></textarea>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="flex justify-end gap-2 p-4 border-t border-gray-200 dark:border-white/10">
|
|
<button
|
|
class="btn"
|
|
onclick={closeExpanded}
|
|
type="button"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="btn bg-primary text-btn-primary-text hover:opacity-90"
|
|
onclick={saveExpanded}
|
|
type="button"
|
|
>
|
|
Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|