Skip to content

Commit

Permalink
Merge branch 'main' into artie-preview-fail
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtieReus authored Oct 18, 2024
2 parents b4c6966 + 436f35a commit 0c95e79
Show file tree
Hide file tree
Showing 17 changed files with 425 additions and 20 deletions.
6 changes: 6 additions & 0 deletions .changeset/eighty-steaks-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cloudoperators/juno-app-heureka": patch
"@cloudoperators/juno-app-greenhouse": patch
---

This fixes the embedded prop handling, ensuring it is passed correctly to AppShell.
5 changes: 5 additions & 0 deletions .changeset/perfect-socks-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-ui-components": minor
---

Migrate the Search Input component to TypeScript
10 changes: 2 additions & 8 deletions apps/heureka/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useLayoutEffect } from "react"
import React from "react"
import styles from "./styles.scss?inline"
import { AppShellProvider, CodeBlock } from "@cloudoperators/juno-ui-components"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { MessagesProvider } from "@cloudoperators/juno-messages-provider"
import AsyncWorker from "./components/AsyncWorker"
import { ErrorBoundary } from "react-error-boundary"
import { useGlobalsActions, StoreProvider } from "./components/StoreProvider"
import { StoreProvider } from "./components/StoreProvider"
import PanelManager from "./components/shared/PanelManager"
import CustomAppShell from "./components/CustomAppShell"

