diff options
Diffstat (limited to 'packages/ui/src/components/line-comment.tsx')
| -rw-r--r-- | packages/ui/src/components/line-comment.tsx | 192 |
1 files changed, 160 insertions, 32 deletions
diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx index 81e4759b0..6a247990b 100644 --- a/packages/ui/src/components/line-comment.tsx +++ b/packages/ui/src/components/line-comment.tsx @@ -1,52 +1,121 @@ -import { onMount, Show, splitProps, type JSX } from "solid-js" +import { createEffect, createSignal, onMount, Show, splitProps, type JSX } from "solid-js" import { Button } from "./button" import { Icon } from "./icon" +import { installLineCommentStyles } from "./line-comment-styles" import { useI18n } from "../context/i18n" -export type LineCommentVariant = "default" | "editor" +installLineCommentStyles() + +export type LineCommentVariant = "default" | "editor" | "add" + +function InlineGlyph(props: { icon: "comment" | "plus" }) { + return ( + <svg data-slot="line-comment-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true"> + <Show + when={props.icon === "comment"} + fallback={ + <path + d="M10 5.41699V10.0003M10 10.0003V14.5837M10 10.0003H5.4165M10 10.0003H14.5832" + stroke="currentColor" + stroke-linecap="square" + /> + } + > + <path d="M16.25 3.75H3.75V16.25L6.875 14.4643H16.25V3.75Z" stroke="currentColor" stroke-linecap="square" /> + </Show> + </svg> + ) +} export type LineCommentAnchorProps = { id?: string top?: number + inline?: boolean + hideButton?: boolean open: boolean variant?: LineCommentVariant + icon?: "comment" | "plus" + buttonLabel?: string onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> onMouseEnter?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> onPopoverFocusOut?: JSX.EventHandlerUnion<HTMLDivElement, FocusEvent> class?: string popoverClass?: string - children: JSX.Element + children?: JSX.Element } export const LineCommentAnchor = (props: LineCommentAnchorProps) => { - const hidden = () => props.top === undefined + const hidden = () => !props.inline && props.top === undefined const variant = () => props.variant ?? "default" + const icon = () => props.icon ?? "comment" + const inlineBody = () => props.inline && props.hideButton return ( <div data-component="line-comment" + data-prevent-autofocus="" data-variant={variant()} data-comment-id={props.id} data-open={props.open ? "" : undefined} + data-inline={props.inline ? "" : undefined} classList={{ [props.class ?? ""]: !!props.class, }} - style={{ - top: `${props.top ?? 0}px`, - opacity: hidden() ? 0 : 1, - "pointer-events": hidden() ? "none" : "auto", - }} + style={ + props.inline + ? undefined + : { + top: `${props.top ?? 0}px`, + opacity: hidden() ? 0 : 1, + "pointer-events": hidden() ? "none" : "auto", + } + } > - <button type="button" data-slot="line-comment-button" onClick={props.onClick} onMouseEnter={props.onMouseEnter}> - <Icon name="comment" size="small" /> - </button> - <Show when={props.open}> + <Show + when={inlineBody()} + fallback={ + <> + <button + type="button" + aria-label={props.buttonLabel} + data-slot="line-comment-button" + on:mousedown={(e) => e.stopPropagation()} + on:mouseup={(e) => e.stopPropagation()} + on:click={props.onClick as any} + on:mouseenter={props.onMouseEnter as any} + > + <Show + when={props.inline} + fallback={<Icon name={icon() === "plus" ? "plus-small" : "comment"} size="small" />} + > + <InlineGlyph icon={icon()} /> + </Show> + </button> + <Show when={props.open}> + <div + data-slot="line-comment-popover" + classList={{ + [props.popoverClass ?? ""]: !!props.popoverClass, + }} + on:mousedown={(e) => e.stopPropagation()} + on:focusout={props.onPopoverFocusOut as any} + > + {props.children} + </div> + </Show> + </> + } + > <div data-slot="line-comment-popover" + data-inline-body="" classList={{ [props.popoverClass ?? ""]: !!props.popoverClass, }} - onFocusOut={props.onPopoverFocusOut} + on:mousedown={(e) => e.stopPropagation()} + on:click={props.onClick as any} + on:mouseenter={props.onMouseEnter as any} + on:focusout={props.onPopoverFocusOut as any} > {props.children} </div> @@ -58,16 +127,22 @@ export const LineCommentAnchor = (props: LineCommentAnchorProps) => { export type LineCommentProps = Omit<LineCommentAnchorProps, "children" | "variant"> & { comment: JSX.Element selection: JSX.Element + actions?: JSX.Element } export const LineComment = (props: LineCommentProps) => { const i18n = useI18n() - const [split, rest] = splitProps(props, ["comment", "selection"]) + const [split, rest] = splitProps(props, ["comment", "selection", "actions"]) return ( - <LineCommentAnchor {...rest} variant="default"> + <LineCommentAnchor {...rest} variant="default" hideButton={props.inline}> <div data-slot="line-comment-content"> - <div data-slot="line-comment-text">{split.comment}</div> + <div data-slot="line-comment-head"> + <div data-slot="line-comment-text">{split.comment}</div> + <Show when={split.actions}> + <div data-slot="line-comment-tools">{split.actions}</div> + </Show> + </div> <div data-slot="line-comment-label"> {i18n.t("ui.lineComment.label.prefix")} {split.selection} @@ -78,6 +153,25 @@ export const LineComment = (props: LineCommentProps) => { ) } +export type LineCommentAddProps = Omit<LineCommentAnchorProps, "children" | "variant" | "open" | "icon"> & { + label?: string +} + +export const LineCommentAdd = (props: LineCommentAddProps) => { + const [split, rest] = splitProps(props, ["label"]) + const i18n = useI18n() + + return ( + <LineCommentAnchor + {...rest} + open={false} + variant="add" + icon="plus" + buttonLabel={split.label ?? i18n.t("ui.lineComment.submit")} + /> + ) +} + export type LineCommentEditorProps = Omit<LineCommentAnchorProps, "children" | "open" | "variant" | "onClick"> & { value: string selection: JSX.Element @@ -109,11 +203,16 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => { const refs = { textarea: undefined as HTMLTextAreaElement | undefined, } + const [text, setText] = createSignal(split.value) const focus = () => refs.textarea?.focus() + createEffect(() => { + setText(split.value) + }) + const submit = () => { - const value = split.value.trim() + const value = text().trim() if (!value) return split.onSubmit(value) } @@ -124,7 +223,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => { }) return ( - <LineCommentAnchor {...rest} open={true} variant="editor" onClick={() => focus()}> + <LineCommentAnchor {...rest} open={true} variant="editor" hideButton={props.inline} onClick={() => focus()}> <div data-slot="line-comment-editor"> <textarea ref={(el) => { @@ -133,19 +232,23 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => { data-slot="line-comment-textarea" rows={split.rows ?? 3} placeholder={split.placeholder ?? i18n.t("ui.lineComment.placeholder")} - value={split.value} - onInput={(e) => split.onInput(e.currentTarget.value)} - onKeyDown={(e) => { + value={text()} + on:input={(e) => { + const value = (e.currentTarget as HTMLTextAreaElement).value + setText(value) + split.onInput(value) + }} + on:keydown={(e) => { + const event = e as KeyboardEvent + event.stopPropagation() if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() + event.preventDefault() split.onCancel() return } if (e.key !== "Enter") return if (e.shiftKey) return - e.preventDefault() - e.stopPropagation() + event.preventDefault() submit() }} /> @@ -155,12 +258,37 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => { {split.selection} {i18n.t("ui.lineComment.editorLabel.suffix")} </div> - <Button size="small" variant="ghost" onClick={split.onCancel}> - {split.cancelLabel ?? i18n.t("ui.common.cancel")} - </Button> - <Button size="small" variant="primary" disabled={split.value.trim().length === 0} onClick={submit}> - {split.submitLabel ?? i18n.t("ui.lineComment.submit")} - </Button> + <Show + when={!props.inline} + fallback={ + <> + <button + type="button" + data-slot="line-comment-action" + data-variant="ghost" + on:click={split.onCancel as any} + > + {split.cancelLabel ?? i18n.t("ui.common.cancel")} + </button> + <button + type="button" + data-slot="line-comment-action" + data-variant="primary" + disabled={text().trim().length === 0} + on:click={submit as any} + > + {split.submitLabel ?? i18n.t("ui.lineComment.submit")} + </button> + </> + } + > + <Button size="small" variant="ghost" onClick={split.onCancel}> + {split.cancelLabel ?? i18n.t("ui.common.cancel")} + </Button> + <Button size="small" variant="primary" disabled={text().trim().length === 0} onClick={submit}> + {split.submitLabel ?? i18n.t("ui.lineComment.submit")} + </Button> + </Show> </div> </div> </LineCommentAnchor> |
