diff --git a/web/components/basic_modals.tsx b/web/components/basic_modals.tsx index 150b1ce8..79db6ca2 100644 --- a/web/components/basic_modals.tsx +++ b/web/components/basic_modals.tsx @@ -1,6 +1,7 @@ import { CompletionContext, CompletionResult } from "@codemirror/autocomplete"; -import { useRef, useState } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks"; import { MiniEditor } from "./mini_editor.tsx"; +import { ComponentChildren, Ref } from "preact"; export function Prompt({ message, @@ -19,7 +20,11 @@ export function Prompt({ }) { const [text, setText] = useState(defaultValue || ""); const returnEl = ( -
+ { + callback(); + }} + >
- - +
+ + +
-
+ ); return returnEl; @@ -73,49 +81,98 @@ export function Confirm({ okButtonRef.current?.focus(); }); const returnEl = ( -
-
-
{ - e.stopPropagation(); - e.preventDefault(); - switch (e.key) { - case "Enter": - callback(true); - break; - case "Escape": - callback(false); - break; - } - }} - > - -
- - -
+ { + callback(false); + }} + > +
+ +
+ +
-
+ ); return returnEl; } + +export function Button({ + children, + primary, + onActivate, + buttonRef, +}: { + children: ComponentChildren; + primary?: boolean; + onActivate: () => void; + buttonRef?: Ref; +}) { + return ( + + ); +} + +export function AlwaysShownModal({ + children, + onCancel, +}: { + children: ComponentChildren; + onCancel?: () => void; +}) { + const dialogRef = useRef(null); + + useEffect(() => { + dialogRef.current?.showModal(); + }, []); + + return ( + { + e.preventDefault(); + onCancel?.(); + }} + onKeyDown={(e) => { + e.stopPropagation(); + }} + ref={dialogRef} + > + {children} + + ); +} diff --git a/web/components/filter.tsx b/web/components/filter.tsx index 30a1d5ef..e225722e 100644 --- a/web/components/filter.tsx +++ b/web/components/filter.tsx @@ -6,6 +6,7 @@ import { FilterOption } from "$lib/web.ts"; import { MiniEditor } from "./mini_editor.tsx"; import { fuzzySearchAndSort } from "../fuse_search.ts"; import { deepEqual } from "../../plug-api/lib/json.ts"; +import { AlwaysShownModal } from "./basic_modals.tsx"; export function FilterList({ placeholder, @@ -93,7 +94,11 @@ export function FilterList({ }, []); const returnEl = ( -
+ { + onSelect(undefined); + }} + >
{ @@ -143,7 +148,9 @@ export function FilterList({ setSelectionOption(Math.max(0, selectedOption - 5)); return true; case "PageDown": - setSelectionOption(Math.max(0, selectedOption + 5)); + setSelectionOption( + Math.min(matchingOptions.length - 1, selectedOption + 5), + ); return true; case "Home": setSelectionOption(0); @@ -170,7 +177,7 @@ export function FilterList({ dangerouslySetInnerHTML={{ __html: helpText }} >
-
+
{matchingOptions && matchingOptions.length > 0 ? matchingOptions.map((option, idx) => (
-
+ ); useEffect(() => { diff --git a/web/components/mini_editor.tsx b/web/components/mini_editor.tsx index 61295200..7a5e2b45 100644 --- a/web/components/mini_editor.tsx +++ b/web/components/mini_editor.tsx @@ -64,19 +64,24 @@ export function MiniEditor( const callbacksRef = useRef(); useEffect(() => { - if (editorDiv.current) { + const currentEditorDiv = editorDiv.current; + if (currentEditorDiv) { // console.log("Creating editor view"); const editorView = new EditorView({ state: buildEditorState(), - parent: editorDiv.current!, + parent: currentEditorDiv, }); editorViewRef.current = editorView; + const focusEditorView = editorView.focus.bind(editorView); + currentEditorDiv.addEventListener("focusin", focusEditorView); + if (focus) { editorView.focus(); } return () => { + currentEditorDiv.removeEventListener("focusin", focusEditorView); if (editorViewRef.current) { editorViewRef.current.destroy(); } @@ -107,37 +112,27 @@ export function MiniEditor( } }, [text, vimMode]); - useEffect(() => { - // So, for some reason, CM doesn't propagate the keydown event, therefore we'll capture it here - // And check if it's the same editor element - function onKeyDown(e: KeyboardEvent) { - const parent = (e.target as any).parentElement.parentElement; - if (parent !== editorViewRef.current?.dom) { - // Different editor element - return; - } - let stopPropagation = false; - if (callbacksRef.current!.onKeyDown) { - stopPropagation = callbacksRef.current!.onKeyDown( - editorViewRef.current!, - e, - ); - } - if (stopPropagation) { - e.preventDefault(); - e.stopPropagation(); - } - } - document.addEventListener("keydown", onKeyDown); - - return () => { - document.removeEventListener("keydown", onKeyDown); - }; - }, []); - let onBlurred = false, onEntered = false; - return
; + return ( +
{ + let stopPropagation = false; + if (callbacksRef.current!.onKeyDown) { + stopPropagation = callbacksRef.current!.onKeyDown( + editorViewRef.current!, + e, + ); + } + if (stopPropagation) { + e.preventDefault(); + e.stopPropagation(); + } + }} + ref={editorDiv} + /> + ); function buildEditorState() { // When vim mode is active, we need for CM to have created the new state @@ -262,12 +257,6 @@ export function MiniEditor( // Reset the state view.setState(buildEditorState()); }); - } else if (focus) { - // console.log("BLURRING WHILE KEEPING FOCUSE"); - // Automatically refocus blurred - if (editorViewRef.current) { - editorViewRef.current.focus(); - } } // Event may occur again in 500ms setTimeout(() => { diff --git a/web/styles/colors.scss b/web/styles/colors.scss index 393961e1..c107bbfb 100644 --- a/web/styles/colors.scss +++ b/web/styles/colors.scss @@ -80,6 +80,39 @@ color: var(--action-button-hover-color); } +.sb-button, .sb-button-primary { + --color: var(--button-color); + --background-color: var(--button-background-color); + --hover-background-color: var(--button-hover-background-color); + --border-color: var(--button-border-color); + + &.sb-button-primary { + --color: var(--primary-button-color); + --background-color: var(--primary-button-background-color); + --hover-background-color: var(--primary-button-hover-background-color); + --border-color: var(--primary-button-border-color); + } + + background-color: var(--background-color); + color: var(--color); + + box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.05); + + border: 1px solid var(--border-color); + border-radius: 0.5em; + padding: 0.2em 0.5em; + font-size: 0.9em; + + &:hover { + background-color: var(--hover-background-color); + } + + &:focus { + outline: 2px solid var(--color); + outline-offset: -3px; + } +} + /* Modal boxes */ .sb-modal-box { color: var(--modal-color); diff --git a/web/styles/modals.scss b/web/styles/modals.scss index d13ed476..5f0b1f25 100644 --- a/web/styles/modals.scss +++ b/web/styles/modals.scss @@ -1,11 +1,6 @@ .sb-modal-box { - position: absolute; - // At the toppest of the toppest - z-index: 1000; - - top: 60px; - left: 50%; - transform: translateX(-50%); + margin-top: 60px; + padding: 0; width: 700px; max-width: 90%; @@ -27,6 +22,11 @@ label { margin: 3px; } + + .sb-mini-editor { + flex-grow: 1; + min-width: 0; + } } .sb-prompt { @@ -37,11 +37,20 @@ } .sb-mini-editor { + background-color: var(--text-field-background-color); margin: 10px 0; - width: 100%; + padding: 0.2em 0.5em; border: 0; + border-radius: 0.5em; outline: none; } + + .sb-prompt-buttons { + display: flex; + justify-content: flex-end; + margin-top: 10px; + gap: 10px; + } } .sb-help-text { @@ -81,4 +90,4 @@ padding-bottom: 3px; border-radius: 5px; } -} \ No newline at end of file +} diff --git a/web/styles/theme.scss b/web/styles/theme.scss index fcb1c161..919eb9fc 100644 --- a/web/styles/theme.scss +++ b/web/styles/theme.scss @@ -1,5 +1,7 @@ html { --ui-accent-color: #464cfc; + --ui-accent-text-color: var(--ui-accent-color); + --ui-accent-contrast-color: #eee; --highlight-color: rgba(255, 255, 0, 0.5); --link-color: #0330cb; --link-missing-color: #9e4705; @@ -29,11 +31,11 @@ html { --modal-color: inherit; --modal-background-color: #fff; --modal-border-color: rgb(108, 108, 108); - --modal-header-label-color: var(--ui-accent-color); + --modal-header-label-color: var(--ui-accent-text-color); --modal-help-background-color: #eee; --modal-help-color: #555; --modal-selected-option-background-color: var(--ui-accent-color); - --modal-selected-option-color: #eee; + --modal-selected-option-color: var(--ui-accent-contrast-color); --modal-hint-background-color: #212476; --modal-hint-color: #eee; --modal-description-color: #aaa; @@ -43,6 +45,17 @@ html { --notification-info-background-color: rgb(187, 221, 247); --notification-error-background-color: rgb(255, 84, 84); + --button-background-color: #eee; + --button-hover-background-color: inherit; + --button-color: black; + --button-border-color: #6c6c6c; + --primary-button-background-color: var(--ui-accent-color); + --primary-button-hover-background-color: color-mix(in srgb, var(--ui-accent-color), black 35%); + --primary-button-color: var(--ui-accent-contrast-color); + --primary-button-border-color: transparent; + + --text-field-background-color: var(--button-background-color); + --action-button-background-color: transparent; --action-button-color: #292929; --action-button-hover-color: #0772be; @@ -116,7 +129,11 @@ html { } html[data-theme="dark"] { + color-scheme: dark; + --ui-accent-color: #464cfc; + --ui-accent-text-color: var(--ui-accent-color); + --ui-accent-text-color: color-mix(in srgb, var(--ui-accent-color), white 50%); --highlight-color: rgba(255, 255, 0, 0.5); --link-color: #7e99fc; --link-missing-color: #9e4705; @@ -146,7 +163,7 @@ html[data-theme="dark"] { --modal-color: #ccc; --modal-background-color: #262626; --modal-border-color: #6c6c6c; - --modal-header-label-color: var(--ui-accent-color); + --modal-header-label-color: var(--ui-accent-text-color); --modal-help-background-color: #333; --modal-help-color: #ccc; --modal-selected-option-background-color: var(--ui-accent-color); @@ -160,6 +177,17 @@ html[data-theme="dark"] { --notification-info-background-color: #1b76bb; --notification-error-background-color: #a32121; + --button-background-color: #555; + --button-hover-background-color: #777; + --button-color: white; + --button-border-color: #666; + --primary-button-background-color: var(--ui-accent-color); + --primary-button-hover-background-color: color-mix(in srgb, var(--ui-accent-color), black 35%); + --primary-button-color: var(--ui-accent-contrast-color); + --primary-button-border-color: transparent; + + --text-field-background-color: var(--button-background-color); + --action-button-background-color: transparent; --action-button-color: #adadad; --action-button-hover-color: #37a1ed;