Files
webref/frontend/src/lib/components/boards/CreateBoardModal.svelte
Danilo Reyes c52ac86739
Some checks failed
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 3s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 3s
CI/CD Pipeline / VM Test - performance (push) Successful in 2s
CI/CD Pipeline / VM Test - security (push) Successful in 2s
CI/CD Pipeline / Backend Linting (push) Successful in 2s
CI/CD Pipeline / Frontend Linting (push) Failing after 12s
CI/CD Pipeline / Nix Flake Check (push) Successful in 37s
CI/CD Pipeline / CI Summary (push) Failing after 0s
lib was accidentally being ignored
2025-11-02 12:23:46 -06:00

267 lines
5.4 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let initialTitle: string = '';
export let initialDescription: string = '';
let title = initialTitle;
let description = initialDescription;
let errors: Record<string, string> = {};
const dispatch = createEventDispatcher<{
create: { title: string; description?: string };
close: void;
}>();
function validate(): boolean {
errors = {};
if (!title.trim()) {
errors.title = 'Title is required';
} else if (title.length > 255) {
errors.title = 'Title must be 255 characters or less';
}
if (description.length > 1000) {
errors.description = 'Description must be 1000 characters or less';
}
return Object.keys(errors).length === 0;
}
function handleSubmit() {
if (!validate()) return;
dispatch('create', {
title: title.trim(),
description: description.trim() || undefined,
});
}
function handleClose() {
dispatch('close');
}
function handleBackdropClick(event: MouseEvent) {
if (event.target === event.currentTarget) {
handleClose();
}
}
</script>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<div
class="modal-backdrop"
on:click={handleBackdropClick}
on:keydown={(e) => e.key === 'Escape' && handleClose()}
role="dialog"
aria-modal="true"
tabindex="-1"
>
<div class="modal-content">
<header class="modal-header">
<h2>Create New Board</h2>
<button class="close-btn" on:click={handleClose} aria-label="Close">×</button>
</header>
<form on:submit|preventDefault={handleSubmit} class="modal-form">
<div class="form-group">
<label for="title">Board Title <span class="required">*</span></label>
<input
id="title"
type="text"
bind:value={title}
placeholder="e.g., Character Design References"
class:error={errors.title}
maxlength="255"
required
/>
{#if errors.title}
<span class="error-text">{errors.title}</span>
{/if}
</div>
<div class="form-group">
<label for="description">Description (optional)</label>
<textarea
id="description"
bind:value={description}
placeholder="Add a description for this board..."
rows="3"
maxlength="1000"
class:error={errors.description}
/>
{#if errors.description}
<span class="error-text">{errors.description}</span>
{:else}
<span class="help-text">{description.length}/1000 characters</span>
{/if}
</div>
<div class="modal-actions">
<button type="button" class="btn-secondary" on:click={handleClose}>Cancel</button>
<button type="submit" class="btn-primary">Create Board</button>
</div>
</form>
</div>
</div>
<style>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-content {
background: white;
border-radius: 16px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.modal-header h2 {
font-size: 1.5rem;
font-weight: 600;
color: #1f2937;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: #6b7280;
cursor: pointer;
padding: 0;
line-height: 1;
transition: color 0.2s;
}
.close-btn:hover {
color: #1f2937;
}
.modal-form {
padding: 1.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group:last-of-type {
margin-bottom: 2rem;
}
label {
display: block;
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
font-size: 0.95rem;
}
.required {
color: #ef4444;
}
input,
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 1rem;
font-family: inherit;
transition: all 0.2s;
}
input:focus,
textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
input.error,
textarea.error {
border-color: #ef4444;
}
textarea {
resize: vertical;
min-height: 80px;
}
.error-text {
display: block;
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.help-text {
display: block;
color: #6b7280;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.modal-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
}
.btn-primary,
.btn-secondary {
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
border: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: white;
color: #374151;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background: #f9fafb;
}
</style>