@@ -78,6 +84,7 @@ export function EmptyState({
diff --git a/libs/shared/ui/src/lib/components/highlight-text/highlight-text.spec.tsx b/libs/shared/ui/src/lib/components/highlight-text/highlight-text.spec.tsx
new file mode 100644
index 00000000000..c80f82a36a5
--- /dev/null
+++ b/libs/shared/ui/src/lib/components/highlight-text/highlight-text.spec.tsx
@@ -0,0 +1,23 @@
+import { renderWithProviders, screen } from '@qovery/shared/util-tests'
+import HighlightText from './highlight-text'
+
+describe('HighlightText', () => {
+ it('renders plain text without a highlight', () => {
+ const { container } = renderWithProviders()
+
+ expect(container).toHaveTextContent('CPU target')
+ expect(container.querySelector('mark')).toBeNull()
+ })
+
+ it('highlights every case-insensitive match', () => {
+ renderWithProviders()
+
+ expect(screen.getAllByText(/cpu/i, { selector: 'mark' }).map((mark) => mark.textContent)).toEqual(['CPU', 'cpu'])
+ })
+
+ it('treats regular expression characters literally', () => {
+ renderWithProviders()
+
+ expect(screen.getByText('(sec', { selector: 'mark' })).toBeInTheDocument()
+ })
+})
diff --git a/libs/shared/ui/src/lib/components/highlight-text/highlight-text.tsx b/libs/shared/ui/src/lib/components/highlight-text/highlight-text.tsx
new file mode 100644
index 00000000000..5277dfc123d
--- /dev/null
+++ b/libs/shared/ui/src/lib/components/highlight-text/highlight-text.tsx
@@ -0,0 +1,28 @@
+export interface HighlightTextProps {
+ text: string
+ highlight?: string
+}
+
+const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+
+export function HighlightText({ text, highlight }: HighlightTextProps) {
+ const query = highlight?.trim()
+ // Split with a capture group puts matches at odd indexes.
+ const parts = query ? text.split(new RegExp(`(${escapeRegExp(query)})`, 'gi')) : [text]
+
+ return (
+ <>
+ {parts.map((part, index) =>
+ index % 2 === 1 ? (
+
+ {part}
+
+ ) : (
+ part
+ )
+ )}
+ >
+ )
+}
+
+export default HighlightText
diff --git a/libs/shared/ui/src/lib/components/inputs/input-search/input-search.tsx b/libs/shared/ui/src/lib/components/inputs/input-search/input-search.tsx
index 87c16a5ebfb..2dfae78a8ad 100644
--- a/libs/shared/ui/src/lib/components/inputs/input-search/input-search.tsx
+++ b/libs/shared/ui/src/lib/components/inputs/input-search/input-search.tsx
@@ -3,7 +3,9 @@ import { twMerge } from '@qovery/shared/util-js'
import Icon from '../../icon/icon'
export interface InputSearchProps {
+ ariaLabel?: string
placeholder?: string
+ defaultValue?: string
className?: string
onChange?: (value: string) => void
isEmpty?: boolean
@@ -14,7 +16,9 @@ export interface InputSearchProps {
export function InputSearch(props: InputSearchProps) {
const {
+ ariaLabel,
placeholder = '',
+ defaultValue,
className = '',
onChange,
isEmpty = false,
@@ -24,7 +28,7 @@ export function InputSearch(props: InputSearchProps) {
} = props
const ref = useRef(null)
- const [toggleDelete, setToggleDelete] = useState(false)
+ const [toggleDelete, setToggleDelete] = useState(Boolean(defaultValue))
const getValue = (value: string) => {
if (onChange) onChange(value)
@@ -57,6 +61,8 @@ export function InputSearch(props: InputSearchProps) {
customSize
)}
type="text"
+ aria-label={ariaLabel}
+ defaultValue={defaultValue}
placeholder={placeholder}
disabled={false}
onChange={(e) => getValue(e.currentTarget.value)}
diff --git a/libs/shared/ui/src/lib/components/inputs/input-select-small/input-select-small.tsx b/libs/shared/ui/src/lib/components/inputs/input-select-small/input-select-small.tsx
index f4e27e2b855..b00f5c2880a 100644
--- a/libs/shared/ui/src/lib/components/inputs/input-select-small/input-select-small.tsx
+++ b/libs/shared/ui/src/lib/components/inputs/input-select-small/input-select-small.tsx
@@ -6,6 +6,7 @@ import Icon from '../../icon/icon'
export interface InputSelectSmallProps {
name: string
label?: string
+ ariaLabel?: string
items: Value[]
getValue?: (name: string, value: Value | null) => void
className?: string
@@ -20,6 +21,7 @@ export function InputSelectSmall(props: InputSelectSmallProps) {
const {
name,
label,
+ ariaLabel,
items,
defaultValue,
className = '',
@@ -54,6 +56,7 @@ export function InputSelectSmall(props: InputSelectSmallProps) {
)
diff --git a/libs/shared/ui/src/lib/components/inputs/input-text-small/input-text-small.tsx b/libs/shared/ui/src/lib/components/inputs/input-text-small/input-text-small.tsx
index caaa2e89c0e..8f3536aae2d 100644
--- a/libs/shared/ui/src/lib/components/inputs/input-text-small/input-text-small.tsx
+++ b/libs/shared/ui/src/lib/components/inputs/input-text-small/input-text-small.tsx
@@ -13,6 +13,7 @@ import Tooltip from '../../tooltip/tooltip'
export interface InputTextSmallProps {
name: string
+ id?: string
type?: string
onChange?: ChangeEventHandler
onKeyDown?: KeyboardEventHandler
@@ -39,6 +40,7 @@ export const InputTextSmall = forwardRef(
) {
const {
name,
+ id,
value,
placeholder,
error,
@@ -59,6 +61,7 @@ export const InputTextSmall = forwardRef(
autoFocus = false,
} = props
+ const inputId = id ?? label
const [focused, setFocused] = useState(false)
const [currentType, setCurrentType] = useState(type)
@@ -90,7 +93,7 @@ export const InputTextSmall = forwardRef(
'input--disabled': disabled,
})}
>
-