Make K-5 Dashboard tabs sticky

Update the K-5 Dashboard tabs so they will stay fixed to the top of
the viewport when the user scrolls. Also added some logic to shrink
the "Welcome, <student>!" message at the top of the tabs when
scrolling so as to not take up too much vertical space.

closes LS-1862
flag = canvas_for_elementary

Test plan:
  - Load the K-5 dashboard as a student
  - Scroll down on the Homeroom tab (either enroll in a bunch of
    courses or make the window small so that scrolling is possible)
  - Expect the tabs to be fixed to the top of the screen and the
    welcome message to shrink to what looks like an h2
  - Scroll back to the top of the screen and expect the welcome
    message to enlarge back to the look of an h1
  - Switch to the Schedule tab
  - Scroll back in time to load more events
  - Expect the tabs to be fixed to the top of the screen and more
    events to be loaded
  - Also expect the planner view to still be centered on "Today",
    rather than the events from the past

Change-Id: If8b2297b0d1b05135a9b635588a6e8f7ac275c21
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/258657
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Peyton Craighill <pcraighill@instructure.com>
Reviewed-by: Nate Armstrong <narmstrong@instructure.com>
QA-Review: Nate Armstrong <narmstrong@instructure.com>
This commit is contained in:
Jeff Largent 2021-02-11 17:23:46 -05:00
parent 680ac696f4
commit a2a795a823
6 changed files with 98 additions and 31 deletions

View File

