Skip to content

Commit

Permalink
feat(Navigation): new pinned feature and reorder (#3717)
Browse files Browse the repository at this point in the history
* feat(Navigation): new pinned feature and reorder

* fix: navigation to have better pin unpin handling

* fix: spacing

* fix: alignement ccursor and empty state on pinned items

* fix: feedback dodo

* fix: item
  • Loading branch information
matthprost committed May 2, 2024
1 parent 34e6b00 commit 87c3a99
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 119 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-badgers-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ultraviolet/plus": minor
---

New pinned feature on `<Navigation />` you can enable it using prop `pinnedFeature={true}` on `<NavigationProvider />`
83 changes: 72 additions & 11 deletions packages/plus/src/components/Navigation/NavigationProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import type { ReactNode, RefObject } from 'react'
import type { Dispatch, ReactNode, Reducer, RefObject } from 'react'
import {
createContext,
useCallback,
useContext,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
import { ANIMATION_DURATION, NAVIGATION_WIDTH } from './constants'
import NavigationLocales from './locales/en'

type Item = {
label: string
active?: boolean
onClick?: (toggle?: true | false) => void
}

type Items = Record<string, Item>

type ContextProps = {
expanded: boolean
toggleExpand: (toggle?: boolean) => void
Expand All @@ -24,6 +33,22 @@ type ContextProps = {
locales: typeof NavigationLocales
width: number
setWidth: (width: number) => void
/**
* This function will reorder the pinned items based on the initial index and
* the end index.
*/
reorderItems: (
/**
* The initial index of the item
*/
initialIndex: number,
/**
* The end index of the item
*/
endIndex: number,
) => void
items: Items
registerItem: Dispatch<Items>
}

export const NavigationContext = createContext<ContextProps>({
Expand All @@ -42,6 +67,9 @@ export const NavigationContext = createContext<ContextProps>({
navigationRef: { current: null },
width: NAVIGATION_WIDTH,
setWidth: () => {},
reorderItems: () => {},
items: {},
registerItem: () => {},
})

export const useNavigation = () => useContext(NavigationContext)
Expand Down Expand Up @@ -69,37 +97,57 @@ type NavigationProviderProps = {
* navigation will be expanded by default otherwise it will be collapsed
*/
initialExpanded?: boolean
/**
* This function is triggered when the user click on the pin/unpin button
* of an item
*/
onClickPinUnpin?: (pinned: string[]) => void
locales?: typeof NavigationLocales
/**
* This function is triggered when user click on expand button on the footer
* of the navigation. This is not triggered when the user resize the navigation
* and it automatically collapse / expand.
*/
onClickExpand?: (expanded: boolean) => void
/**
* This function is triggered when the user click on the pin/unpin button
* of an item. To access all pinned item you can use the `useNavigation` hook
* and access the `pinnedItems` property.
*/
onClickPinUnpin?: (
/**
* The state of the item after the click
*/
state: 'pin' | 'unpin',
/**
* The current items that has been pinned on click
*/
pinned: string,
) => void
}

export const NavigationProvider = ({
children,
pinnedFeature = false,
onClickPinUnpin,
initialPinned,
initialExpanded = true,
locales = NavigationLocales,
pinLimit = 7,
onClickExpand,
initialWidth = NAVIGATION_WIDTH,
onClickPinUnpin,
}: NavigationProviderProps) => {
const [expanded, setExpanded] = useState(initialExpanded)
const [pinnedItems, setPinnedItems] = useState<string[]>(initialPinned ?? [])
const [animation, setAnimation] = useState<boolean | 'expand' | 'collapse'>(
false,
)
const [width, setWidth] = useState<number>(initialWidth)

// This is used to store the items that are registered in the navigation
// This way we can retrieve items with their active state in pinned feature
const [items, registerItem] = useReducer<Reducer<Items, Items>>(
(oldState: Items, newState: Items) => ({
...oldState,
...newState,
}),
{},
)
const navigationRef = useRef<HTMLDivElement>(null)

// This function will be triggered when expand/collapse button is clicked
Expand Down Expand Up @@ -133,21 +181,29 @@ export const NavigationProvider = ({
const pinItem = useCallback(
(item: string) => {
setPinnedItems([...pinnedItems, item])

onClickPinUnpin?.(pinnedItems)
onClickPinUnpin?.('pin', item)
},
[onClickPinUnpin, pinnedItems],
)

const unpinItem = useCallback(
(item: string) => {
setPinnedItems(pinnedItems.filter(localItem => localItem !== item))

onClickPinUnpin?.(pinnedItems)
onClickPinUnpin?.('unpin', item)
},
[onClickPinUnpin, pinnedItems],
)

const reorderItems = useCallback(
(initialIndex: number, endIndex: number) => {
const newPinnedItems = [...pinnedItems]
const [removed] = newPinnedItems.splice(initialIndex, 1)
newPinnedItems.splice(endIndex, 0, removed)
setPinnedItems(newPinnedItems)
},
[pinnedItems],
)

const value = useMemo(
() => ({
expanded,
Expand All @@ -163,6 +219,9 @@ export const NavigationProvider = ({
navigationRef,
width,
setWidth,
reorderItems,
registerItem,
items,
}),
[
expanded,
Expand All @@ -175,6 +234,8 @@ export const NavigationProvider = ({
pinLimit,
animation,
width,
reorderItems,
items,
],
)

Expand Down

0 comments on commit 87c3a99

Please sign in to comment.