implement react router for all routes in sidenav
SideNav is now working for all routes Instead ERB Html now is using React.Router closes FOO-3886 closes FOO-3982 closes FOO-4326 closes FOO-4327 flag=instui_nav test plan: - Log in to Canvas as an Admin - Go to RootAccount > Settings > Feature Options > Enable New InstUI NavBar - This should render the new SideNav - Navbar should be displayed Change-Id: Ic6d88dfb5284358846ee192cad6cbce7c8300da9 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/342010 QA-Review: Gustavo Bernardes <gustavo.bernardes@instructure.com> Product-Review: Gustavo Bernardes <gustavo.bernardes@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Gustavo Bernardes <gustavo.bernardes@instructure.com> Reviewed-by: Charley Kline <ckline@instructure.com>
This commit is contained in:
parent
d4ca674c38
commit
b1e6794484
|
@ -102,6 +102,12 @@ $ic-tooltip-arrow-size: 0.375rem;
|
|||
}
|
||||
}
|
||||
|
||||
.ic-svg-external-tool>div:first-child {
|
||||
svg>path {
|
||||
transform: scale(0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.ic-sidenav-tray:active:hover,
|
||||
.ic-user-tray:active:hover {
|
||||
color: var(--ic-brand-primary);
|
||||
|
@ -127,7 +133,6 @@ $ic-tooltip-arrow-size: 0.375rem;
|
|||
.ic-user-avatar {
|
||||
width: 1.875rem;
|
||||
height: 1.875rem;
|
||||
border: 2px solid var(--ic-brand-global-nav-avatar-border) !important;
|
||||
}
|
||||
|
||||
.ic-collapse-div {
|
||||
|
@ -438,8 +443,6 @@ body.primary-nav-expanded {
|
|||
gap: 0.2rem !important;
|
||||
width: auto !important;
|
||||
height: 63.55px !important;
|
||||
font-weight: 400;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.ic-user-tray {
|
||||
|
@ -463,6 +466,12 @@ body.primary-nav-expanded {
|
|||
}
|
||||
}
|
||||
|
||||
.ic-svg-external-tool>div:first-child {
|
||||
svg>path {
|
||||
transform: scale(0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.ic-sidenav-tray:active:hover,
|
||||
.ic-user-tray:active:hover {
|
||||
color: var(--ic-brand-primary);
|
||||
|
@ -488,7 +497,6 @@ body.primary-nav-expanded {
|
|||
.ic-user-avatar {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border: 2px solid var(--ic-brand-global-nav-avatar-border) !important;
|
||||
}
|
||||
|
||||
/** New SideNav CSS */
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# Copyright (C) 2024 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_relative "../common"
|
||||
require_relative "../../helpers/k5_common"
|
||||
|
||||
describe "New SideNav Navigation" do
|
||||
include_context "in-process server selenium tests"
|
||||
include K5Common
|
||||
|
||||
context "As a Teacher" do
|
||||
before do
|
||||
course_with_teacher_logged_in
|
||||
@course.root_account.enable_feature!(:instui_nav)
|
||||
end
|
||||
|
||||
it "minimizes and expand the side nav when clicked" do
|
||||
get "/"
|
||||
primary_nav_toggle = f("#sidenav-toggle")
|
||||
primary_nav_toggle.click
|
||||
wait_for_ajaximations
|
||||
expect(f("body")).not_to have_class("primary-nav-expanded")
|
||||
primary_nav_toggle.click
|
||||
wait_for_ajaximations
|
||||
expect(f("body")).to have_class("primary-nav-expanded")
|
||||
end
|
||||
|
||||
describe "Profile Link" do
|
||||
it "shows the profile tray upon clicking" do
|
||||
get "/"
|
||||
user_tray = f("#user-tray")
|
||||
user_tray.click
|
||||
wait_for_ajaximations
|
||||
expect(f('[aria-label="User profile picture"]')).to be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
describe "Courses Link" do
|
||||
it "shows the courses tray upon clicking" do
|
||||
get "/"
|
||||
courses_tray = f("#courses-tray")
|
||||
courses_tray.click
|
||||
wait_for_ajaximations
|
||||
expect(f("[aria-label='Courses tray']")).to be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
describe "LTI Tools" do
|
||||
it "shows a custom logo/link for LTI tools" do
|
||||
@tool = Account.default.context_external_tools.new({
|
||||
name: "Commons",
|
||||
domain: "canvaslms.com",
|
||||
consumer_key: "12345",
|
||||
shared_secret: "secret"
|
||||
})
|
||||
@tool.set_extension_setting(:global_navigation, {
|
||||
url: "canvaslms.com",
|
||||
visibility: "admins",
|
||||
display_type: "full_width",
|
||||
text: "Commons",
|
||||
icon_svg_path_64: "M100,37L70.1,10.5v17.6H38.6c-4.9,0-8.8,3.9-8.8,8.8s3.9,8.8,8.8,8.8h31.5v17.6L100,37z"
|
||||
})
|
||||
@tool.save!
|
||||
get "/"
|
||||
expect(f("#external-tool-tray")).to be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
describe "Recent History" do
|
||||
before do
|
||||
Setting.set("enable_page_views", "db")
|
||||
@assignment = @course.assignments.create(name: "another assessment")
|
||||
@quiz = Quizzes::Quiz.create!(title: "quiz1", context: @course)
|
||||
page_view_for url: app_url + "/courses/#{@course.id}/assignments/#{@assignment.id}",
|
||||
context: @course,
|
||||
created_at: 5.minutes.ago,
|
||||
asset_category: "assignments",
|
||||
asset_code: @assignment.asset_string
|
||||
page_view_for url: app_url + "/courses/#{@course.id}/quizzes/#{@quiz.id}",
|
||||
context: @course,
|
||||
created_at: 1.minute.ago,
|
||||
asset_category: "quizzes",
|
||||
asset_code: @quiz.asset_string
|
||||
end
|
||||
|
||||
it "shows the Recent History tray upon clicking" do
|
||||
get "/"
|
||||
wait_for_ajaximations
|
||||
expect(f("#history-tray")).to be_displayed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -49,7 +49,7 @@ const portalRouter = createBrowserRouter(
|
|||
{accountGradingSettingsRoutes}
|
||||
|
||||
{(window.ENV.FEATURES.instui_nav || localStorage.instui_nav_dev) &&
|
||||
['/', '/accounts/*', '/calendar/*', '/courses/*', '/conversations/*'].map(path => (
|
||||
['/', '/*', '/*/*'].map(path => (
|
||||
<Route
|
||||
key={`key-to-${path}`}
|
||||
path={path}
|
||||
|
|
|
@ -26,6 +26,7 @@ import MobileNavigation from './react/MobileNavigation'
|
|||
import ready from '@instructure/ready'
|
||||
import NewTabIndicator from './react/NewTabIndicator'
|
||||
import {QueryProvider} from '@canvas/query'
|
||||
import {getExternalTools} from './react/utils'
|
||||
|
||||
const I18n = useI18nScope('common')
|
||||
|
||||
|
@ -73,7 +74,7 @@ ready(() => {
|
|||
const mobileContextNavContainer = document.getElementById('mobileContextNavContainer')
|
||||
ReactDOM.render(
|
||||
<QueryProvider>
|
||||
<SideNav />
|
||||
<SideNav externalTools={getExternalTools()} />
|
||||
</QueryProvider>,
|
||||
mobileContextNavContainer,
|
||||
() => {
|
||||
|
|
|
@ -264,7 +264,7 @@ export default function MobileGlobalMenu(props: Props) {
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 64 64"
|
||||
dangerouslySetInnerHTML={{__html: tool.svgPath}}
|
||||
dangerouslySetInnerHTML={{__html: tool.svgPath ?? ''}}
|
||||
width="1em"
|
||||
height="1em"
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import React, {useMemo} from 'react'
|
||||
import {Portal} from '@instructure/ui-portal'
|
||||
import SideNav from './SideNav'
|
||||
import {getExternalTools, type ExternalTool} from './utils'
|
||||
|
||||
export function Component() {
|
||||
const externalTools = useMemo<ExternalTool[]>(() => getExternalTools(), [])
|
||||
const mountPoint: HTMLElement | null = document.getElementById('header')
|
||||
if (!mountPoint) {
|
||||
return null
|
||||
|
@ -28,7 +30,7 @@ export function Component() {
|
|||
mountPoint.innerHTML = ''
|
||||
return (
|
||||
<Portal open={true} mountNode={mountPoint}>
|
||||
<SideNav />
|
||||
<SideNav externalTools={externalTools} />
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
IconAdminLine,
|
||||
IconCalendarMonthLine,
|
||||
IconCanvasLogoSolid,
|
||||
IconClockLine,
|
||||
IconCoursesLine,
|
||||
IconDashboardLine,
|
||||
IconFolderLine,
|
||||
|
@ -46,7 +47,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
|
|||
import {getUnreadCount} from './queries/unreadCountQuery'
|
||||
import {getSetting, setSetting} from './queries/settingsQuery'
|
||||
import {getActiveItem, getTrayLabel, getTrayPortal} from './utils'
|
||||
import type {ActiveTray} from './utils'
|
||||
import type {ActiveTray, ExternalTool} from './utils'
|
||||
|
||||
const I18n = useI18nScope('sidenav')
|
||||
|
||||
|
@ -66,25 +67,27 @@ export const InformationIconEnum = {
|
|||
|
||||
const defaultActiveItem = getActiveItem()
|
||||
|
||||
const SideNav = () => {
|
||||
const SideNav = ({externalTools = []}: {externalTools?: ExternalTool[]}) => {
|
||||
const [isTrayOpen, setIsTrayOpen] = useState(false)
|
||||
const [activeTray, setActiveTray] = useState<ActiveTray | null>(null)
|
||||
const [selectedNavItem, setSelectedNavItem] = useState<ActiveTray | ''>(defaultActiveItem)
|
||||
const sideNavRef = useRef<HTMLDivElement | null>(null)
|
||||
const logoRef = useRef<Element | null>(null)
|
||||
const accountRef = useRef<Element | null>(null)
|
||||
const adminRef = useRef<Element | null>(null)
|
||||
const dashboardRef = useRef<Element | null>(null)
|
||||
const coursesRef = useRef<Element | null>(null)
|
||||
const adminRef = useRef<Element | null>(null)
|
||||
const calendarRef = useRef<Element | null>(null)
|
||||
const inboxRef = useRef<Element | null>(null)
|
||||
const historyRef = useRef<Element | null>(null)
|
||||
const externalTool = useRef<Element | null>(null)
|
||||
const helpRef = useRef<Element | null>(null)
|
||||
const logoRef = useRef<Element | null>(null)
|
||||
|
||||
// after tray is closed, eventually set activeTray to null
|
||||
// we don't do this immediately in order to maintain animation of closing tray
|
||||
useEffect(() => {
|
||||
if (!isTrayOpen) {
|
||||
setTimeout(() => setActiveTray(null), 150)
|
||||
setTimeout(() => setActiveTray(null), 100)
|
||||
}
|
||||
}, [isTrayOpen])
|
||||
|
||||
|
@ -113,13 +116,11 @@ const SideNav = () => {
|
|||
accountRef.current.dataset.selected = 'true'
|
||||
}
|
||||
break
|
||||
|
||||
case 'accounts':
|
||||
if (adminRef.current instanceof HTMLElement) {
|
||||
adminRef.current.dataset.selected = 'true'
|
||||
}
|
||||
break
|
||||
|
||||
case 'dashboard':
|
||||
if (dashboardRef.current instanceof HTMLElement) {
|
||||
dashboardRef.current.dataset.selected = 'true'
|
||||
|
@ -157,6 +158,8 @@ const SideNav = () => {
|
|||
contentPadding: '0.1rem',
|
||||
backgroundColor: 'transparent',
|
||||
hoverBackgroundColor: 'transparent',
|
||||
fontWeight: 400,
|
||||
linkTextDecoration: 'inherit',
|
||||
}
|
||||
|
||||
const getHelpIcon = (): JSX.Element => {
|
||||
|
@ -176,11 +179,11 @@ const SideNav = () => {
|
|||
const countsEnabled = Boolean(
|
||||
window.ENV.current_user_id && !window.ENV.current_user?.fake_student
|
||||
)
|
||||
|
||||
const brandConfig =
|
||||
(window.ENV.active_brand_config as {
|
||||
variables: {'ic-brand-header-image': string}
|
||||
}) ?? null
|
||||
|
||||
if (brandConfig) {
|
||||
const variables = brandConfig.variables
|
||||
logoUrl = variables['ic-brand-header-image']
|
||||
|
@ -246,11 +249,20 @@ const SideNav = () => {
|
|||
document.querySelector('#courses-tray'),
|
||||
document.querySelector('#calendar-tray'),
|
||||
document.querySelector('#inbox-tray'),
|
||||
document.querySelector('#history-tray'),
|
||||
document.querySelector('#external-tool-tray'),
|
||||
document.querySelector('#help-tray'),
|
||||
]
|
||||
if (Array.isArray(sideNavTrays))
|
||||
sideNavTrays.forEach(sideNavTray => sideNavTray?.classList.add('ic-sidenav-tray'))
|
||||
|
||||
const externalToolsSvgImg = ['ic-svg-external-tool', 'ic-img-external-tool']
|
||||
|
||||
if (Array.isArray(externalToolsSvgImg))
|
||||
externalToolsSvgImg.forEach(svgImgClassName =>
|
||||
document.querySelector('#external-tool-tray')?.classList.add(svgImgClassName)
|
||||
)
|
||||
|
||||
document.querySelector('#user-tray')?.classList.add('ic-user-tray')
|
||||
document.querySelector('#canvas-logo')?.classList.add('ic-canvas-logo')
|
||||
document.querySelector('#brand-logo')?.classList.add('ic-brand-logo')
|
||||
|
@ -261,6 +273,7 @@ const SideNav = () => {
|
|||
const collapseButton = collapseDiv.childNodes[0] as HTMLElement
|
||||
collapseDiv.classList.add('ic-collapse-div')
|
||||
collapseButton.classList.add('ic-collapse-button')
|
||||
collapseButton.id = 'sidenav-toggle'
|
||||
|
||||
if (collapseGlobalNav) document.body.classList.remove('primary-nav-expanded')
|
||||
else document.body.classList.add('primary-nav-expanded')
|
||||
|
@ -324,6 +337,7 @@ const SideNav = () => {
|
|||
...navItemThemeOverride,
|
||||
contentPadding: '0',
|
||||
}}
|
||||
minimized={collapseGlobalNav}
|
||||
data-testid="sidenav-header-logo"
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
|
@ -356,9 +370,12 @@ const SideNav = () => {
|
|||
src={window.ENV.current_user.avatar_image_url}
|
||||
data-testid="sidenav-user-avatar"
|
||||
showBorder="always"
|
||||
frameBorder={2}
|
||||
frameBorder={4}
|
||||
themeOverride={{
|
||||
background: 'transparent',
|
||||
borderColor: '#ffffff',
|
||||
borderWidthSmall: '0.2em',
|
||||
borderWidthMedium: '0.2rem',
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
|
@ -372,6 +389,7 @@ const SideNav = () => {
|
|||
}}
|
||||
selected={selectedNavItem === 'profile'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="admin-tray"
|
||||
|
@ -385,6 +403,7 @@ const SideNav = () => {
|
|||
}}
|
||||
selected={selectedNavItem === 'accounts'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="dashboard-tray"
|
||||
|
@ -392,8 +411,9 @@ const SideNav = () => {
|
|||
icon={isK5User ? <IconHomeLine data-testid="K5HomeIcon" /> : <IconDashboardLine />}
|
||||
label={isK5User ? I18n.t('Home') : I18n.t('Dashboard')}
|
||||
href="/"
|
||||
themeOverride={navItemThemeOverride}
|
||||
selected={selectedNavItem === 'dashboard'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="courses-tray"
|
||||
|
@ -408,6 +428,7 @@ const SideNav = () => {
|
|||
}}
|
||||
selected={selectedNavItem === 'courses'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="calendar-tray"
|
||||
|
@ -415,8 +436,9 @@ const SideNav = () => {
|
|||
icon={<IconCalendarMonthLine />}
|
||||
label={I18n.t('Calendar')}
|
||||
href="/calendar"
|
||||
themeOverride={navItemThemeOverride}
|
||||
selected={selectedNavItem === 'calendar'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="inbox-tray"
|
||||
|
@ -449,7 +471,51 @@ const SideNav = () => {
|
|||
href="/conversations"
|
||||
selected={selectedNavItem === 'conversations'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
<SideNavBar.Item
|
||||
id="history-tray"
|
||||
elementRef={el => (historyRef.current = el)}
|
||||
icon={<IconClockLine />}
|
||||
label={I18n.t('History')}
|
||||
href={window.ENV.page_view_update_url}
|
||||
selected={selectedNavItem === 'history'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
{externalTools &&
|
||||
externalTools.map(tool => (
|
||||
<SideNavBar.Item
|
||||
key={tool.href}
|
||||
id="external-tool-tray"
|
||||
elementRef={el => (externalTool.current = el)}
|
||||
icon={
|
||||
'svgPath' in tool ? (
|
||||
<svg
|
||||
id="svg-external-tool"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 26 26"
|
||||
dangerouslySetInnerHTML={{__html: tool.svgPath ?? ''}}
|
||||
width="26px"
|
||||
height="26px"
|
||||
aria-hidden="true"
|
||||
role="presentation"
|
||||
focusable="false"
|
||||
style={{fill: 'currentColor', fontSize: 26}}
|
||||
/>
|
||||
) : (
|
||||
<img id="img-external-tool" width="26px" height="26px" src={tool.imgSrc} alt="" />
|
||||
)
|
||||
}
|
||||
label={tool.label}
|
||||
href={tool.href?.toString()}
|
||||
selected={tool.isActive}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
))}
|
||||
<SideNavBar.Item
|
||||
id="help-tray"
|
||||
icon={
|
||||
|
@ -485,6 +551,7 @@ const SideNav = () => {
|
|||
}}
|
||||
selected={selectedNavItem === 'help'}
|
||||
themeOverride={navItemThemeOverride}
|
||||
minimized={collapseGlobalNav}
|
||||
/>
|
||||
</SideNavBar>
|
||||
<Tray
|
||||
|
|
|
@ -20,10 +20,12 @@ import {useScope as useI18nScope} from '@canvas/i18n'
|
|||
|
||||
const I18n = useI18nScope('Navigation')
|
||||
|
||||
type CommonProperties = {
|
||||
export type CommonProperties = {
|
||||
href: string | null | undefined
|
||||
isActive: boolean
|
||||
label: string
|
||||
svgPath?: string
|
||||
imgSrc?: string
|
||||
}
|
||||
|
||||
type SvgTool = CommonProperties & {svgPath: string}
|
||||
|
|
Loading…
Reference in New Issue