Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7040aba
[ui] add internal Navigation, NavigtaionItem components WIP
franzheidl Apr 9, 2024
0127dda
[ui] NavigationIem: add stories
franzheidl Apr 9, 2024
9b7a564
[ui] Navigation: add first test
franzheidl Apr 10, 2024
3c463d0
[ui] Navigation: remove unnecessary wrapper div
franzheidl Apr 10, 2024
62174d5
[ui] NavigationItem: render button or link
franzheidl Apr 10, 2024
0adfc28
[ui] NavigationItem: add wrapperClassName prop, aria-disabled and ari…
franzheidl Apr 10, 2024
eb029ef
[ui] Navigation: add aria-disabled attribute
franzheidl Apr 10, 2024
a6907be
[ui] Navigation: add more tests
franzheidl Apr 10, 2024
235198f
[ui] NavigationItem: properly disable link items, too
franzheidl Apr 10, 2024
898ea20
[ui] Navigation: add className, arbitrary props test
franzheidl Apr 10, 2024
92d1d7e
[ui] Navigation: render arbitrary props
franzheidl Apr 10, 2024
96986d5
[ui] Navigation, -Item: add link stories
franzheidl Apr 10, 2024
0495131
[ui] Navigation: add WIP stories
franzheidl Apr 10, 2024
e73c6b1
Merge branch 'main' into navigation-rework
franzheidl Apr 24, 2024
9245995
[ui] fix storybook build for now
franzheidl Apr 24, 2024
119d9cd
[ui] Navigation: WIP: determine selected item by key or any relevant …
franzheidl Apr 24, 2024
2e02bc4
[ui] Navigation: stories: add missing keys
franzheidl Apr 24, 2024
9b40e9e
[ui] Navigation: add more tests
franzheidl Apr 25, 2024
bf673d1
[ui] Navigation: add more tests for setting the activeItem
franzheidl Apr 30, 2024
6dea001
[ui] Navigation: add re-rendering tests for activeItem prop
franzheidl Apr 30, 2024
0cb2a0a
Automatic application of license header
Apr 30, 2024
453b20c
[ui] Navigation: add remaining tests for settign the active item init…
franzheidl Apr 30, 2024
f68f373
[ui] Navigation: add skipped test
franzheidl Apr 30, 2024
d2efba2
[ui] Navigation: actually skip test as per prev commit message
franzheidl Apr 30, 2024
71207b5
[ui] NavigationItem: make sure component renders ok outside of parent…
franzheidl May 2, 2024
39c3005
[ui] Navigation, -Item: add navigationItem tests
franzheidl May 2, 2024
3ec71b6
Merge branch 'main' into navigation-rework
franzheidl May 2, 2024
c16f6ba
Automatic application of license header
May 2, 2024
b707508
[ui] Navigation: add another (skipped) test
franzheidl May 2, 2024
86b5bf7
[ui] Navigation: fix typo in test description
franzheidl May 2, 2024
03fa054
[ui] Navigation: re-work tests so they work with cross-ontext/-compon…
franzheidl May 7, 2024
55d86e2
[ui] NavigationItem: Tests. add comment
franzheidl May 7, 2024
d871987
[ui] NavigationItem: render icon
franzheidl May 7, 2024
2f016c3
[ui] NavigationItem: center items (icon etc.)
franzheidl May 7, 2024
f20f940
Merge branch 'main' into navigation-rework
franzheidl May 8, 2024
f6db720
[ui] Navigation: rename prop for clarity
franzheidl May 8, 2024
9b7ebcb
[ui] NavigationItem: Test: reflect changed prop name in test
franzheidl May 8, 2024
5c9bf2b
[ui] Navigation: don’t use default navigationRole prop
franzheidl May 8, 2024
57e091f
[ui] NavigationItem: accept activeItemStyles prop
franzheidl May 8, 2024
9012174
[ui] SideNavigation, -Item: rework to thin wrappers around Navigation…
franzheidl May 8, 2024
4875266
[ui] SideNavigationItem: remove obsolete imports
franzheidl May 10, 2024
7583110
[ui] SideNavigation: pass navigationRole prop to Navigation
franzheidl May 10, 2024
0c28bb3
[ui] NavigationItem: render combined `[navigationRole]-item-active` c…
franzheidl May 10, 2024
f0f2ac2
[ui] TopNavigation, -Item: re-work to wrap Navigation, -Item components
franzheidl May 10, 2024
eca9264
[ui] NavigationItem: allow passing inactive Styles that would otherwi…
franzheidl May 10, 2024
ce13136
[ui] TabNavigation, -Item: re-work to wrap Navigation, -Item
franzheidl May 10, 2024
571b6d6
[ui] TabNavigation, -Item: render a class to represent tabStyle
franzheidl May 10, 2024
c2bfb85
[ui] TabNavigation, -Item: make tests work
franzheidl May 10, 2024
856801c
[ui] Navigation: add docs
franzheidl May 11, 2024
ba6b5c6
[ui] NavigationItem: remove activeStyles, add docs, fix ariaLabel pro…
franzheidl May 11, 2024
b352c93
[ui] Side-, Tab-, TopNavigationItem: add bold styles for active state
franzheidl May 11, 2024
ff1dd64
Merge branch 'main' into navigation-rework
franzheidl May 13, 2024
cda1680
[ui] Navigation: remove `navigationRole` prop
franzheidl May 14, 2024
e9b9445
[ui] bump version to 2.14.0
franzheidl May 14, 2024
6454f14
[ui] TabNavigation. TopNavigation: Update story descriptions
franzheidl May 14, 2024
9e99110
Merge branch 'main' into navigation-rework
franzheidl May 14, 2024
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
2 changes: 1 addition & 1 deletion libs/juno-ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"module": "build/index.js",
"source": "src/index.js",
"style": "build/lib/variables.css",
"version": "2.13.8",
"version": "2.14.0",
"files": [
"src/colors.css",
"tailwind.config.js"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { createContext, useEffect, useState } from "react"
import PropTypes from "prop-types"
import { NavigationItem } from "../NavigationItem/"

export const NavigationContext = createContext()

/** A generic Navigation component providing all the necessary functionality for a navigation. For internal use only. Not to be used directly, but to be wrapped by more role-specific / semantic navigation components such as `TabNavigation`, `TopNavigation`, `SideNavigation`. */
export const Navigation = ({
activeItem,
ariaLabel,
children,
className,
disabled,
onActiveItemChange,
onChange,
...props
}) => {
const [activeItm, setActiveItm] = useState("")
const [items, setItems] = useState(new Map())

const findItemIdByKeyValue = (valueToFind) => {
// The prioritized sequence of individual item keys to check for a value:
const prioritizedKeys = ["value", "children", "label"]
const itemsKeys = Array.from(items.keys())
if (itemsKeys.includes(valueToFind)) {
// return the value if it is found in the keys of the items map
return valueToFind
} else {
// If the value is not found in the keys of the items map, search for the value in the individual items according to the sequence in prioritizedKeys. If a matching item is found, return its id or null:
let foundItemId
for (let [key, obj] of items.entries()) {
prioritizedKeys.forEach((pKey) => {
if (obj[pKey] === valueToFind) {
foundItemId = obj.id
}
})
}
return foundItemId
}
}

useEffect(() => {
if (activeItem) {
const activeItemId = findItemIdByKeyValue(activeItem)
setActiveItm(activeItemId)
}
}, [activeItem])

// Re-evaluate active item when items map changes (essential to set the active item properly on first render!):
useEffect(() => {
if (activeItem) {
const activeItemId = findItemIdByKeyValue(activeItem)
setActiveItm(activeItemId)
}
}, [items])

// Key is set as established by the child item according to priority: value || children || label
const addItem = (key, children, label, value) => {
setItems((oldMap) =>
new Map(oldMap).set(key, {
id: key, // store the associated key of the item in the map inside the object, so we can easily get the key later if we have to find an object by any of its keys
value: value,
label: label,
children: children,
displayName: children || label || value, // priority of what to actually render in each item
})
)
}

const handleActiveItemChange = (key) => {
setActiveItm(key)
onActiveItemChange && onActiveItemChange(key)
}

return (
<NavigationContext.Provider
value={{
activeItem: activeItm,
addItem: addItem,
handleActiveItemChange: handleActiveItemChange,
navigationDisabled: disabled,
}}
>
<ul
aria-disabled={disabled ? true : null}
aria-label={ariaLabel && ariaLabel.length ? ariaLabel : null}
className={`juno-navigation
${disabled ? "juno-navigation-disabled" : ""}
${className}`}
role="navigation"
{...props}
>
{children}
</ul>
</NavigationContext.Provider>
)
}

// TODO: validate whether children are instances of NavigationItem

Navigation.propTypes = {
/** The currently active item. Pass the `value`, `label` prop, or the child string of the respective NavigationItem. */
activeItem: PropTypes.string,
/** The aria label of the navigation */
arialLabel: PropTypes.string,
/** The child navigation items of the navigation */
children: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
/** Pass a custom className to the navigation parent element */
className: PropTypes.string,
/** Whether the navigation is disabled. Will disable all children. */
disabled: PropTypes.bool,
/** Handler to execute when the active item changes. Alias to `onChange`. */
onActiveItemChange: PropTypes.func,
/** Handler to execute when the active item changes. Alias to `onActiveItemChange`. */
onChange: PropTypes.func,
}

Navigation.defaultProps = {
activeItem: "",
ariaLabel: "",
children: null,
className: "",
disabled: false,
onActiveItemChange: undefined,
onChange: undefined,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react"
import { Navigation } from "./index.js"
import { NavigationItem } from "../NavigationItem/"

export default {
title: "Internal/Navigation",
component: Navigation,
argTypes: {
children: {
control: false,
},
role: {
options: ["TabNavigation", "TopNavigation", "SideNavigation"],
control: { type: "select" },
},
},
}

const Template = ({ children, ...props }) => (
<Navigation {...props}>{children}</Navigation>
)

export const DefaultWithChildren = {
render: Template,
args: {
activeItem: "Item 1",
children: [
<NavigationItem key="i-1">Item 1</NavigationItem>,
<NavigationItem key="i-2">Item 2</NavigationItem>,
<NavigationItem key="i-3">Item 3</NavigationItem>,
<NavigationItem key="i-4" disabled>
Item 4
</NavigationItem>,
],
},
}

export const WithValuesAndLabels = {
render: Template,
args: {
children: [
<NavigationItem key="i-1" value="i-1" label="Item 1" />,
<NavigationItem key="i-2" value="i-2" label="Item 2" />,
<NavigationItem key="i-3" value="i-3" label="Item 3" />,
],
},
}

export const WithValuesLabelsAndChildren = {
render: Template,
args: {
children: [
<NavigationItem key="i-1" value="i-1" label="Item 1 Label">
Item 1
</NavigationItem>,
<NavigationItem key="i-2" value="i-2" label="Item 2 Label">
Item 2
</NavigationItem>,
<NavigationItem key="i-3" value="i-3" label="Item 3 Label">
Item 3
</NavigationItem>,
],
},
}

export const ValuesOnly = {
render: Template,
args: {
children: [
<NavigationItem key="i-1" value="Item 1" />,
<NavigationItem key="i-2" value="Item 2" active />,
<NavigationItem key="i-3" value="Item 3" />,
],
},
}

export const WithActiveItemByValue = {
render: Template,
args: {
activeItem: "item-2",
children: [
<NavigationItem key="i-1" value="item-1" label="Item 1" />,
<NavigationItem key="i-2" value="item-2" label="Item 2" />,
<NavigationItem key="i-3" value="item-3" label="Item 3" />,
],
},
}

// TODO:
export const WithActiveItemByLabel = {
render: Template,
args: {
activeItem: "Item 2",
children: [
<NavigationItem key="i-1" value="item-1" label="Item 1" />,
<NavigationItem key="i-2" value="item-2" label="Item 2" />,
<NavigationItem key="i-3" value="item-3" label="Item 3" />,
],
},
}

// TODO:
export const WithActiveItemByChild = {
render: Template,
args: {
activeItem: "Item 2",
children: [
<NavigationItem key="i-1" value="itm-1">
Item 1
</NavigationItem>,
<NavigationItem key="i-2" value="itm-2">
Item 2
</NavigationItem>,
<NavigationItem key="i-3" value="itm-3">
Item 3
</NavigationItem>,
],
},
}

export const Disabled = {
render: Template,
args: {
disabled: true,
children: [
<NavigationItem key="i-1">Item 1</NavigationItem>,
<NavigationItem key="i-2" active>
Item 2
</NavigationItem>,
<NavigationItem key="i-3">Item 3</NavigationItem>,
],
},
}

export const ItemsAsLinks = {
render: Template,
args: {
children: [
<NavigationItem href="https://www.sap.com" key="i-1">
Link 1
</NavigationItem>,
<NavigationItem href="https://www.sap.com" key="i-2">
Link 2
</NavigationItem>,
<NavigationItem href="https://www.sap.com" key="i-3">
Link 3
</NavigationItem>,
],
},
}
Loading