generated from nhcarrigan/template
feat: add built-in file editor with syntax highlighting #79
@@ -31,30 +31,101 @@
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onclick={onCancel}>
|
||||
<div class="dialog-overlay" onclick={onCancel}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="mx-4 w-full max-w-md rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-xl"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 class="mb-2 text-lg font-semibold text-gray-100">{title}</h2>
|
||||
<p class="mb-6 text-sm text-gray-300">{message}</p>
|
||||
<div class="dialog-content" onclick={(e) => e.stopPropagation()}>
|
||||
<h2 class="dialog-title">{title}</h2>
|
||||
<p class="dialog-message">{message}</p>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-md border border-gray-600 bg-gray-700 px-4 py-2 text-sm text-gray-200 hover:bg-gray-600"
|
||||
onclick={onCancel}
|
||||
>
|
||||
<div class="dialog-actions">
|
||||
<button class="btn-cancel" onclick={onCancel}>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md px-4 py-2 text-sm text-white {destructive
|
||||
? 'bg-red-600 hover:bg-red-700'
|
||||
: 'bg-pink-600 hover:bg-pink-700'}"
|
||||
onclick={onConfirm}
|
||||
>
|
||||
<button class="btn-confirm" class:destructive onclick={onConfirm}>
|
||||
{confirmText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
margin: 0 1rem;
|
||||
width: 100%;
|
||||
max-width: 28rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1.5rem;
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dialog-message {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
border-radius: 0.375rem;
|
||||
border: none;
|
||||
background-color: var(--accent-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-confirm:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.btn-confirm.destructive {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.btn-confirm.destructive:hover {
|
||||
background-color: #b91c1c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-50"
|
||||
class="menu-overlay"
|
||||
onclick={onClose}
|
||||
oncontextmenu={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -70,16 +70,9 @@
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="absolute z-50 min-w-[160px] rounded-md border border-gray-700 bg-gray-800 py-1 shadow-lg"
|
||||
style="left: {x}px; top: {y}px;"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
||||
onclick={handleNewFile}
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="menu-content" style="left: {x}px; top: {y}px;" onclick={(e) => e.stopPropagation()}>
|
||||
<button class="menu-item" onclick={handleNewFile}>
|
||||
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -90,11 +83,8 @@
|
||||
New File
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
||||
onclick={handleNewFolder}
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<button class="menu-item" onclick={handleNewFolder}>
|
||||
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -106,13 +96,10 @@
|
||||
</button>
|
||||
|
||||
{#if targetEntry}
|
||||
<div class="my-1 border-t border-gray-700"></div>
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<button
|
||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
||||
onclick={handleRename}
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<button class="menu-item" onclick={handleRename}>
|
||||
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -123,11 +110,8 @@
|
||||
Rename
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-red-400 hover:bg-gray-700"
|
||||
onclick={handleDelete}
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<button class="menu-item destructive" onclick={handleDelete}>
|
||||
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -140,3 +124,57 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
min-width: 160px;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 0.25rem 0;
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.menu-item.destructive {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
margin: 0.25rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
onCancel,
|
||||
}: Props = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally - intentionally capture initial value once
|
||||
let inputValue = $state(initialValue);
|
||||
let inputElement: HTMLInputElement | undefined = $state();
|
||||
|
||||
@@ -52,36 +51,123 @@
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onclick={onCancel}>
|
||||
<div class="dialog-overlay" onclick={onCancel}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="mx-4 w-full max-w-md rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-xl"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-100">{title}</h2>
|
||||
<div class="dialog-content" onclick={(e) => e.stopPropagation()}>
|
||||
<h2 class="dialog-title">{title}</h2>
|
||||
|
||||
<input
|
||||
bind:this={inputElement}
|
||||
bind:value={inputValue}
|
||||
type="text"
|
||||
{placeholder}
|
||||
class="mb-6 w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 text-sm text-gray-100 placeholder-gray-400 focus:border-pink-500 focus:outline-none focus:ring-1 focus:ring-pink-500"
|
||||
class="dialog-input"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-md border border-gray-600 bg-gray-700 px-4 py-2 text-sm text-gray-200 hover:bg-gray-600"
|
||||
onclick={onCancel}
|
||||
>
|
||||
<div class="dialog-actions">
|
||||
<button class="btn-cancel" onclick={onCancel}>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md bg-pink-600 px-4 py-2 text-sm text-white hover:bg-pink-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onclick={handleSubmit}
|
||||
disabled={!inputValue.trim()}
|
||||
>
|
||||
<button class="btn-confirm" onclick={handleSubmit} disabled={!inputValue.trim()}>
|
||||
{confirmText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
margin: 0 1rem;
|
||||
width: 100%;
|
||||
max-width: 28rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1.5rem;
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dialog-input {
|
||||
width: 100%;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-primary);
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.dialog-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dialog-input:focus {
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 1px var(--accent-primary);
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
border-radius: 0.375rem;
|
||||
border: none;
|
||||
background-color: var(--accent-primary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-confirm:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.btn-confirm:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user