Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
960fae7
feat: search in header
OliwiaGowor Feb 17, 2025
1a76271
Merge branch 'main' of github.com:kyma-project/busola into command-pa…
OliwiaGowor Feb 19, 2025
ee3915e
Merge branch 'main' of github.com:kyma-project/busola into command-pa…
OliwiaGowor Feb 19, 2025
481ac94
fix: center search & command palette
OliwiaGowor Feb 20, 2025
2421afc
fix: prevent input from closing
OliwiaGowor Feb 20, 2025
dc91e25
fix: adjust for small screens
OliwiaGowor Feb 20, 2025
6a02aff
fix: lint fix
OliwiaGowor Feb 20, 2025
4871ed1
fix: search disappearing
OliwiaGowor Feb 21, 2025
f52d4a9
fix: positioning also for cmd+k
OliwiaGowor Feb 21, 2025
9fb00e7
cleanup
OliwiaGowor Feb 21, 2025
6402e71
fix: fix opening when search is not visible & initial focus
OliwiaGowor Feb 21, 2025
a5b6034
fix: prevent jumping
OliwiaGowor Feb 21, 2025
2a91cde
fix: escape
OliwiaGowor Feb 21, 2025
e5a4289
fix: unsaved in history mode & wrong focus
OliwiaGowor Feb 21, 2025
0f4cc6e
adjust test
OliwiaGowor Feb 21, 2025
6bccd0e
adjust placement & hide search button
OliwiaGowor Feb 25, 2025
a794c98
bring back search button
OliwiaGowor Feb 25, 2025
6a31e52
add ref and move snow
OliwiaGowor Feb 26, 2025
2ae64cb
cleanup
OliwiaGowor Feb 26, 2025
5f99937
adjust tests
OliwiaGowor Feb 26, 2025
a009545
fix test?
OliwiaGowor Feb 26, 2025
d4a30fa
adjust tests
OliwiaGowor Feb 26, 2025
333fb13
fix positioning
OliwiaGowor Mar 3, 2025
3747f59
fix lint
OliwiaGowor Mar 3, 2025
010ff3b
Merge branch 'main' of github.com:kyma-project/busola into command-pa…
OliwiaGowor Mar 3, 2025
5b53a95
Merge branch 'main' of github.com:kyma-project/busola into command-pa…
OliwiaGowor Mar 3, 2025
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
1 change: 1 addition & 0 deletions public/i18n/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ command-palette:
no-results-found: No results found
search:
remove-ns-context: Remove Namespace context
quick-navigation: Quick navigation
common:
ariaLabel:
new-tab-link: This link will be opened in a new tab.
Expand Down
6 changes: 3 additions & 3 deletions src/command-pallette/CommandPaletteProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const CommandPaletteProvider = ({
>();

const setShowDialog = (value: boolean) => {
const modalPresent = document.querySelector('ui5-dialog[open]');
const modalPresent =
document.querySelector('ui5-dialog[open]') ||
document.querySelector('.command-palette-ui');
// disable opening palette if other modal is present
if (!modalPresent || !value) {
_setShowDialog(value);
Expand All @@ -37,8 +39,6 @@ export const CommandPaletteProvider = ({
setShowDialog(!showDialog);
// [on Firefox] prevent opening the browser search bar via CMD/CTRL+K
e.preventDefault();
} else if (key === 'Escape') {
hide();
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.command-palette-search-bar {
width: var(--_ui5-v2-7-0_input_width) !important;
}

.ui5-shellbar-mid-content {
flex-grow: 1;
}

@media (max-width: 1040px) {
.command-palette-search-bar {
width: 100% !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useEffect, RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createPortal } from 'react-dom';
import { Icon, Input } from '@ui5/webcomponents-react';
import { K8sResource } from 'types';
import { useObjectState } from 'shared/useObjectState';
import { CommandPaletteUI } from './CommandPaletteUI';
import { useRecoilValue } from 'recoil';
import { availableNodesSelector } from 'state/navigation/availableNodesSelector';
import { SCREEN_SIZE_BREAKPOINT_M } from './types';
import './CommandPaletteSearchBar.scss';

type CommandPaletteSearchBarProps = {
slot?: string;
shouldFocus?: boolean;
setShouldFocus?: Function;
shellbarRef?: RefObject<HTMLElement>;
};

export function CommandPaletteSearchBar({
slot,
shouldFocus,
setShouldFocus,
shellbarRef,
}: CommandPaletteSearchBarProps) {
useRecoilValue(availableNodesSelector); // preload the values to prevent page rerenders
const { t } = useTranslation();
const [open, setOpen] = useState(shouldFocus || false);
const [resourceCache, updateResourceCache] = useObjectState<
Record<string, K8sResource[]>
>();
const shouldShowDialog = shouldFocus ? shouldFocus : open;

const setShowDialog = (value: boolean) => {
const modalPresent =
document.querySelector('ui5-dialog[open]') ||
document.querySelector('.command-palette-ui');
// disable opening palette if other modal is present
if (!modalPresent || !value) {
setOpen(value);
}
};

useEffect(() => {
const shellbarCurr = shellbarRef?.current;
const searchButton = shellbarCurr?.shadowRoot?.querySelector(
'.ui5-shellbar-search-button',
) as HTMLElement;
const searchField = shellbarCurr?.shadowRoot?.querySelector(
'.ui5-shellbar-search-field',
) as HTMLElement;

if (
searchButton &&
searchField &&
window.innerWidth > SCREEN_SIZE_BREAKPOINT_M
) {
searchButton.style.display = 'none';

// search bar has to be always visible on big screen
shellbarCurr?.setAttribute('show-search-field', '');
searchField.style.display = 'flex';
} else if (searchButton && searchField) {
searchButton.style.display = 'inline-block';
shellbarCurr?.removeAttribute('show-search-field');
searchField.style.display = 'none';
}
}, [window.innerWidth, shellbarRef?.current]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<>
<Input
id="command-palette-search-bar"
accessibleName="command-palette-search-bar"
onClick={() => setOpen(true)}
onInput={e => e.preventDefault()}
showClearIcon
className="search-with-display-more command-palette-search-bar"
icon={<Icon name="slim-arrow-right" />}
slot={slot}
placeholder={t('command-palette.search.quick-navigation')}
/>
{shouldShowDialog &&
createPortal(
<CommandPaletteUI
showCommandPalette={shouldShowDialog}
hide={() => {
setShowDialog(false);
if (setShouldFocus) setShouldFocus(false);
}}
resourceCache={resourceCache}
updateResourceCache={updateResourceCache}
/>,
document.body,
)}
</>
);
}
49 changes: 47 additions & 2 deletions src/command-pallette/CommandPalletteUI/CommandPaletteUI.scss
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
.command-palette-ui {
position: fixed;
top: calc(var(--_ui5-v2-7-0_shellbar_root_height) + 0.5rem);
width: 100%;
height: 100%;

display: flex;
justify-content: center;

z-index: 1000000;
background-color: rgba(0, 0, 0, 0.6);
background-color: rgba(0, 0, 0, 0.3);

&__wrapper {
display: flex;
width: 60vw;
max-width: 700px;
position: fixed;
margin-top: 20vh;
top: 0.5rem;
opacity: 0%; //prevent visually jumping
}

&__content {
Expand All @@ -23,5 +26,47 @@
background: var(--sapList_Background);
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 0.125rem 0
color-mix(in srgb, var(--sapContent_ShadowColor) 16%, transparent),
0 0.5rem 1rem 0
color-mix(in srgb, var(--sapContent_ShadowColor) 16%, transparent);
}

.input-container {
display: flex;
flex-direction: row;
align-items: center;

.input-back-button {
display: none;
}
}
}

@media (max-width: 1040px) {
.command-palette-ui {
&__wrapper {
width: 100vw;
max-width: unset;
top: 0;
height: 100%;
opacity: 100%;
}

.input-container {
display: flex;
flex-direction: row;
align-items: center;

.search-with-display-more {
margin-right: 2.5rem;
}

.input-back-button {
display: block;
padding: 0 0.25rem;
box-sizing: content-box;
}
}
}
}
112 changes: 92 additions & 20 deletions src/command-pallette/CommandPalletteUI/CommandPaletteUI.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { ReactNode, useEffect, useState } from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useEventListener } from 'hooks/useEventListener';
import { addHistoryEntry, getHistoryEntries } from './search-history';
import { activeNamespaceIdState } from 'state/activeNamespaceIdAtom';
import {
CommandPalletteHelp,
NamespaceContextDisplay,
ShortHelpText,
SuggestedQuery,
} from './components/components';
import { ResultsList } from './ResultsList/ResultsList';
import { addHistoryEntry, getHistoryEntries } from './search-history';
import { useSearchResults } from './useSearchResults';
import './CommandPaletteUI.scss';
import { K8sResource } from 'types';
import { useRecoilValue } from 'recoil';
import { activeNamespaceIdState } from 'state/activeNamespaceIdAtom';
import { Icon, Input } from '@ui5/webcomponents-react';
import { Button, Icon, Input } from '@ui5/webcomponents-react';
import './CommandPaletteUI.scss';
import { handleActionIfFormOpen } from 'shared/components/UnsavedMessageBox/helpers';
import { isResourceEditedState } from 'state/resourceEditedAtom';
import { isFormOpenState } from 'state/formOpenAtom';
import { SCREEN_SIZE_BREAKPOINT_M } from './types';

function Background({
hide,
Expand Down Expand Up @@ -51,6 +55,10 @@ export function CommandPaletteUI({
updateResourceCache,
}: CommandPaletteProps) {
const namespace = useRecoilValue(activeNamespaceIdState);
const [isResourceEdited, setIsResourceEdited] = useRecoilState(
isResourceEditedState,
);
const [isFormOpen, setIsFormOpen] = useRecoilState(isFormOpenState);

const [query, setQuery] = useState('');
const [originalQuery, setOriginalQuery] = useState('');
Expand All @@ -62,6 +70,8 @@ export function CommandPaletteUI({
const [isHistoryMode, setHistoryMode] = useState(false);
const [historyIndex, setHistoryIndex] = useState(0);

const commandPaletteRef = useRef<HTMLDivElement | null>(null);

const {
results,
suggestedQuery,
Expand All @@ -77,9 +87,42 @@ export function CommandPaletteUI({

useEffect(() => setNamespaceContext(namespace), [namespace]);
useEffect(() => {
document.getElementById('command-palette-search')?.focus();
setTimeout(
() => document.getElementById('command-palette-search')?.focus(),
100,
);
}, []);

useEffect(() => {
const headerInput = document.getElementById('command-palette-search-bar');
const headerSlot = document
.querySelector('ui5-shellbar')
?.shadowRoot?.querySelector('.ui5-shellbar-search-field') as HTMLElement;
const paletteCurrent = commandPaletteRef.current;

if (!showCommandPalette || !headerSlot) return;

//show search bar when Command Palette is open
document
.querySelector('ui5-shellbar')
?.setAttribute('show-search-field', '');
headerSlot.style.display = 'flex';

//position Command Palette
if (
window.innerWidth > SCREEN_SIZE_BREAKPOINT_M &&
headerInput &&
paletteCurrent
) {
const shellbarRect = headerInput.getBoundingClientRect();
paletteCurrent.style.right = `${window.innerWidth -
shellbarRect.right}px`;
paletteCurrent.style.opacity = '100%'; //prevent visually jumping
} else if (paletteCurrent) {
paletteCurrent.style.right = '0px';
}
}, [showCommandPalette, window.innerWidth]); // eslint-disable-line react-hooks/exhaustive-deps

const commandPaletteInput = document.getElementById('command-palette-search');

const handleQuerySelection = () => {
Expand All @@ -104,8 +147,18 @@ export function CommandPaletteUI({
const historyEntries = getHistoryEntries();
if (key === 'Enter' && results[0]) {
// choose current entry
addHistoryEntry(results[0].query);
results[0].onActivate();
e.preventDefault();

handleActionIfFormOpen(
isResourceEdited,
setIsResourceEdited,
isFormOpen,
setIsFormOpen,
() => {
addHistoryEntry(results[0].query);
results[0].onActivate();
},
);
} else if (key === 'Tab') {
e.preventDefault();
// fill search with active history entry
Expand Down Expand Up @@ -170,6 +223,10 @@ export function CommandPaletteUI({
'keydown',
(e: Event) => {
const { key } = e as KeyboardEvent;
if (key === 'Escape') {
hide();
}

return !isHistoryMode
? keyDownInDropdownMode(key, e)
: keyDownInHistoryMode(key, e);
Expand All @@ -179,22 +236,37 @@ export function CommandPaletteUI({

return (
<Background hide={hide}>
<div className="command-palette-ui__wrapper" role="dialog">
<div
className="command-palette-ui__wrapper"
role="dialog"
ref={commandPaletteRef}
>
<div className="command-palette-ui__content">
<NamespaceContextDisplay
namespaceContext={namespaceContext}
setNamespaceContext={setNamespaceContext}
/>
<Input
id="command-palette-search"
accessibleName="command-palette-search"
value={!isHistoryMode ? query : ''}
placeholder={!isHistoryMode ? '' : query}
onInput={(e: any) => setQuery((e.target as HTMLInputElement).value)}
showClearIcon
className="search-with-display-more full-width"
icon={<Icon name="slim-arrow-right" />}
/>
<div className="input-container">
<Button
className="input-back-button"
design="Transparent"
onClick={hide}
>
<Icon name="nav-back"></Icon>
</Button>
<Input
id="command-palette-search"
accessibleName="command-palette-search"
value={!isHistoryMode ? query : ''}
placeholder={!isHistoryMode ? '' : query}
onInput={(e: any) =>
setQuery((e.target as HTMLInputElement).value)
}
showClearIcon
className="search-with-display-more full-width"
icon={<Icon name="slim-arrow-right" />}
/>
</div>
{!showHelp && (
<>
<ResultsList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@
background: var(--sapList_SelectionBackgroundColor);
}
}

@media (max-width: 1040px) {
.command-palette-ui__results {
max-height: unset;
}
}
Loading
Loading