function App(props = {}) {
const { setEmbedded, setApiEndpoint } = useGlobalsActions()
const preErrorClasses = `
custom-error-pre
border-theme-error
Expand All @@ -24,11 +23,6 @@ function App(props = {}) {
w-full
`

useLayoutEffect(() => {
setApiEndpoint(props.endpoint)
if (props.embedded === "true" || props.embedded === true) setEmbedded(true)
}, [])

const fallbackRender = ({ error }) => {
return (
<div className="w-1/2">
Expand Down
5 changes: 3 additions & 2 deletions apps/heureka/src/components/CustomAppShell.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import React from "react"
import { AppShell, PageHeader, TopNavigation, TopNavigationItem } from "@cloudoperators/juno-ui-components"
import { useGlobalsActions, useGlobalsActiveView } from "./StoreProvider"
import { useGlobalsActions, useGlobalsActiveView, useGlobalsEmbedded } from "./StoreProvider"
import ServicesView from "./services/ServicesView"
import IssueMatchesView from "./issueMatches/IssueMatchesView"
import ComponentsView from "./components/ComponentsView"
Expand All @@ -21,6 +21,7 @@ const VIEW_CONFIG = {
const CustomAppShell = ({ children }) => {
const { setActiveView, setShowPanel } = useGlobalsActions()
const activeView = useGlobalsActiveView()
const embedded = useGlobalsEmbedded()

const handleNavItemChange = (item) => {
setActiveView(item)
Expand All @@ -40,7 +41,7 @@ const CustomAppShell = ({ children }) => {
const ActiveComponent = VIEW_CONFIG[activeView]?.component

return (
<AppShell pageHeader={<PageHeader heading="Converged Cloud | Heureka" />} topNavigation={topNavigation}>
<AppShell pageHeader={<PageHeader heading="Heureka" />} topNavigation={topNavigation} embedded={embedded}>
{ActiveComponent && <ActiveComponent />}
{children}
</AppShell>
Expand Down
4 changes: 2 additions & 2 deletions apps/heureka/src/hooks/useQueryClientFn.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const useQueryClientFn = () => {
As stated in getQueryDefaults, the order of registration of query defaults does matter. Since the first matching defaults are returned by getQueryDefaults, the registration should be made in the following order: from the least generic key to the most generic one. This way, in case of specific key, the first matching one would be the expected one.
*/
useEffect(() => {
if (!queryClient || !endpoint) return
if (!queryClient) return

// Services main query
queryClient.setQueryDefaults(["ServicesMain"], {
Expand Down Expand Up @@ -135,7 +135,7 @@ const useQueryClientFn = () => {

// Set queryClientFnReady to true once
setQueryClientFnReady(true)
}, [queryClient, endpoint])
}, [queryClient])
}

export default useQueryClientFn
2 changes: 1 addition & 1 deletion apps/heureka/src/lib/slices/createGlobalsSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import constants from "../../components/shared/constants"

const createGlobalsSlice = (set, get, options) => ({
globals: {
embedded: false, //Set to true if app is to be embedded in another existing app or page.
embedded: options?.embedded === true || options?.embedded === "true", //Set to true if app is to be embedded in another existing app or page.
apiEndpoint: options?.apiEndpoint, //The API endpoint to use for fetching data.
isUrlStateSetup: false, //Set to true when the URL state has been set up.
queryClientFnReady: false, //Set to true when the queryClient function is ready to be used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from "react"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Filters } from "./index"
import { SearchInput } from "../SearchInput/index"
import { SearchInput } from "../../deprecated_js/SearchInput"

describe("Filters", () => {
test("renders Filters", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import PropTypes from "prop-types"
import React, { useContext, useLayoutEffect } from "react"
import * as themes from "./themes"
import { SearchInput } from "../SearchInput/SearchInput.component"
import { SearchInput } from "../../deprecated_js/SearchInput"

// DEFAULT THEME (DARK)
const DEFAULT_THEME = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect, ChangeEvent, KeyboardEvent, MouseEvent, useCallback } from "react"

import { Icon } from "../Icon"
import { Stack } from "../Stack"

import "./searchinput.scss"

export interface SearchInputProps {
/**
* Specifies the name attribute for the input element.
*/
name?: string
/**
* Determines the visual styling variant of the SearchInput component.
* - "default": Standard search input styling.
* - "hero": A larger search input intended for standalone use on a dedicated search page, akin to the initial Google search page.
* - "rounded": A search input with rounded edges.
*/
variant?: "rounded" | "hero" | "default"
/**
* Disables the search input when set to true.
*/
disabled?: boolean
/**
* Custom placeholder text displayed in the search input.
*/
placeholder?: string
/**
* Initial value for the search input.
*/
value?: string
/**
* Controls the autocomplete attribute of the input element.
* Pass a valid autocomplete value.
* We do not enforce validity.
*/
autoComplete?: string
/**
* Determines whether to show the 'Clear' button.
*/
clear?: boolean
/**
* Pass an optional CSS class to apply to the search input.
*/
className?: string
/**
* Callback function invoked when a search is triggered, either by pressing the 'Enter' key or by clicking the search icon.
*/
onSearch?: (_value: string) => void
/**
* Click handler for the search icon.
*/
onClick?: (_event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void
/**
* Change handler for the search input.
*/
onChange?: (_event: ChangeEvent<HTMLInputElement>) => void
/**
* KeyPress handler for the search input. By default, triggers the onSearch function when the 'Enter' key is pressed.
*/
onKeyPress?: (_event: KeyboardEvent<HTMLInputElement>) => void
/**
* Click handler for the 'Clear' button.
*/
onClear?: (_event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void
}

const getWrapperStyles = (variant: "rounded" | "hero" | "default"): string => {
const baseStyles = "jn-relative jn-inline-block jn-win-max"
switch (variant) {
case "rounded":
return `${baseStyles} jn-w-auto`
case "hero":
return `${baseStyles} jn-w-full`
default:
return `${baseStyles} jn-w-auto`
}
}

const getSearchStyles = (variant: "rounded" | "hero" | "default"): string => {
const baseStyles = `
jn-bg-theme-textinput
jn-text-theme-high
jn-shadow
jn-w-full
focus:jn-outline-none
focus:jn-ring-2
focus:jn-ring-theme-focus
disabled:jn-cursor-not-allowed
disabled:jn-opacity-50
`

const roundedStyles = "jn-rounded-full focus:jn-rounded-full"
switch (variant) {
case "rounded":
return `${baseStyles} ${roundedStyles} jn-text-base jn-w-auto jn-pl-3 jn-pr-16 jn-py-1`
case "hero":
return `${baseStyles} ${roundedStyles} jn-text-lg jn-w-full jn-pl-6 jn-pr-20 jn-py-2.5`
default:
return `${baseStyles} jn-rounded jn-text-base jn-leading-4 jn-pl-4 jn-pr-16 jn-py-2.5`
}
}

const getIconWrapperStyles = (variant: "rounded" | "hero" | "default"): string => {
switch (variant) {
case "rounded":
return "jn-absolute jn-inline-flex jn-right-3 jn-top-1"
case "hero":
return "jn-absolute jn-inline-flex jn-right-5"
default:
return "jn-absolute jn-inline-flex jn-right-3 jn-top-2"
}
}

const getClearIconStyles = (variant: "rounded" | "hero" | "default"): string => {
switch (variant) {
case "hero":
return "jn-mr-2.5"
default:
return "jn-mr-2"
}
}

const getClearIconSize = (variant: "rounded" | "hero" | "default"): string => {
return variant === "hero" ? "24" : "18"
}

/**
* A SearchInput is a controlled input component for searching.
* It provides a text field to enter a search query and optional clear and search icons.
* Three styling variants are supported: "rounded", "hero", and "default".
*/
export const SearchInput: React.FC<SearchInputProps> = ({
value = "",
name = "search",
variant = "default",
disabled = false,
clear = true,
onSearch,
onChange,
onClick,
onKeyPress,
onClear,
autoComplete = "off",
placeholder = "Search…",
className = "",
...props
}) => {
const [val, setValue] = useState(value)

useEffect(() => {
setValue(value)
}, [value])

const handleInputChange = useCallback(
(event: ChangeEvent<HTMLInputElement>): void => {
setValue(event.target.value)
onChange?.(event)
},
[onChange]
)

const handleKeyPress = useCallback(
(event: KeyboardEvent<HTMLInputElement>): void => {
if (event.key === "Enter" && onSearch) {
onSearch(val)
}
onKeyPress?.(event)
},
[onSearch, onKeyPress, val]
)

const handleSearchClick = useCallback(
(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>): void => {
onSearch?.(val)
onClick?.(event)
},
[onSearch, onClick, val]
)

const handleClearClick = useCallback(
(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>): void => {
setValue("")
onClear?.(event)
},
[onClear]
)

return (
<div className={`juno-search-input-wrapper ${getWrapperStyles(variant)} ${className}`} role="search">
<Stack gap="2" alignment="center">
<input
type="search"
name={name}
placeholder={placeholder}
disabled={disabled}
value={val}
autoComplete={autoComplete}
className={`juno-search-input ${getSearchStyles(variant)}`}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
{...props}
/>
<div className={getIconWrapperStyles(variant)}>
{clear && val?.length > 0 && (
<Icon
icon="close"
size={getClearIconSize(variant)}
title="Clear"
className={getClearIconStyles(variant)}
onClick={handleClearClick}
disabled={disabled}
/>
)}
<Icon icon="search" title="Search" onClick={handleSearchClick} disabled={disabled} />
</div>
</Stack>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { SearchInput } from "./index.js"
import { SearchInput } from "./"

export default {
title: "Components/SearchInput",
Expand Down
Loading

0 comments on commit 0c95e79

Please sign in to comment.