@ -16,17 +16,19 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import React, {useEffect, useRef, useState} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import I18n from 'i18n!dashboard'
import {Tabs} from '@instructure/ui-tabs'
import {Heading} from '@instructure/ui-heading'
import {
IconBankLine,
IconCalendarMonthLine,
IconHomeLine,
IconStarLightLine
} from '@instructure/ui-icons'
import {Tabs} from '@instructure/ui-tabs'
import {View} from '@instructure/ui-view'
export const TAB_IDS = {
HOMEROOM: 'tab-homeroom',
@ -67,33 +69,57 @@ DashboardIconTab.propTypes = {
selected: PropTypes.bool.isRequired
}
const DashboardTabs = ({currentTab, onRequestTabChange, tabsRef}) => {
const DashboardTabs = ({currentTab, name, onRequestTabChange, tabsRef}) => {
const [sticky, setSticky] = useState(false)
const containerRef = useRef(null)
useEffect(() => {
// Need to copy the value of containerRef on mount so it will still be
// available when the cleanup function runs.
const cachedRef = containerRef.current
// This IntersectionObserver will let us know when position: sticky has kicked in
// on the tabs. See https://developers.google.com/web/updates/2017/09/sticky-headers
const observer = new IntersectionObserver(
([e]) => {
setSticky(e.intersectionRatio < 1)
},
{threshold: [1]}
)
observer.observe(cachedRef)
return () => observer.unobserve(cachedRef)
}, [])
return (
<div className="ic-Dashboard-tabs">
<Tabs elementRef={tabsRef} onRequestTabChange={onRequestTabChange} tabOverflow="scroll">
{Object.keys(TABS).map(id => (
<Tabs.Panel
id={id}
key={id}
renderTitle={
<DashboardIconTab
icon={TABS[id].icon}
label={TABS[id].label}
selected={currentTab === id}
/>
}
selected={currentTab === id}
>
<span />
</Tabs.Panel>
))}
</Tabs>
<div className="ic-Dashboard-tabs" ref={containerRef}>
<View as="div" padding="medium 0 0 0" background="primary">
<Heading as="h1" level={sticky ? 'h2' : 'h1'} margin="0 0 small 0">
{I18n.t('Welcome, %{name}!', {name})}
</Heading>
<Tabs elementRef={tabsRef} onRequestTabChange={onRequestTabChange} tabOverflow="scroll">
{Object.keys(TABS).map(id => (
<Tabs.Panel
id={id}
key={id}
renderTitle={
<DashboardIconTab
icon={TABS[id].icon}
label={TABS[id].label}
selected={currentTab === id}
/>
}
selected={currentTab === id}
>
<span />
</Tabs.Panel>
))}
</Tabs>
</View>
</div>
)
}
DashboardTabs.propTypes = {
currentTab: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
onRequestTabChange: PropTypes.func.isRequired,
tabsRef: PropTypes.func
}

View File

@ -22,7 +22,6 @@ import PropTypes from 'prop-types'
import $ from 'jquery'
import {initializePlanner, responsiviser, store} from '@instructure/canvas-planner'
import {ApplyTheme} from '@instructure/ui-themeable'
import {Heading} from '@instructure/ui-heading'
import {View} from '@instructure/ui-view'
import apiUserContent from 'compiled/str/apiUserContent'
@ -117,11 +116,9 @@ export const K5Dashboard = ({
<ApplyTheme theme={theme}>
<Provider store={store}>
<View as="section">
<Heading level="h1" margin="medium 0 small 0">
{I18n.t('Welcome, %{name}!', {name: display_name})}
</Heading>
<DashboardTabs
currentTab={currentTab}
name={display_name}
onRequestTabChange={(_, {id}) => handleRequestTabChange(id)}
tabsRef={setTabsRef}
/>

View File

@ -152,7 +152,7 @@ const AssignmentLinks = ({color, requestTabChange, numDueToday = 0, numMissing =
)
}
AssignmentLinks.displayName = 'AssignmentsLinks'
AssignmentLinks.displayName = 'AssignmentLinks'
AssignmentLinks.propTypes = {
color: PropTypes.string.isRequired,
requestTabChange: PropTypes.func.isRequired,
@ -198,7 +198,7 @@ const K5DashboardCard = ({
const dashboardCard = (
<div
className="ic-DashboardCard"
style={{opacity: isDragging ? 1 : 1, transform: 'translate3d(0,0,0)', width: responsiveWidth}}
style={{opacity: isDragging ? 0 : 1, transform: 'translate3d(0,0,0)', width: responsiveWidth}}
aria-label={originalName}
data-testid="k5-dashboard-card"
>

View File

@ -84,7 +84,9 @@ export const HomeroomPage = props => {
)}
{cards?.length > 0 && (
<View as="section">
<Heading level="h2">{I18n.t('My Subjects')}</Heading>
<Heading level="h2" margin="medium 0 0 0">
{I18n.t('My Subjects')}
</Heading>
<K5DashboardContext.Provider
value={{assignmentsDueToday, assignmentsMissing, isStudent, responsiveSize}}
>

View File

@ -41,12 +41,30 @@
}
// These overrides are required because InstUI Tabs don't natively support icons or
// themeable "selected" colors.
// themeable "selected" colors. There are also some properties required for the
// animated sticky header transitions that can't be accomplished easily via InstUI.
.ic-Dashboard-tabs {
position: sticky;
// This is a trick to let our JS code know when position: sticky has kicked in.
// See https://developers.google.com/web/updates/2017/09/sticky-headers.
top: -1px;
padding-top: 1px;
border-bottom: $ic-border-color 1px solid;
// We need a higher z-index than the planner items, some of which are at 1.
z-index: 2;
& h1 {
transition: font-size 0.1s ease;
}
& [role="tab"] {
padding: 0.75rem 1.25rem;
}
& [role="tabpanel"] {
display: none;
}
& .ic-Dashboard-tabs__tab {
display: flex;
align-items: flex-end;

View File

@ -39,7 +39,7 @@ if (!Array.prototype.flat) {
configurable: true,
value: function flat(depth = 1) {
if (depth === 0) return this.slice()
return this.reduce(function(acc, cur) {
return this.reduce(function (acc, cur) {
if (Array.isArray(cur)) {
acc.push(...flat.call(cur, depth - 1))
} else {
@ -87,6 +87,30 @@ if (!('MutationObserver' in window)) {
})
}
if (!('IntersectionObserver' in window)) {
Object.defineProperty(window, 'IntersectionObserver', {
writable: true,
configurable: true,
value: class IntersectionObserver {
disconnect() {
return null
}
observe() {
return null
}
takeRecords() {
return null
}
unobserve() {
return null
}
}
})
}
if (!('matchMedia' in window)) {
window.matchMedia = () => ({
matches: false,