-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connect: Add SearchBar #23980
Connect: Add SearchBar #23980
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* Copyright 2023 Gravitational, Inc | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import { Flex, Text } from 'design'; | ||
|
||
import { Highlight } from './Highlight'; | ||
|
||
export default { | ||
title: 'Shared/Highlight', | ||
}; | ||
|
||
export const Story = () => { | ||
const keywords = [ | ||
'Aliquam', | ||
'olor', | ||
// Overlapping matches: 'lor' and 'rem' both match 'lorem', so the whole word should get | ||
// highlighted. | ||
'lor', | ||
'rem', | ||
// https://www.contentful.com/blog/unicode-javascript-and-the-emoji-family/ | ||
// Unfortunately, the library we use for highlighting seems to match only the first emoji of | ||
// such group, e.g. searching for the emoji of a son won't match a group in which the son is | ||
// present. | ||
'👩', | ||
'👨👨👦👦', | ||
'🥑', | ||
]; | ||
return ( | ||
<Flex | ||
flexDirection="column" | ||
gap={6} | ||
css={` | ||
max-width: 60ch; | ||
`} | ||
> | ||
<Flex flexDirection="column" gap={2}> | ||
<Text> | ||
Highlighting <code>{keywords.join(', ')}</code> in the below text: | ||
</Text> | ||
<Text> | ||
<Highlight text={loremIpsum} keywords={keywords} /> | ||
</Text> | ||
</Flex> | ||
|
||
<Flex flexDirection="column" gap={2}> | ||
<Text>Custom highlighting</Text> | ||
<Text> | ||
<CustomHighlight> | ||
<Highlight text={loremIpsum} keywords={keywords} /> | ||
</CustomHighlight> | ||
</Text> | ||
</Flex> | ||
</Flex> | ||
); | ||
}; | ||
|
||
const loremIpsum = | ||
'Lorem ipsum 👩👩👧👦 dolor sit amet, 👨👨👦👦 consectetur adipiscing elit. 🥑 Aliquam vel augue varius, venenatis velit sit amet, aliquam arcu. Morbi dictum mattis ultrices. Nullam ut porta ipsum, porta ornare nibh. Vivamus magna felis, semper sed enim sit amet, varius rhoncus leo. Aenean ornare convallis sem ut accumsan.'; | ||
|
||
const CustomHighlight = styled.div` | ||
mark { | ||
background-color: magenta; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/** | ||
* Copyright 2023 Gravitational, Inc | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { findAll } from 'highlight-words-core'; | ||
|
||
/** | ||
* Highlight wraps the keywords found in the text in <mark> tags. | ||
* | ||
* It is a simplified version of the component provided by the react-highlight-words package. | ||
* It can be extended with the features provided by highlight-words-core (e.g. case sensitivity). | ||
* | ||
* It doesn't handle Unicode super well because highlight-words-core uses a regex with the i flag | ||
* underneath. This means that the component will not always ignore differences in case, for example | ||
* when matching a string with the Turkish İ. | ||
*/ | ||
export function Highlight(props: { text: string; keywords: string[] }) { | ||
const chunks = findAll({ | ||
textToHighlight: props.text, | ||
searchWords: props.keywords, | ||
}); | ||
|
||
return ( | ||
<> | ||
{chunks.map((chunk, index) => { | ||
const { end, highlight, start } = chunk; | ||
const chunkText = props.text.substr(start, end - start); | ||
|
||
if (highlight) { | ||
return <mark key={index}>{chunkText}</mark>; | ||
} else { | ||
return chunkText; | ||
} | ||
})} | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,10 @@ export const createAppConfigSchema = (platform: Platform) => { | |
.boolean() | ||
.default(false) | ||
.describe('Enables collecting of anonymous usage data.'), | ||
'feature.searchBar': z | ||
.boolean() | ||
.default(false) | ||
.describe('Replaces the command bar with the new search bar'), | ||
'keymap.tab1': shortcutSchema | ||
.default(defaultKeymap['tab1']) | ||
.describe(getShortcutDesc('open tab 1')), | ||
|
@@ -68,6 +72,9 @@ export const createAppConfigSchema = (platform: Platform) => { | |
'keymap.newTab': shortcutSchema | ||
.default(defaultKeymap['newTab']) | ||
.describe(getShortcutDesc('open a new tab')), | ||
'keymap.newTerminalTab': shortcutSchema | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this related to the search bar in any way? If not, I would move it to a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is related! Before the search bar was introduced, you could open a new terminal tab by pressing Cmd + K, typing any command you want and then pressing Enter. With the addition of the search bar, there's no way at the moment to open a local terminal tab from the search bar. So we added a new item to the More Options menu to the left of the profile selector. The shortcut is displayed there to the right of the action. This way the user can still open a new terminal tab using the keyboard only. Since the plan is to backport the search bar to v12.3, I say we can just keep those commits in this PR. |
||
.default(defaultKeymap['newTerminalTab']) | ||
.describe(getShortcutDesc('open a new terminal tab')), | ||
'keymap.previousTab': shortcutSchema | ||
.default(defaultKeymap['previousTab']) | ||
.describe(getShortcutDesc('go to the previous tab')), | ||
|
@@ -118,14 +125,17 @@ export type KeyboardShortcutAction = | |
| 'tab9' | ||
| 'closeTab' | ||
| 'newTab' | ||
| 'newTerminalTab' | ||
| 'previousTab' | ||
| 'nextTab' | ||
| 'openCommandBar' | ||
| 'openConnections' | ||
| 'openClusters' | ||
| 'openProfiles'; | ||
|
||
const getDefaultKeymap = (platform: Platform) => { | ||
const getDefaultKeymap = ( | ||
platform: Platform | ||
): Record<KeyboardShortcutAction, string> => { | ||
switch (platform) { | ||
case 'win32': | ||
return { | ||
|
@@ -140,6 +150,7 @@ const getDefaultKeymap = (platform: Platform) => { | |
tab9: 'Ctrl+9', | ||
closeTab: 'Ctrl+W', | ||
newTab: 'Ctrl+T', | ||
newTerminalTab: 'Ctrl+Shift+T', | ||
previousTab: 'Ctrl+Shift+Tab', | ||
nextTab: 'Ctrl+Tab', | ||
openCommandBar: 'Ctrl+K', | ||
|
@@ -160,6 +171,7 @@ const getDefaultKeymap = (platform: Platform) => { | |
tab9: 'Alt+9', | ||
closeTab: 'Ctrl+W', | ||
newTab: 'Ctrl+T', | ||
newTerminalTab: 'Ctrl+Shift+T', | ||
previousTab: 'Ctrl+Shift+Tab', | ||
nextTab: 'Ctrl+Tab', | ||
openCommandBar: 'Ctrl+K', | ||
|
@@ -180,6 +192,7 @@ const getDefaultKeymap = (platform: Platform) => { | |
tab9: 'Command+9', | ||
closeTab: 'Command+W', | ||
newTab: 'Command+T', | ||
newTerminalTab: 'Shift+Command+T', | ||
previousTab: 'Control+Shift+Tab', | ||
nextTab: 'Control+Tab', | ||
openCommandBar: 'Command+K', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
mapAttempt
is fine as mapping a non-success attempt doesn't make sense – there's no data to map if the attempt wasn't successful. If we ever need to map the error, we can add something likemapError
. Though I wish JS had Go or Haskell-like package scoping so that we don't have to slap "attempt" into the function name to make it more clear where it comes from.