+ {/* Left Button */}
+ {scrollPosition > 0 &&
+
+ }
+
+ {/* Right Button */}
+
+
+
+ {/* Carousel */}
+
+ {/* TODO software card type */}
+ {cards.length > 0 && cards.map((card: any, index: number) => (
+
+
+
+ ))
+ }
+
+
+ )
+}
diff --git a/frontend/components/softwarePage/SearchInput.tsx b/frontend/components/softwarePage/SearchInput.tsx
new file mode 100644
index 000000000..c2156e70f
--- /dev/null
+++ b/frontend/components/softwarePage/SearchInput.tsx
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all)
+// SPDX-FileCopyrightText: 2022 - 2023 dv4all
+//
+// SPDX-License-Identifier: Apache-2.0
+
+import {useState, useEffect} from 'react'
+import {useDebounce} from '~/utils/useDebounce'
+import TextField from '@mui/material/TextField'
+
+type SearchInputProps = {
+ placeholder: string,
+ onSearch: Function,
+ delay?: number,
+ defaultValue?: string,
+}
+
+export default function SearchInput({
+ placeholder,
+ onSearch,
+ delay = 400,
+ defaultValue = ''
+}: SearchInputProps) {
+ const [state, setState] = useState({
+ value: defaultValue ?? '',
+ wait: true
+ })
+ const searchFor = useDebounce(state.value, delay)
+
+ useEffect(() => {
+ if ((searchFor !== '' && defaultValue === '') || defaultValue !== '') {
+ setState({value: defaultValue, wait: true})
+ }
+ }, [searchFor, defaultValue])
+
+ useEffect(() => {
+ let abort = false
+ const {wait, value} = state
+ if (!wait && value === searchFor) {
+ if (abort) return
+ setState({
+ wait: true,
+ value
+ })
+ onSearch(searchFor)
+ }
+ return () => {
+ abort = true
+ }
+ }, [state, searchFor, onSearch])
+
+ return (
+