@@ -34,11 +34,33 @@ export const NavigationList = ({ className, children, ...props }: ComponentProps
34
34
)
35
35
}
36
36
37
+ // TODO: Use GDS's `useControlled` when the `OnChangeArgs` generic is added
38
+ function useControlled < T , OnChangeValue extends T = T , OnChangeArgs extends any [ ] = [ ] > (
39
+ controlledValue : T | undefined ,
40
+ defaultValue : T ,
41
+ onChange ?: ( value : OnChangeValue , ...args : OnChangeArgs ) => void ,
42
+ ) {
43
+ const [ uncontrolledValue , setUncontrolledValue ] = useState (
44
+ controlledValue !== undefined ? controlledValue : defaultValue ,
45
+ )
46
+ const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
47
+ const setValue = ( newValue : OnChangeValue , ...args : OnChangeArgs ) => {
48
+ if ( newValue === value ) return
49
+ onChange ?.( newValue , ...args )
50
+ if ( controlledValue === undefined ) {
51
+ setUncontrolledValue ( newValue )
52
+ }
53
+ }
54
+ return [ value , setValue ] as const
55
+ }
56
+
37
57
declare namespace NavigationItemProps {
38
58
interface BaseProps {
39
59
title : string
40
60
icon ?: ReactNode
41
61
selected ?: boolean | 'partially'
62
+ expanded ?: boolean
63
+ onExpandedChange ?: ( expanded : boolean , manual : boolean ) => void
42
64
}
43
65
type OmittedButtonOrLinkProps = 'title' | 'selected'
44
66
interface ButtonProps extends BaseProps , Omit < ButtonOrLinkProps . ButtonProps , OmittedButtonOrLinkProps > { }
@@ -55,14 +77,16 @@ export const NavigationItem = ({
55
77
title,
56
78
icon,
57
79
selected,
80
+ expanded : controlledExpanded ,
81
+ onExpandedChange,
58
82
onClick,
59
83
className,
60
84
children,
61
85
...props
62
86
} : NavigationItemProps ) => {
63
87
const navigationListContext = useContext ( NavigationListContext )
64
88
const depth = navigationListContext ?. depth ?? 0
65
- const [ expandedIfChildren , setExpanded ] = useState ( false )
89
+ const [ expandedIfChildren , setExpanded ] = useControlled ( controlledExpanded , false , onExpandedChange )
66
90
const expanded = children ? expandedIfChildren : false
67
91
68
92
return (
@@ -85,7 +109,7 @@ export const NavigationItem = ({
85
109
< ButtonOrLink
86
110
selected = { selected === true }
87
111
onClick = { ( event : MouseEvent < HTMLButtonElement & HTMLAnchorElement > ) => {
88
- setExpanded ( true )
112
+ setExpanded ( true , false )
89
113
onClick ?.( event )
90
114
} }
91
115
className = { `
@@ -140,7 +164,7 @@ export const NavigationItem = ({
140
164
variant = "naked"
141
165
size = "xsmall"
142
166
aria-expanded = { expanded }
143
- onClick = { ( ) => setExpanded ( ( expanded ) => ! expanded ) }
167
+ onClick = { ( ) => setExpanded ( ! expanded , true ) }
144
168
>
145
169
< CaretDown
146
170
alt = { expanded ? 'Collapse' : 'Expand' }
@@ -160,12 +184,12 @@ export const NavigationItem = ({
160
184
duration = { 300 }
161
185
mode = "exit-enter"
162
186
className = { `
163
- not-safari: group-data-[depth=1]/navigation-list:[--gds-transition-enter-translate-x:-16px]
164
- not-safari: group-data-[depth=1]/navigation-list:[--gds-transition-exit-translate-x:-16px]
187
+ group-data-[depth=1]/navigation-list:[--gds-transition-enter-translate-x:-16px]
188
+ group-data-[depth=1]/navigation-list:[--gds-transition-exit-translate-x:-16px]
165
189
not-in-group-data-[depth=1]/navigation-list:[--gds-transition-enter-opacity:1]
166
190
not-in-group-data-[depth=1]/navigation-list:[--gds-transition-exit-opacity:1]
167
- rtl:not-safari: group-data-[depth=1]/navigation-list:[--gds-transition-enter-translate-x:16px]
168
- rtl:not-safari: group-data-[depth=1]/navigation-list:[--gds-transition-exit-translate-x:16px]
191
+ rtl:group-data-[depth=1]/navigation-list:[--gds-transition-enter-translate-x:16px]
192
+ rtl:group-data-[depth=1]/navigation-list:[--gds-transition-exit-translate-x:16px]
169
193
` }
170
194
>
171
195
< NavigationList
@@ -190,9 +214,7 @@ export const NavigationItem = ({
190
214
data-expanded = { expanded || undefined }
191
215
className = { `
192
216
absolute -top-2 start-0 z-10 aspect-square w-full origin-[start] bg-space-1800 fill-none stroke-space-1500 transition duration-150
193
- safari:delay-150
194
217
not-data-[expanded]:opacity-0
195
- not-data-[expanded]:safari:delay-0
196
218
rtl:-scale-x-100
197
219
` }
198
220
>
@@ -232,9 +254,7 @@ export const NavigationItem = ({
232
254
data-expanded = { expanded || undefined }
233
255
className = { `
234
256
absolute -bottom-2 start-0 aspect-square w-full origin-[start] bg-space-1800 fill-none stroke-space-1500 transition duration-150
235
- safari:delay-150
236
257
not-data-[expanded]:opacity-0
237
- not-data-[expanded]:safari:delay-0
238
258
nearest-group-[:has(ul:not(:scope_ul_*,[inert]_*)>li:last-child[data-expanded])]/navigation-item:opacity-0
239
259
nearest-group-[:has(ul:not(:scope_ul_*,[inert]_*)>li:last-child[data-expanded])]/navigation-item:delay-150
240
260
@style-[--docs-navigation-item-last=1]:opacity-0
0 commit comments