/**
* Shared task-modal pieces: the overlay shell (backdrop, form, title, error,
* footer) and the title/description/prompt field trio used by both the
* NewTaskModal and the EditTaskModal. State stays in the owning modal; these
* are controlled components.
*/
import type { ReactNode } from 'react'
import { TAG_NAME_MAX_LENGTH, TAG_PROMPT_MAX_LENGTH, TASK_TAG_LIMIT, normalizeTags, type TaskTag } from '../../core/tasks.ts'
import { t } from '../locales.ts'
import css from '../board.module.css'
/** DOM id shared by the tag-name inputs and their datalist (one board at a time). */
const TAG_NAME_LIST_ID = 'dsh-task-board-tag-names'
/** Modal overlay: closes on backdrop press, submits through the form. */
export function ModalShell({
ariaLabel,
title,
error,
pending,
submitLabel,
onSubmit,
onClose,
secondaryAction,
children,
}: {
ariaLabel: string
title: string
error: string | undefined
pending: boolean
submitLabel: string
onSubmit: () => void
onClose: () => void
/** Optional second action beside the primary submit (e.g. create and run). */
secondaryAction?: { label: string; onSubmit: () => void }
children: ReactNode
}) {
return (
{ if (event.target === event.currentTarget) onClose() }}>
)
}
/** Title + description + prompt fields shared by the new and edit task forms. */
export function TaskContentFields({
title,
description,
prompt,
onTitleChange,
onDescriptionChange,
onPromptChange,
}: {
title: string
description: string
prompt: string
/** Receives the new title; the owner also clears its error state. */
onTitleChange: (value: string) => void
onDescriptionChange: (value: string) => void
onPromptChange: (value: string) => void
}) {
return (
<>
>
)
}
/**
* Task labels (issue #1521): one row per label holding the badge name and an
* optional execution hint. The name inputs offer the labels already used on the
* board through a datalist, and picking one adopts its hint when the row has
* none — so a business line is defined once and reused by every later task.
*/
export function TaskTagFields({
tags,
knownTags,
onChange,
}: {
tags: TaskTag[]
/** Labels already carried elsewhere on the board. */
knownTags: TaskTag[]
onChange: (tags: TaskTag[]) => void
}) {
const update = (index: number, patch: Partial): void => {
onChange(tags.map((tag, position) => (position === index ? { ...tag, ...patch } : tag)))
}
return (
)
}
/**
* Clean a tag list via normalizeTags: trim, drop blanks and duplicates, cap
* lengths and count, so the wire always carries a valid tag list.
*/
export function cleanTags(tags: readonly TaskTag[]): TaskTag[] {
return normalizeTags(tags) ?? []
}