Skip to content

Commit 4dcc083

Browse files
committed
feat(ui-navigation): rework AppNav
INSTUI-4783
1 parent af1fb17 commit 4dcc083

14 files changed

Lines changed: 836 additions & 18 deletions

File tree

packages/__docs__/src/App/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,10 @@ class App extends Component<AppProps, AppState> {
569569

570570
renderThemeSelect() {
571571
const allThemeKeys = Object.keys(this.state.docsData!.themes)
572-
const showRebrandThemes =
573-
this.state.showMinorVersionSelector &&
574-
this.state.selectedMinorVersion !== 'v11_6'
572+
// const showRebrandThemes =
573+
// this.state.showMinorVersionSelector &&
574+
// this.state.selectedMinorVersion !== 'v11_6'
575+
const showRebrandThemes = true // TODO temp workaround
575576
const themeKeys = showRebrandThemes
576577
? allThemeKeys
577578
: allThemeKeys.filter((key) => !key.startsWith('rebrand'))

packages/ui-navigation/package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,22 @@
7272
"default": "./es/exports/a.js"
7373
},
7474
"./v11_6": {
75-
"types": "./types/exports/a.d.ts",
76-
"import": "./es/exports/a.js",
77-
"require": "./lib/exports/a.js",
78-
"default": "./es/exports/a.js"
75+
"types": "./types/exports/b.d.ts",
76+
"import": "./es/exports/b.js",
77+
"require": "./lib/exports/b.js",
78+
"default": "./es/exports/b.js"
7979
},
8080
"./v11_7": {
81-
"types": "./types/exports/a.d.ts",
82-
"import": "./es/exports/a.js",
83-
"require": "./lib/exports/a.js",
84-
"default": "./es/exports/a.js"
81+
"types": "./types/exports/b.d.ts",
82+
"import": "./es/exports/b.js",
83+
"require": "./lib/exports/b.js",
84+
"default": "./es/exports/b.js"
8585
},
8686
"./latest": {
87-
"types": "./types/exports/a.d.ts",
88-
"import": "./es/exports/a.js",
89-
"require": "./lib/exports/a.js",
90-
"default": "./es/exports/a.js"
87+
"types": "./types/exports/b.d.ts",
88+
"import": "./es/exports/b.js",
89+
"require": "./lib/exports/b.js",
90+
"default": "./es/exports/b.js"
9191
}
9292
}
9393
}

packages/ui-navigation/src/AppNav/v1/Item/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
passthroughProps
3333
} from '@instructure/ui-react-utils'
3434

35-
import { View } from '@instructure/ui-view'
35+
import { View } from '@instructure/ui-view/v11_6'
3636
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
3737
import type { ScreenReaderContentProps } from '@instructure/ui-a11y-content'
3838

packages/ui-navigation/src/AppNav/v1/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { withStyleLegacy as withStyle } from '@instructure/emotion'
2828

2929
import { callRenderProp, omitProps } from '@instructure/ui-react-utils'
3030

31-
import { View } from '@instructure/ui-view/latest'
32-
import { Menu } from '@instructure/ui-menu/latest'
31+
import { View } from '@instructure/ui-view/v11_6'
32+
import { Menu } from '@instructure/ui-menu/v11_6'
3333
import { Item } from './Item'
3434

3535
import generateStyle from './styles'

packages/ui-navigation/src/AppNav/v1/Item/__tests__/Item.test.tsx renamed to packages/ui-navigation/src/AppNav/v2/Item/__tests__/Item.test.tsx

