From 1ee2475d4ab854f52d973d37c15dc39c9000042a Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Wed, 20 May 2026 13:43:00 +0200 Subject: [PATCH 01/13] Refactor input components and update styles across various modals and forms with different theme colors --- src/main/frontend/app/app.css | 52 +++++++++++++++++++ .../datamapper/forms/add-mapping-form.tsx | 4 +- .../file-structure/editor-file-structure.tsx | 2 +- .../file-structure/inline-rename-input.tsx | 6 ++- .../file-structure/studio-file-structure.tsx | 4 +- .../flow/add-subcomponent-modal.tsx | 13 ++--- .../components/flow/canvas-context-menu.tsx | 4 +- .../app/components/flow/create-node-modal.tsx | 13 ++--- .../app/components/git/git-changes.tsx | 4 +- .../app/components/git/git-commit-box.tsx | 2 +- .../app/components/git/git-toolbar.tsx | 4 +- .../frontend/app/components/inputs/button.tsx | 2 +- .../app/components/inputs/close-button.tsx | 17 ++++++ .../app/components/inputs/dropdown.tsx | 4 +- .../frontend/app/components/inputs/input.tsx | 2 +- .../app/components/loading-spinner.tsx | 2 +- .../app/components/navbar/navbar-link.tsx | 9 +++- .../sidebars-layout/sidebar-close.tsx | 2 +- .../sidebars-layout/sidebar-layout.tsx | 4 +- src/main/frontend/app/components/tabs/tab.tsx | 10 +--- src/main/frontend/app/root.tsx | 20 ++++++- .../add-configuration-modal.tsx | 4 +- .../configurations/add-configuration-tile.tsx | 2 +- .../configuration-file-tile.tsx | 4 +- .../configurations/configuration-overview.tsx | 4 +- .../frontend/app/routes/editor/editor.tsx | 4 +- .../frontend/app/routes/help/help-image.tsx | 2 +- .../clone-configuration-modal.tsx | 12 ++--- .../projectlanding/configuration-row.tsx | 4 +- .../new-configuration-modal.tsx | 9 ++-- .../routes/projectlanding/project-landing.tsx | 18 +++---- .../settings/pages/project-settings.tsx | 2 +- .../app/routes/studio/canvas/flow.tsx | 2 +- .../components/deprecated-popover.tsx | 2 +- .../components/missing-requirements.tsx | 2 +- .../studio/canvas/nodetypes/frank-node.tsx | 4 +- .../studio/context/context-input-field.tsx | 7 +-- .../routes/studio/context/context-input.tsx | 2 +- .../context/deprecated-list-popover.tsx | 2 +- .../studio/context/element-hover-card.tsx | 2 +- .../routes/studio/context/group-context.tsx | 5 +- .../routes/studio/context/sorted-elements.tsx | 4 +- src/main/frontend/app/stores/flow-store.ts | 2 +- src/main/frontend/icons/solar/Pen.svg | 4 +- src/main/frontend/styles/markdown.css | 16 +++--- 45 files changed, 188 insertions(+), 111 deletions(-) create mode 100644 src/main/frontend/app/components/inputs/close-button.tsx diff --git a/src/main/frontend/app/app.css b/src/main/frontend/app/app.css index 39c7b627..cbdfc733 100644 --- a/src/main/frontend/app/app.css +++ b/src/main/frontend/app/app.css @@ -233,6 +233,34 @@ body { color: #808080 !important; } +.react-flow__panel.react-flow__controls { + background-color: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 6px; + box-shadow: none; + overflow: hidden; +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button { + background-color: var(--color-background); + border-bottom: 1px solid var(--color-border); + fill: var(--color-foreground); +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button:last-child { + border-bottom: none; +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button:hover { + background-color: var(--color-hover); +} + +.react-flow__panel.react-flow__controls .react-flow__controls-button svg { + fill: var(--color-foreground); + max-width: 12px; + max-height: 12px; +} + :root { /* Allotment Styling */ --focus-border: var(--color-brand); @@ -251,3 +279,27 @@ body { line-height: 1.4; white-space: nowrap; } + +/* Scrollbar styling */ +* { + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-foreground-muted); +} diff --git a/src/main/frontend/app/components/datamapper/forms/add-mapping-form.tsx b/src/main/frontend/app/components/datamapper/forms/add-mapping-form.tsx index 7fd4c754..94612f64 100644 --- a/src/main/frontend/app/components/datamapper/forms/add-mapping-form.tsx +++ b/src/main/frontend/app/components/datamapper/forms/add-mapping-form.tsx @@ -228,7 +228,7 @@ function AddMappingForm({ onSave, sources, targets, initialData }: MappingModalP {/* Target → Output */}
- +
- + - +
)} {!isRoot && ( diff --git a/src/main/frontend/app/components/file-structure/inline-rename-input.tsx b/src/main/frontend/app/components/file-structure/inline-rename-input.tsx index 3493140c..5dd1458f 100644 --- a/src/main/frontend/app/components/file-structure/inline-rename-input.tsx +++ b/src/main/frontend/app/components/file-structure/inline-rename-input.tsx @@ -1,4 +1,5 @@ import type { TreeItemIndex } from 'react-complex-tree' +import Input from '~/components/inputs/input' interface InlineRenameInputProps { icon: React.ComponentType<{ className?: string }> @@ -20,9 +21,10 @@ export default function InlineRenameInput({ return (
e.preventDefault()}> - onChange(e.target.value)} onKeyDown={(e) => { diff --git a/src/main/frontend/app/components/file-structure/studio-file-structure.tsx b/src/main/frontend/app/components/file-structure/studio-file-structure.tsx index 16676ca7..50b93e9f 100644 --- a/src/main/frontend/app/components/file-structure/studio-file-structure.tsx +++ b/src/main/frontend/app/components/file-structure/studio-file-structure.tsx @@ -516,10 +516,10 @@ export default function StudioFileStructure() { ) } - if (!project) return

No Project Selected

+ if (!project) return

No Project Selected

if (providerLoading) return if (!dataProvider) - return

No configurations found in src/main/configurations

+ return

No configurations found in src/main/configurations

const toolbarBtnClass = 'cursor-pointer rounded p-1 hover:bg-hover text-foreground' diff --git a/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx b/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx index 4d10574a..5c96b1ed 100644 --- a/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx +++ b/src/main/frontend/app/components/flow/add-subcomponent-modal.tsx @@ -1,5 +1,6 @@ import { createPortal } from 'react-dom' import Button from '../inputs/button' +import Search from '~/components/search/search' import type { ElementDetails } from '@frankframework/doc-library-core' import { useMemo, useState, type ChangeEvent } from 'react' @@ -80,13 +81,7 @@ export default function AddSubcomponentModal({

Add Subcomponent

{/* Paragraph / content */} - +
    {filteredChildren.length > 0 ? ( @@ -99,7 +94,7 @@ export default function AddSubcomponentModal({ onClick={() => setSelectedElement(child)} onDoubleClick={handleAddChild} className={`cursor-pointer px-3 py-2 ${ - isSelected ? 'bg-foreground-active text-background' : 'hover:bg-foreground-active/10' + isSelected ? 'bg-foreground-active text-background' : 'hover:bg-hover' }`} > {child.name} @@ -107,7 +102,7 @@ export default function AddSubcomponentModal({ ) }) ) : ( -
  • No results found
  • +
  • No results found
  • )}
diff --git a/src/main/frontend/app/components/flow/canvas-context-menu.tsx b/src/main/frontend/app/components/flow/canvas-context-menu.tsx index 0892cd54..312c0a6c 100644 --- a/src/main/frontend/app/components/flow/canvas-context-menu.tsx +++ b/src/main/frontend/app/components/flow/canvas-context-menu.tsx @@ -46,7 +46,7 @@ export default function CanvasContextMenu({ const itemClass = 'flex items-center justify-between gap-6 px-3 py-1.5 text-sm whitespace-nowrap' const enabledClass = `${itemClass} cursor-pointer hover:bg-hover text-foreground` - const disabledClass = `${itemClass} cursor-default text-muted-foreground opacity-50` + const disabledClass = `${itemClass} cursor-default text-foreground-muted opacity-50` function menuItem(label: string, onClick: () => void, enabled: boolean, shortcutId?: string) { return ( @@ -62,7 +62,7 @@ export default function CanvasContextMenu({ } > {label} - {shortcutId && {formatShortcut(shortcutId)}} + {shortcutId && {formatShortcut(shortcutId)}}
) } diff --git a/src/main/frontend/app/components/flow/create-node-modal.tsx b/src/main/frontend/app/components/flow/create-node-modal.tsx index 9ffe6de0..2cee080b 100644 --- a/src/main/frontend/app/components/flow/create-node-modal.tsx +++ b/src/main/frontend/app/components/flow/create-node-modal.tsx @@ -3,6 +3,7 @@ import useFlowStore from '~/stores/flow-store' import useNodeContextStore from '~/stores/node-context-store' import { useFFDoc } from '@frankframework/doc-library-react' import Button from '../inputs/button' +import Search from '~/components/search/search' import type { Elements, FFDocJson } from '@frankframework/doc-library-core' interface CreateNodeModalProperties { @@ -120,13 +121,7 @@ function CreateNodeModal({

Add Node

Select the element to be added from the list below.

- handleOnChange(event)} - className="border-border focus:ring-foreground-active mb-3 w-full rounded border px-3 py-2 focus:ring focus:outline-none" - /> + handleOnChange(event)} />
    {filteredElements.length > 0 ? ( @@ -139,7 +134,7 @@ function CreateNodeModal({ onClick={() => setSelectedElement(element.name)} onDoubleClick={handleCreateNode} className={`cursor-pointer px-3 py-2 ${ - isSelected ? 'bg-foreground-active text-background' : 'hover:bg-foreground-active/10' + isSelected ? 'bg-foreground-active text-background' : 'hover:bg-hover' }`} > {element.name} @@ -147,7 +142,7 @@ function CreateNodeModal({ ) }) ) : ( -
  • No results found
  • +
  • No results found
  • )}
diff --git a/src/main/frontend/app/components/git/git-changes.tsx b/src/main/frontend/app/components/git/git-changes.tsx index 67d13752..55cea6fa 100644 --- a/src/main/frontend/app/components/git/git-changes.tsx +++ b/src/main/frontend/app/components/git/git-changes.tsx @@ -80,7 +80,7 @@ function FileSection({ > {collapsed ? '▸' : '▾'} {title} - + {files.length} @@ -130,7 +130,7 @@ function FileSection({ {fileName} - {dirPath && {dirPath}} + {dirPath && {dirPath}}
) diff --git a/src/main/frontend/app/components/git/git-commit-box.tsx b/src/main/frontend/app/components/git/git-commit-box.tsx index 51bd7c28..f12ef81f 100644 --- a/src/main/frontend/app/components/git/git-commit-box.tsx +++ b/src/main/frontend/app/components/git/git-commit-box.tsx @@ -21,7 +21,7 @@ export default function GitCommitBox({ value={commitMessage} onChange={(e) => onMessageChange(e.target.value)} placeholder="Commit message..." - className="border-border bg-background text-foreground placeholder:text-muted-foreground w-full resize-none rounded border p-2.5 text-xs focus:outline-none" + className="border-border bg-background text-foreground placeholder:text-foreground-muted w-full resize-none rounded border p-2.5 text-xs focus:outline-none" rows={4} onKeyDown={(e) => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { diff --git a/src/main/frontend/app/components/git/git-toolbar.tsx b/src/main/frontend/app/components/git/git-toolbar.tsx index d2c292b6..2c659423 100644 --- a/src/main/frontend/app/components/git/git-toolbar.tsx +++ b/src/main/frontend/app/components/git/git-toolbar.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import clsx from 'clsx' import type { GitStatus } from '~/types/git.types' import Button from '~/components/inputs/button' +import Input from '~/components/inputs/input' interface GitToolbarProps { status: GitStatus | null @@ -97,14 +98,13 @@ export default function GitToolbar({ )} {showToken && !status?.isLocal && (
- onTokenChange(e.target.value)} placeholder={ hasStoredToken ? 'Using saved token (override here)' : 'Personal access token (for private repos)' } - className="border-border bg-background text-foreground placeholder:text-muted-foreground w-full rounded border px-2 py-1 text-xs focus:outline-none" />
)} diff --git a/src/main/frontend/app/components/inputs/button.tsx b/src/main/frontend/app/components/inputs/button.tsx index b193d0f1..5deb92fc 100644 --- a/src/main/frontend/app/components/inputs/button.tsx +++ b/src/main/frontend/app/components/inputs/button.tsx @@ -10,7 +10,7 @@ export default function Button({ @@ -177,7 +177,7 @@ export default function ConfigurationOverview() { return (
-
navigate('/')}> +
navigate('/')}>

Switch configuration

diff --git a/src/main/frontend/app/routes/editor/editor.tsx b/src/main/frontend/app/routes/editor/editor.tsx index 0c2193c2..51a47c4c 100644 --- a/src/main/frontend/app/routes/editor/editor.tsx +++ b/src/main/frontend/app/routes/editor/editor.tsx @@ -770,7 +770,7 @@ export default function CodeEditor() { 'cursor-pointer px-3 py-1 transition-colors', leftTab === 'files' ? 'bg-selected text-foreground font-medium' - : 'hover:bg-foreground-active text-muted-foreground', + : 'hover:bg-hover text-foreground-muted', )} > Files @@ -781,7 +781,7 @@ export default function CodeEditor() { 'border-border cursor-pointer border-l px-3 py-1 transition-colors', leftTab === 'git' ? 'bg-selected text-foreground font-medium' - : 'hover:bg-foreground-active text-muted-foreground', + : 'hover:bg-hover text-foreground-muted', )} > Git diff --git a/src/main/frontend/app/routes/help/help-image.tsx b/src/main/frontend/app/routes/help/help-image.tsx index 8221f92e..ed7ff2f0 100644 --- a/src/main/frontend/app/routes/help/help-image.tsx +++ b/src/main/frontend/app/routes/help/help-image.tsx @@ -11,7 +11,7 @@ export const HelpImage: React.FC = ({ src, alt = '', caption, wi return (
{alt} - {caption &&

{caption}

} + {caption &&

{caption}

}
) } diff --git a/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx b/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx index 48744d7f..21f0079d 100644 --- a/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx +++ b/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import DirectoryPicker from '~/components/directory-picker/directory-picker' import Button from '~/components/inputs/button' +import Input from '~/components/inputs/input' import { filesystemService } from '~/services/filesystem-service' interface CloneProjectModalProperties { @@ -80,15 +81,14 @@ export default function CloneConfigurationModal({
- setShowPicker(true)} /> -
@@ -96,10 +96,9 @@ export default function CloneConfigurationModal({
- setRepoUrl(event.target.value)} - className="border-border bg-background focus:border-foreground-active focus:ring-foreground-active w-full rounded border px-2 py-1 text-sm transition focus:ring-2 focus:outline-none" placeholder="https://github.com/user/repo.git" aria-label="repository url" /> @@ -108,11 +107,10 @@ export default function CloneConfigurationModal({ {!isLocal && (
- setToken(event.target.value)} - className="border-border bg-background focus:border-foreground-active focus:ring-foreground-active w-full rounded border px-2 py-1 text-sm transition focus:ring-2 focus:outline-none" placeholder="Personal access token for private repos" aria-label="access token" /> diff --git a/src/main/frontend/app/routes/projectlanding/configuration-row.tsx b/src/main/frontend/app/routes/projectlanding/configuration-row.tsx index edc91ed2..5e0b45e3 100644 --- a/src/main/frontend/app/routes/projectlanding/configuration-row.tsx +++ b/src/main/frontend/app/routes/projectlanding/configuration-row.tsx @@ -34,7 +34,7 @@ export default function ConfigurationRow({ event.stopPropagation() onExport() }} - className="text-foreground-muted cursor-pointer rounded p-2 transition-colors hover:text-blue-500" + className="text-foreground-muted hover:text-foreground cursor-pointer rounded p-2 transition-colors" aria-label="Export configuration as zip" title="Export as .zip" > @@ -47,7 +47,7 @@ export default function ConfigurationRow({ event.stopPropagation() onRemove() }} - className="text-foreground-muted cursor-pointer rounded p-2 transition-colors hover:text-red-500" + className="text-foreground-muted hover:text-foreground cursor-pointer rounded p-2 transition-colors" aria-label="Remove from recent configurations" > diff --git a/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx b/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx index daa5604f..1e848653 100644 --- a/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx +++ b/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import DirectoryPicker from '~/components/directory-picker/directory-picker' import Button from '~/components/inputs/button' +import Input from '~/components/inputs/input' import { filesystemService } from '~/services/filesystem-service' interface NewProjectModalProperties { @@ -64,15 +65,14 @@ export default function NewConfigurationModal({
- setShowPicker(true)} /> -
@@ -80,10 +80,9 @@ export default function NewConfigurationModal({
- setName(event.target.value)} - className="border-border bg-background focus:border-foreground-active focus:ring-foreground-active w-full rounded border px-2 py-1 text-sm transition focus:ring-2 focus:outline-none" placeholder="Enter configuration name" />
diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index 626547b3..acb05a81 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -215,7 +215,7 @@ export default function ProjectLanding() {
{!isLocalEnvironment && ( -
+
Cloud workspace projects are automatically removed after 24 hours of inactivity. After you are done please use the Export functionality in the landing page to download a backup of your project.
@@ -261,7 +261,7 @@ export default function ProjectLanding() { const Header = () => (
-

Flow

+

Flow

) @@ -308,7 +308,7 @@ const ProjectList = ({
{frameworkConfigurations.length > 0 && (
-

Remote

+

Remote

{frameworkConfigurations.map((configuration) => (
)} {isDiscovering && frameworkConfigurations.length === 0 && ( -

Scanning for remote instances...

+

Scanning for remote instances...

)} {projects.length === 0 && frameworkConfigurations.length === 0 && !isDiscovering && ( -

No configurations found

+

No configurations found

)} {projects.length > 0 && ( <> -

Recent

+

Recent

{projects.map((project) => ( void }) => (
-
- Recent +
+ Recent
onSearchChange(e.target.value)} /> @@ -359,7 +359,7 @@ const Toolbar = ({ onSearchChange }: { onSearchChange: (val: string) => void }) ) const LoadingState = () => ( -
+
Initializing workspace...
) diff --git a/src/main/frontend/app/routes/settings/pages/project-settings.tsx b/src/main/frontend/app/routes/settings/pages/project-settings.tsx index 9ea99e9d..df75c1ce 100644 --- a/src/main/frontend/app/routes/settings/pages/project-settings.tsx +++ b/src/main/frontend/app/routes/settings/pages/project-settings.tsx @@ -49,7 +49,7 @@ export default function ProjectSettings() { ) : ( // No project loaded -
+
Load a project in the  Project Overview diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index 39732ce6..3db22c7d 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -1495,7 +1495,7 @@ function FlowCanvas({ onOpenInEditor }: { onOpenInEditor: () => void }) { deleteKeyCode={null} minZoom={0.2} > - + {deprecated.description} +
  • {deprecated.description}
  • )}
    , diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/components/missing-requirements.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/components/missing-requirements.tsx index 89df861f..20c9a84c 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/components/missing-requirements.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/components/missing-requirements.tsx @@ -59,7 +59,7 @@ export default function MissingRequirements({ missingChildren, isFulfilled }: Mi {values.join(', ')}
    - {expanded && (show less)} + {expanded && (show less)} ) })} diff --git a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx index 4350317e..40964041 100644 --- a/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx +++ b/src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx @@ -501,7 +501,7 @@ export default function FrankNode(properties: NodeProps) { {properties.data.attributes && Object.entries(properties.data.attributes).map(([key, value]) => (
    -

    {key}

    +

    {key}

    {value}

    ))} @@ -556,7 +556,7 @@ export default function FrankNode(properties: NodeProps) { {possibleChildren.length > 0 && (
    setIsModalOpen(true)} >
    diff --git a/src/main/frontend/app/routes/studio/context/context-input-field.tsx b/src/main/frontend/app/routes/studio/context/context-input-field.tsx index 6bc4da7b..a9934322 100644 --- a/src/main/frontend/app/routes/studio/context/context-input-field.tsx +++ b/src/main/frontend/app/routes/studio/context/context-input-field.tsx @@ -2,6 +2,7 @@ import React from 'react' import Toggle from '~/components/inputs/toggle' import ValidatedInput from '~/components/inputs/validatedInput' +import Input from '~/components/inputs/input' interface ContextInputFieldProperties { id: string @@ -28,7 +29,7 @@ export default function ContextInputField({ value={value} onChange={(event) => onChange(event.currentTarget.value)} onKeyDown={onKeyDown} - className="border-border bg-background focus:border-foreground-active focus:ring-foreground-active mt-1 w-full rounded-md border px-3 py-2 shadow-sm sm:text-sm" + className="border-border bg-background focus:border-border focus:ring-border mt-1 w-full rounded-md border px-3 py-2 shadow-sm sm:text-sm" > {Object.keys(enumOptions).map((optKey) => ( @@ -67,13 +68,13 @@ export default function ContextInputField({ // Default text input return ( - onChange(event.currentTarget.value)} onKeyDown={onKeyDown} - className="border-border focus:border-foreground-active mt-1 w-full rounded-md border px-3 py-2 shadow-sm focus:ring-0 focus:outline-none sm:text-sm" + wrapperClassName="mt-1" /> ) } diff --git a/src/main/frontend/app/routes/studio/context/context-input.tsx b/src/main/frontend/app/routes/studio/context/context-input.tsx index 012a2f9d..92da1a97 100644 --- a/src/main/frontend/app/routes/studio/context/context-input.tsx +++ b/src/main/frontend/app/routes/studio/context/context-input.tsx @@ -37,7 +37,7 @@ export default function ContextInput({
    {required && *} -
    , diff --git a/src/main/frontend/app/routes/studio/context/element-hover-card.tsx b/src/main/frontend/app/routes/studio/context/element-hover-card.tsx index 54e2ee2c..2ad23d93 100644 --- a/src/main/frontend/app/routes/studio/context/element-hover-card.tsx +++ b/src/main/frontend/app/routes/studio/context/element-hover-card.tsx @@ -87,7 +87,7 @@ export default function ElementHoverCard({ anchorRect, element, onUnlock, onEnte href={frankdocUrl} target="_blank" rel="noopener noreferrer" - className="border-border bg-backdrop hover:border-foreground-active hover:text-foreground-active flex shrink-0 flex-col items-center justify-center gap-1 self-stretch rounded border px-3 text-xs transition-colors" + className="border-border bg-backdrop hover:border-border hover:text-foreground flex shrink-0 flex-col items-center justify-center gap-1 self-stretch rounded border px-3 text-xs transition-colors" > FrankDoc diff --git a/src/main/frontend/app/routes/studio/context/group-context.tsx b/src/main/frontend/app/routes/studio/context/group-context.tsx index df80766e..b28633a1 100644 --- a/src/main/frontend/app/routes/studio/context/group-context.tsx +++ b/src/main/frontend/app/routes/studio/context/group-context.tsx @@ -2,6 +2,7 @@ import useFlowStore, { isGroupNode } from '~/stores/flow-store' import { GROUP_COLORS, GROUP_DEFAULT_COLOR } from '~/routes/studio/canvas/nodetypes/group-node' import { ALL_SHORTCUTS, formatShortcutParts, useShortcutStore } from '~/stores/shortcut-store' import Button from '~/components/inputs/button' +import Input from '~/components/inputs/input' export default function GroupContext({ nodeId }: Readonly<{ nodeId: string }>) { const node = useFlowStore((state) => state.nodes.find((node) => node.id === nodeId)) @@ -38,11 +39,9 @@ export default function GroupContext({ nodeId }: Readonly<{ nodeId: string }>) {
    - useFlowStore.getState().setGroupnodeLabel(nodeId, changeEvent.target.value)} - className="border-border bg-background text-foreground focus:ring-ring w-full rounded border px-3 py-2 text-sm focus:ring-1 focus:outline-none" />
    diff --git a/src/main/frontend/app/routes/studio/context/sorted-elements.tsx b/src/main/frontend/app/routes/studio/context/sorted-elements.tsx index 871e1281..f6f878d2 100644 --- a/src/main/frontend/app/routes/studio/context/sorted-elements.tsx +++ b/src/main/frontend/app/routes/studio/context/sorted-elements.tsx @@ -75,7 +75,7 @@ export default function SortedElements({ type, items, onDragStart, searchTerm }:
    - - {show && ( -
    - )} -
    + +
    + {show && + anchorRect && + createPortal( +
    , + document.body, + )} + ) } From eabffe8458bf6f46830928b373eaaea912e16e64 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 26 May 2026 10:23:35 +0200 Subject: [PATCH 03/13] Refactor button components and replace close buttons with a consistent CloseButton component across modals --- .../directory-picker/directory-picker.tsx | 8 +- .../app/components/flow/create-node-modal.tsx | 5 +- .../frontend/app/components/inputs/button.tsx | 10 +- .../app/components/inputs/kebab-menu.tsx | 94 +++++++++++++++++++ .../frontend/app/components/search/search.tsx | 34 ++++--- .../add-configuration-modal.tsx | 5 +- .../routes/projectlanding/action-button.tsx | 9 +- .../clone-configuration-modal.tsx | 5 +- .../projectlanding/configuration-row.tsx | 48 +++++----- .../new-configuration-modal.tsx | 5 +- .../routes/projectlanding/project-landing.tsx | 54 ++++++----- src/main/frontend/icons/solar/Library.svg | 4 + 12 files changed, 193 insertions(+), 88 deletions(-) create mode 100644 src/main/frontend/app/components/inputs/kebab-menu.tsx create mode 100644 src/main/frontend/icons/solar/Library.svg diff --git a/src/main/frontend/app/components/directory-picker/directory-picker.tsx b/src/main/frontend/app/components/directory-picker/directory-picker.tsx index 32b8c75f..c5f5d55f 100644 --- a/src/main/frontend/app/components/directory-picker/directory-picker.tsx +++ b/src/main/frontend/app/components/directory-picker/directory-picker.tsx @@ -5,6 +5,7 @@ import { filesystemService } from '~/services/filesystem-service' import type { FilesystemEntry } from '~/types/filesystem.types' import { ApiError } from '~/utils/api' import Button from '../inputs/button' +import CloseButton from '../inputs/close-button' interface DirectoryPickerProperties { isOpen: boolean @@ -89,12 +90,7 @@ export default function DirectoryPicker({

    Select Directory

    - +
    diff --git a/src/main/frontend/app/components/flow/create-node-modal.tsx b/src/main/frontend/app/components/flow/create-node-modal.tsx index 2cee080b..fd69de47 100644 --- a/src/main/frontend/app/components/flow/create-node-modal.tsx +++ b/src/main/frontend/app/components/flow/create-node-modal.tsx @@ -3,6 +3,7 @@ import useFlowStore from '~/stores/flow-store' import useNodeContextStore from '~/stores/node-context-store' import { useFFDoc } from '@frankframework/doc-library-react' import Button from '../inputs/button' +import CloseButton from '../inputs/close-button' import Search from '~/components/search/search' import type { Elements, FFDocJson } from '@frankframework/doc-library-core' @@ -147,9 +148,7 @@ function CreateNodeModal({
    - +
    diff --git a/src/main/frontend/app/components/inputs/button.tsx b/src/main/frontend/app/components/inputs/button.tsx index 5deb92fc..2aa0f848 100644 --- a/src/main/frontend/app/components/inputs/button.tsx +++ b/src/main/frontend/app/components/inputs/button.tsx @@ -1,18 +1,22 @@ import React from 'react' import clsx from 'clsx' +type ButtonVariant = 'default' | 'ghost' + export default function Button({ children, className, + variant = 'default', ...properties -}: React.PropsWithChildren>>) { +}: React.PropsWithChildren & { variant?: ButtonVariant }>>) { return ( + + {menuPosition && + createPortal( +
    + {items.map((item) => ( + + ))} +
    , + document.body, + )} + + ) +} diff --git a/src/main/frontend/app/components/search/search.tsx b/src/main/frontend/app/components/search/search.tsx index e44233ef..f5477f2e 100644 --- a/src/main/frontend/app/components/search/search.tsx +++ b/src/main/frontend/app/components/search/search.tsx @@ -7,6 +7,8 @@ interface SearchProperties { type?: string placeholder?: string value?: string + className?: string + inputClassName?: string onChange: (event: React.ChangeEvent) => void onKeyDown?: (event: React.KeyboardEvent) => void } @@ -16,26 +18,30 @@ export default function Search({ type = 'search', placeholder = 'Search', value, + className, + inputClassName, onChange, onKeyDown, }: Readonly) { const generatedId = useId() const inputId = id ?? generatedId return ( -
    - - +
    +
    + + +
    ) } diff --git a/src/main/frontend/app/routes/configurations/add-configuration-modal.tsx b/src/main/frontend/app/routes/configurations/add-configuration-modal.tsx index 5b17a2a8..0ddb180d 100644 --- a/src/main/frontend/app/routes/configurations/add-configuration-modal.tsx +++ b/src/main/frontend/app/routes/configurations/add-configuration-modal.tsx @@ -3,6 +3,7 @@ import { createConfigurationFile } from '~/services/configuration-file-service' import { useProjectStore } from '~/stores/project-store' import type { ConfigurationProject } from '~/types/project.types' import Button from '~/components/inputs/button' +import CloseButton from '~/components/inputs/close-button' import Input from '~/components/inputs/input' import DirectoryPicker from '~/components/directory-picker/directory-picker' import { fetchProject } from '~/services/project-service' @@ -135,9 +136,7 @@ export default function AddConfigurationModal({ {loading ? 'Adding...' : `Add ${displayFilename || 'configuration file'} to ${currentConfiguration.name}`} - +
    {error &&

    {error}

    } diff --git a/src/main/frontend/app/routes/projectlanding/action-button.tsx b/src/main/frontend/app/routes/projectlanding/action-button.tsx index c180e190..b39b26c6 100644 --- a/src/main/frontend/app/routes/projectlanding/action-button.tsx +++ b/src/main/frontend/app/routes/projectlanding/action-button.tsx @@ -4,11 +4,16 @@ import Button from '~/components/inputs/button' interface ActionButtonProperties { onClick?: React.MouseEventHandler label: string + className?: string } -export default function ActionButton({ onClick, label }: Readonly) { +export default function ActionButton({ onClick, label, className }: Readonly) { return ( - ) diff --git a/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx b/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx index 21f0079d..15480621 100644 --- a/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx +++ b/src/main/frontend/app/routes/projectlanding/clone-configuration-modal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import DirectoryPicker from '~/components/directory-picker/directory-picker' import Button from '~/components/inputs/button' +import CloseButton from '~/components/inputs/close-button' import Input from '~/components/inputs/input' import { filesystemService } from '~/services/filesystem-service' @@ -135,9 +136,7 @@ export default function CloneConfigurationModal({ Clone - +
    diff --git a/src/main/frontend/app/routes/projectlanding/configuration-row.tsx b/src/main/frontend/app/routes/projectlanding/configuration-row.tsx index 5e0b45e3..c5787d7e 100644 --- a/src/main/frontend/app/routes/projectlanding/configuration-row.tsx +++ b/src/main/frontend/app/routes/projectlanding/configuration-row.tsx @@ -1,6 +1,7 @@ -import CloseSquareIcon from 'icons/solar/Close Square.svg?react' import ArchiveIcon from '/icons/solar/Archive.svg?react' +import TrashBinIcon from '/icons/solar/Trash Bin.svg?react' import type { RecentConfigurationProject } from '~/types/project.types' +import KebabMenu, { type KebabMenuItem } from '~/components/inputs/kebab-menu' interface ConfigurationRowProperties { project: RecentConfigurationProject @@ -17,6 +18,24 @@ export default function ConfigurationRow({ onRemove, onExport, }: Readonly) { + const menuItems: KebabMenuItem[] = [ + ...(isLocal + ? [] + : [ + { + label: 'Export as .zip', + icon: , + onClick: onExport, + }, + ]), + { + label: 'Remove from recent', + icon: , + onClick: onRemove, + className: 'text-red-500', + }, + ] + return (
    {project.rootPath}

    -
    - {!isLocal && ( - - )} - - -
    +
    ) } diff --git a/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx b/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx index 1e848653..97e7a4ae 100644 --- a/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx +++ b/src/main/frontend/app/routes/projectlanding/new-configuration-modal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import DirectoryPicker from '~/components/directory-picker/directory-picker' import Button from '~/components/inputs/button' +import CloseButton from '~/components/inputs/close-button' import Input from '~/components/inputs/input' import { filesystemService } from '~/services/filesystem-service' @@ -103,9 +104,7 @@ export default function NewConfigurationModal({ Create Configuration - +
    diff --git a/src/main/frontend/app/routes/projectlanding/project-landing.tsx b/src/main/frontend/app/routes/projectlanding/project-landing.tsx index 67097dc1..4be7f610 100644 --- a/src/main/frontend/app/routes/projectlanding/project-landing.tsx +++ b/src/main/frontend/app/routes/projectlanding/project-landing.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState, useCallback, useRef } from 'react' +import React, { useEffect, useState, useCallback, useRef } from 'react' import { useNavigate } from 'react-router' import FfIcon from '/icons/custom/ff!-icon.svg?react' -import ArchiveIcon from '/icons/solar/Archive.svg?react' +import LibraryIcon from '/icons/solar/Library.svg?react' import { fetchInstanceConfigurations, type FFConfiguration } from '~/services/frank-framework-service' import { useProjectStore } from '~/stores/project-store' @@ -188,10 +188,10 @@ export default function ProjectLanding() { if (isLoading || isOpeningProject) return return ( -
    +
    -
    +
    @@ -213,15 +213,15 @@ export default function ProjectLanding() { isDiscovering={isDiscovering} />
    - - {!isLocalEnvironment && ( -
    - Cloud workspace projects are automatically removed after 24 hours of inactivity. After you are done please - use the Export functionality in the landing page to download a backup of your project. -
    - )}
    + {!isLocalEnvironment && ( +
    + Cloud workspace projects are automatically removed after 24 hours of inactivity. After you are done please use + the Export functionality in the landing page to download a backup of your project. +
    + )} + setIsModalOpen(false)} @@ -259,9 +259,11 @@ export default function ProjectLanding() { } const Header = () => ( -
    - -

    Flow

    +
    +
    + +

    Flow

    +
    ) @@ -278,11 +280,15 @@ const Sidebar = ({ onCloneClick: () => void onImportClick: () => void }) => ( -