Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion web/src/components/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@ import { Dialog as BaseDialog } from "@base-ui/react/dialog";
import { useMergedRef } from "@mantine/hooks";
import { type ComponentProps, useRef } from "react";

function DialogRoot({
onOpenChange,
...props
}: ComponentProps<typeof BaseDialog.Root>) {
return (
<BaseDialog.Root
{...props}
onOpenChange={(open, details) => {
const target = details.event.target;
const controlled =
target instanceof HTMLElement
? target.getAttribute("aria-controls")
: null;
// Escape belongs to the field's open suggestions before the writing dialog.
if (
!open &&
details.reason === "escape-key" &&
controlled &&
target instanceof HTMLElement &&
target.ownerDocument
.getElementById(controlled)
?.matches('[role="listbox"][data-mention-suggestions]')
) {
details.cancel();
return;
}
onOpenChange?.(open, details);
}}
/>
);
}

function DialogPopup({
initialFocus,
ref,
Expand All @@ -20,4 +52,4 @@ function DialogPopup({

// Base UI owns focus trapping and restoration. Only forms opt into field focus;
// other dialogs open on their container, without selecting an action.
export const Dialog = { ...BaseDialog, Popup: DialogPopup };
export const Dialog = { ...BaseDialog, Root: DialogRoot, Popup: DialogPopup };
48 changes: 21 additions & 27 deletions web/src/components/Forms.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,7 @@
margin-block-start: 2px;
}

.inlineComposer
:is(.composerHighlight, .composerMentionMeasure, .composerEditor textarea) {
.inlineComposer :is(.composerHighlight, .composerEditor textarea) {
padding-block-start: 12px;
line-height: 24px;
}
Expand All @@ -376,8 +375,7 @@
min-block-size: 52px;
}

.homepageComposer
:is(.composerHighlight, .composerMentionMeasure, .composerEditor textarea) {
.homepageComposer :is(.composerHighlight, .composerEditor textarea) {
line-height: 28px;
}

Expand Down Expand Up @@ -422,11 +420,17 @@
margin-block: -6px 12px;
}

.mentionPositioner {
z-index: max(92, calc(var(--layer-popover) + 2));
}

.mentionPositioner[data-anchor-hidden] {
visibility: hidden;
pointer-events: none;
}

.mentionSuggestions {
position: absolute;
z-index: var(--layer-popover);
inset-inline: 0;
inset-block-start: calc(100% - 0.5rem);
position: relative;
overflow: hidden;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-panel);
Expand All @@ -437,7 +441,8 @@
.mentionSuggestionsCaret {
inset-inline: auto;
inset-block-start: auto;
max-block-size: min(18rem, 42vh);
max-inline-size: calc(100vw - 16px);
max-block-size: min(18rem, 42vh, var(--available-height));
overflow-y: auto;
transform-origin: top left;
animation: mentionSuggestionsIn var(--motion-fast) var(--ease-out);
Expand Down Expand Up @@ -525,22 +530,16 @@
white-space: pre-wrap;
}

.composerMentionMeasure {
.mentionCaretMeasure {
position: absolute;
inset: 0;
z-index: 0;
overflow: visible;
padding: var(--space-2) 0 var(--space-3);
color: transparent;
font-size: 1.25rem;
line-height: 1.4;
overflow-wrap: break-word;
pointer-events: none;
visibility: hidden;
inset: 0 auto auto 0;
box-sizing: border-box;
white-space: pre-wrap;
visibility: hidden;
pointer-events: none;
}

.composerMentionMeasure span {
.mentionCaretMeasure span {
display: inline-block;
inline-size: 1px;
block-size: 1em;
Expand Down Expand Up @@ -582,8 +581,7 @@
outline: none;
}

.pageComposer
:is(.composerHighlight, .composerMentionMeasure, .composerEditor textarea) {
.pageComposer :is(.composerHighlight, .composerEditor textarea) {
padding-block: 14px 10px;
line-height: 24px;
}
Expand Down Expand Up @@ -1409,10 +1407,6 @@
font-size: 1.125rem;
}

.composerMentionMeasure {
font-size: 1.125rem;
}

.pageComposer .composerForm textarea {
min-block-size: 7rem;
}
Expand Down
42 changes: 41 additions & 1 deletion web/src/features/comments/CommentComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,21 @@ export function CommentForm({
aria-describedby={`${formId}-guidance ${formId}-error`}
disabled={pending}
id={`${formId}-content`}
aria-autocomplete="list"
aria-controls={
mention && mentionItems.length
? `${formId}-mentions`
: undefined
}
aria-activedescendant={
mention && mentionItems[mentionActive]
? `${formId}-mentions-${mentionItems[mentionActive].userId}`
: undefined
}
onBlur={() => {
setMention(null);
setMentionItems([]);
}}
onChange={(event) => {
setContent(event.target.value);
setMention(
Expand All @@ -698,8 +713,24 @@ export function CommentForm({
);
setError(null);
}}
onSelect={(event) => {
const field = event.currentTarget;
setMention(
field.selectionStart === field.selectionEnd
? activeMention(field.value, field.selectionStart)
: null,
);
}}
onKeyDown={(event) => {
if (mentionItems.length > 0 && mention) {
if (event.nativeEvent.isComposing) return;
if (
mentionItems.length > 0 &&
mention &&
!event.shiftKey &&
!event.altKey &&
!event.metaKey &&
!event.ctrlKey
) {
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
event.preventDefault();
setMentionActive(
Expand Down Expand Up @@ -757,9 +788,18 @@ export function CommentForm({
) : null}
</div>
<MentionSuggestions
id={`${formId}-mentions`}
active={mentionActive}
items={mentionItems}
onPick={pickMention}
content={content}
mention={mention}
textareaRef={textareaRef}
enabled={active && !pending && commentsEnabled}
onDismiss={() => {
setMention(null);
setMentionItems([]);
}}
/>
<div className={styles.formMeta} id={`${formId}-guidance`}>
<span className={styles.srOnly}>
Expand Down
Loading
Loading