File renamed without changes.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import { ComponentElement, Component } from 'react'
26+
27+
import { logError as error } from '@instructure/console'
28+
import {
29+
callRenderProp,
30+
getElementType,
31+
matchComponentTypes,
32+
passthroughProps
33+
} from '@instructure/ui-react-utils'
34+
35+
import { View } from '@instructure/ui-view/latest'
36+
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
37+
import type { ScreenReaderContentProps } from '@instructure/ui-a11y-content'
38+
39+
import { withStyle } from '@instructure/emotion'
40+
41+
import generateStyle from './styles'
42+
import type { AppNavItemProps } from './props'
43+
import { allowedProps } from './props'
44+
45+
/**
46+
---
47+
parent: AppNav
48+
id: AppNav.Item
49+
---
50+
@module Item
51+
**/
52+
@withStyle(generateStyle)
53+
class Item extends Component<AppNavItemProps> {
54+
static readonly componentId = 'AppNav.Item'
55+
56+
static allowedProps = allowedProps
57+
58+
static defaultProps = {
59+
children: null,
60+
isSelected: false,
61+
cursor: 'pointer',
62+
isDisabled: false
63+
} as const
64+
65+
ref: Element | null = null
66+
67+
componentDidMount() {
68+
this.props.makeStyles?.()
69+
}
70+
71+
componentDidUpdate() {
72+
this.props.makeStyles?.()
73+
}
74+
75+
handleRef = (el: Element | null) => {
76+
const { elementRef } = this.props
77+
78+
this.ref = el
79+
80+
if (typeof elementRef === 'function') {
81+
elementRef(el)
82+
}
83+
}
84+
85+
handleClick = (e: React.MouseEvent) => {
86+
const { isDisabled, onClick } = this.props
87+
88+
if (isDisabled) {
89+
e.preventDefault()
90+
e.stopPropagation()
91+
} else if (typeof onClick === 'function') {
92+
onClick(e)
93+
}
94+
}
95+
96+
render() {
97+
const ElementType = getElementType(Item, this.props)
98+
99+
const { renderIcon, renderLabel, href, renderAfter, cursor, isDisabled } =
100+
this.props
101+
102+
const icon = callRenderProp(renderIcon)
103+
const label = callRenderProp(renderLabel)
104+
105+
const labelIsForScreenReaders = matchComponentTypes<
106+
ComponentElement<ScreenReaderContentProps, ScreenReaderContent>
107+
>(label, [ScreenReaderContent])
108+
109+
if (icon) {
110+
error(
111+
labelIsForScreenReaders,
112+
'[AppNav] If an icon is used, the label text should be wrapped in <ScreenReaderContent />.'
113+
)
114+
}
115+
116+
return (
117+
<View
118+
{...passthroughProps(this.props)}
119+
as={ElementType}
120+
href={href}
121+
onClick={this.handleClick}
122+
disabled={isDisabled}
123+
elementRef={this.handleRef}
124+
display="flex"
125+
position="relative"
126+
borderRadius="medium"
127+
cursor={isDisabled ? 'not-allowed' : cursor}
128+
css={this.props.styles?.item}
129+
data-cid="AppNav.Item"
130+
>
131+
{icon}
132+
{labelIsForScreenReaders ? (
133+
label
134+
) : (
135+
<span css={this.props.styles?.label}>{label}</span>
136+
)}
137+
{renderAfter && callRenderProp(renderAfter)}
138+
</View>
139+
)
140+
}
141+
}
142+
143+
export default Item
144+
export { Item }
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015 - present Instructure, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
import type {
26+
AsElementType,
27+
AppNavItemTheme,
28+
OtherHTMLAttributes
29+
} from '@instructure/shared-types'
30+
import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
31+
import type { Cursor } from '@instructure/shared-types'
32+
import React from 'react'
33+
import { Renderable } from '@instructure/shared-types'
34+
35+
type AppNavItemOwnProps = {
36+
/**
37+
* The text to display. If the `icon` prop is used, label text must be wrapped
38+
* in `ScreenReaderContent`.
39+
*/
40+
renderLabel: Renderable
41+
/**
42+
* Content to display after the renderLabel text, such as a badge
43+
*/
44+
renderAfter?: Renderable
45+
/**
46+
* The visual to display (ex. an Image, Logo, Avatar, or Icon)
47+
*/
48+
renderIcon?: Renderable
49+
/**
50+
* If the item goes to a new page, pass an href
51+
*/
52+
href?: string
53+
/**
54+
* If the item does not go to a new page, pass an onClick
55+
*/
56+
onClick?: (event: React.MouseEvent) => void
57+
/**
58+
* Denotes which item is currently selected
59+
*/
60+
isSelected?: boolean
61+
/**
62+
* provides a reference to the underlying focusable (`button` or `a`) element
63+
*/
64+
elementRef?: (element: Element | null) => void
65+
/**
66+
* The element type to render as (will default to `<a>` if href is provided)
67+
*/
68+
as?: AsElementType
69+
/**
70+
* Specify the mouse cursor to use on :hover.
71+
* The `pointer` cursor is used by default.
72+
*/
73+
cursor?: Cursor
74+
/**
75+
* Disables the link or button visually and functionally
76+
*/
77+
isDisabled?: boolean
78+
}
79+
80+
type PropKeys = keyof AppNavItemOwnProps
81+
82+
type AllowedPropKeys = Readonly<Array<PropKeys>>
83+
84+
type AppNavItemProps = AppNavItemOwnProps &
85+
WithStyleProps<AppNavItemTheme, AppNavItemStyle> &
86+
OtherHTMLAttributes<AppNavItemOwnProps>
87+
88+
type AppNavItemStyle = ComponentStyle<'item' | 'label'>
89+
const allowedProps: AllowedPropKeys = [
90+
'renderLabel',
91+
'renderAfter',
92+
'renderIcon',
93+
'href',
94+
'onClick',
95+
'isSelected',
96+
'elementRef',
97+
'as',
98+
'cursor',
99+
'isDisabled'
100+
]
101+
102+
export type { AppNavItemProps, AppNavItemStyle }
103+
export { allowedProps }

0 commit comments

Comments
 (0)