diff --git a/apps/portals/my-pages/src/components/Header/Header.css.ts b/apps/portals/my-pages/src/components/Header/Header.css.ts index 847e4716b855..bc620e2873b1 100644 --- a/apps/portals/my-pages/src/components/Header/Header.css.ts +++ b/apps/portals/my-pages/src/components/Header/Header.css.ts @@ -7,7 +7,7 @@ import { import { theme, themeUtils } from '@island.is/island-ui/theme' export const header = style({ - position: 'relative', + position: 'fixed', zIndex: zIndex.header, display: 'flex', alignItems: 'center', @@ -18,7 +18,6 @@ export const header = style({ '@media': { [`screen and (min-width: ${theme.breakpoints.lg}px)`]: { height: SERVICE_PORTAL_HEADER_HEIGHT_LG, - position: 'fixed', }, }, transition: 'all 250ms ease-in-out', diff --git a/apps/portals/my-pages/src/components/Layout/Layout.css.ts b/apps/portals/my-pages/src/components/Layout/Layout.css.ts index 026b46939360..d44c413fe927 100644 --- a/apps/portals/my-pages/src/components/Layout/Layout.css.ts +++ b/apps/portals/my-pages/src/components/Layout/Layout.css.ts @@ -21,12 +21,6 @@ export const lock = style({ export const btn = style({}) -export const mobileNav = style({ - position: 'sticky', - top: 0, - zIndex: 99, -}) - globalStyle(`${btn} > span`, { boxShadow: 'none', }) diff --git a/apps/portals/my-pages/src/components/Layout/NarrowLayout.tsx b/apps/portals/my-pages/src/components/Layout/NarrowLayout.tsx index 3c102bb5df89..27c139af7ec7 100644 --- a/apps/portals/my-pages/src/components/Layout/NarrowLayout.tsx +++ b/apps/portals/my-pages/src/components/Layout/NarrowLayout.tsx @@ -1,12 +1,16 @@ import { ReactNode } from 'react' -import { Box, NavigationItem, Icon } from '@island.is/island-ui/core' +import { + Box, + Navigation, + NavigationItem, + Icon, +} from '@island.is/island-ui/core' import ContentBreadcrumbs from '../../components/ContentBreadcrumbs/ContentBreadcrumbs' import { m, ServicePortalNavigationItem, ModuleAlertBannerSection, GoBack, - Navigation, } from '@island.is/portals/my-pages/core' import { useLocale } from '@island.is/localization' import { useWindowSize } from 'react-use' @@ -97,10 +101,11 @@ export const NarrowLayout = ({ ) }} asSpan - baseId="service-portal-navigation" + baseId={'service-portal-navigation'} title={formatMessage(activeParent?.name ?? m.tableOfContents)} items={subNavItems ?? []} expand + expandOnActivation titleIcon={activeParent?.icon} /> @@ -117,7 +122,7 @@ export const NarrowLayout = ({ > {isMobile && subNavItems && subNavItems.length > 0 && ( - + { return item?.href ? ( @@ -127,7 +132,7 @@ export const NarrowLayout = ({ ) }} asSpan - baseId="service-portal-mobile-navigation" + baseId={'service-portal-mobile-navigation'} title={ activeParent?.name ? formatMessage(activeParent?.name) diff --git a/libs/portals/my-pages/core/src/components/Navigation/Navigation.css.ts b/libs/portals/my-pages/core/src/components/Navigation/Navigation.css.ts deleted file mode 100644 index e4f08cac0b62..000000000000 --- a/libs/portals/my-pages/core/src/components/Navigation/Navigation.css.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { style, styleVariants, keyframes } from '@vanilla-extract/css' -import { theme } from '@island.is/island-ui/theme' - -export const divider = style({ - width: '100%', - height: 1, -}) - -export const largerClickableArea = style({ - ':after': { - position: 'absolute', - display: 'inline-block', - cursor: 'pointer', - left: 0, - right: 0, - bottom: 0, - top: 0, - content: '""', - margin: -10, - }, -}) - -export const root = style({ - transition: 'background-color 150ms', -}) - -export const ul = style({ - borderLeftWidth: 1, - borderLeftStyle: 'solid', - borderLeftColor: theme.color.transparent, -}) - -export const colorScheme = styleVariants({ - blue: {}, - purple: {}, - darkBlue: {}, -}) - -export const text = style({ - position: 'relative', -}) - -export const textNarrower = style({ - width: 'calc(100% - 34px)', -}) - -export const link = style({ - position: 'relative', - ':hover': { - textDecoration: 'none', - cursor: 'pointer', - }, -}) - -export const level = styleVariants({ - 1: { - padding: 0, - }, - 2: { - marginTop: theme.spacing[1], - marginBottom: theme.spacing[1], - marginLeft: theme.spacing[3], - marginRight: theme.spacing[3], - }, -}) - -export const menuBtn = style({ - width: '100%', - cursor: 'pointer', - outline: 'none', - borderRadius: 8, - padding: `${theme.spacing['p2']}px ${theme.spacing[2]}px`, - transition: 'box-shadow .25s, color .25s, background-color .25s', - ':focus-visible': { - boxShadow: `0 0 0 3px ${theme.color.blue400}`, - }, -}) - -export const listItem = style({ - position: 'relative', -}) - -const translate = 'translateX(-50%) translateY(-50%)' - -export const icon = style({ - position: 'absolute', - left: '50%', - top: '50%', - opacity: 1, - transform: `${translate} rotateZ(0deg)`, - transition: 'opacity 150ms ease, transform 300ms ease', -}) - -export const iconRemoveHidden = style({ - opacity: 0, - transform: `${translate} rotateZ(-90deg)`, -}) - -export const iconRemoveVisible = style({ - opacity: 1, - transform: `${translate} rotateZ(0deg)`, -}) - -export const iconAddHidden = style({ - opacity: 0, - transform: `${translate} rotateZ(0deg)`, -}) - -export const iconAddVisible = style({ - opacity: 1, - transform: `${translate} rotateZ(-0deg)`, -}) - -export const rotated = style({ - transition: 'transform 300ms ease', -}) - -export const accordionIcon = style({ - position: 'absolute', - display: 'inline-block', - lineHeight: 0, - justifyContent: 'center', - alignItems: 'center', - borderRadius: '50%', - top: 10, - right: 0, - width: 24, - height: 24, - outline: 0, -}) - -export const dropdownIcon = style({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: 40, - width: 40, - borderRadius: '50%', -}) - -export const menuShadow = styleVariants({ - blue: { - boxShadow: ' 0px 4px 30px rgba(0, 97, 255, 0.25)', - }, - purple: { - boxShadow: ' 0px 4px 30px rgba(106, 46, 160, 0.25)', - }, - darkBlue: { - boxShadow: ' 0px 4px 30px rgba(0, 0, 60, 0.25)', - }, -}) - -export const transition = style({ - opacity: 0, - transition: 'opacity 150ms ease-in-out', - selectors: { - '&[data-enter]': { - opacity: 1, - }, - }, -}) -const stretchKeyframe = keyframes({ - '0%': { - transform: 'scaleX(0)', - }, - '100%': { - transform: 'scaleX(1)', - }, -}) -export const menuDialog = style({ - '::before': { - content: '', - position: 'absolute', - top: 0, - bottom: -1, - zIndex: -1, - left: `-${theme.spacing[2]}px`, - right: `-${theme.spacing[2]}px`, - background: theme.color.blue100, - borderBottom: `1px solid ${theme.color.blue200}`, - animation: `${stretchKeyframe} 0.35s ease-in-out`, - }, -}) - -export const mobileNav = style({ - zIndex: 1500, - opacity: 1, - backgroundColor: theme.color.blue100, - height: '100%', -}) - -export const shakeKeyframe = keyframes({ - from: { - transform: 'translateX(0)', - }, - '25%': { - transform: 'translateX(-1px)', - }, - '45%': { - transform: 'translateX(1px)', - }, - '55%': { - transform: 'translateX(-1px)', - }, - '75%': { - transform: 'translateX(1px)', - }, - to: { - transform: 'translateX(0)', - }, -}) - -export const shake = style({ - animation: `${shakeKeyframe} 0.5s`, -}) diff --git a/libs/portals/my-pages/core/src/components/Navigation/Navigation.tsx b/libs/portals/my-pages/core/src/components/Navigation/Navigation.tsx deleted file mode 100644 index d5e33451c180..000000000000 --- a/libs/portals/my-pages/core/src/components/Navigation/Navigation.tsx +++ /dev/null @@ -1,698 +0,0 @@ -import React, { - FC, - useState, - useEffect, - ReactNode, - createContext, - ReactElement, - useRef, -} from 'react' -import cn from 'classnames' -import AnimateHeight from 'react-animate-height' -import { useMenuState, Menu, MenuButton, MenuStateReturn } from 'reakit/Menu' -import { theme, Colors } from '@island.is/island-ui/theme' -import { - Text, - Box, - BoxProps, - FocusableBox, - Icon, - IconProps, - ModalBase, - VisuallyHidden, -} from '@island.is/island-ui/core' - -import * as styles from './Navigation.css' - -type NavigationContextProps = { - baseId: string - activeAccordions: Array - toggleAccordion: (id: string) => void -} - -export const NavigationContext = createContext({ - baseId: '', - activeAccordions: [], - toggleAccordion: () => null, -}) - -export type NavigationColorAttributes = - | 'color' - | 'dividerColor' - | 'backgroundColor' - | 'activeColor' - -const colorSchemeColors: Record< - keyof typeof styles.colorScheme, - Record -> = { - blue: { - color: 'blue600' as Colors, - dividerColor: 'blue200' as Colors, - backgroundColor: 'blue100' as Colors, - activeColor: 'blue400' as Colors, - }, - darkBlue: { - color: 'white' as Colors, - dividerColor: 'blue600' as Colors, - backgroundColor: 'blue400' as Colors, - activeColor: 'white' as Colors, - }, - purple: { - color: 'purple600' as Colors, - dividerColor: 'purple200' as Colors, - backgroundColor: 'purple100' as Colors, - activeColor: 'purple400' as Colors, - }, -} - -export interface NavigationItem { - title: string - href?: string - active?: boolean - accordion?: boolean - items?: NavigationItem[] - typename?: string - slug?: string[] -} -interface MobileNavigationDialogProps { - Title: ReactNode - colorScheme: keyof typeof styles.colorScheme - items: NavigationItem[] - renderLink: NavigationTreeProps['renderLink'] - asSpan?: NavigationTreeProps['asSpan'] - isVisible: boolean - onClick: () => void - menuState: MenuStateReturn - mobileNavigationButtonCloseLabel?: string -} - -interface NavigationTreeProps { - items: NavigationItem[] - level?: Level - colorScheme?: keyof typeof styles.colorScheme - expand?: boolean - renderLink?: (link: ReactElement, item?: NavigationItem) => ReactNode - menuState: MenuStateReturn - linkOnClick?: () => void - id?: string - labelId?: string - asSpan?: boolean -} - -export interface NavigationProps { - title: string - titleIcon?: Pick - label?: string - activeItemTitle?: string - colorScheme?: keyof typeof styles.colorScheme - /** - * Keep all child menu items expanded - */ - expand?: boolean - /** - * Only a single acccordion can be expanded at a time - */ - singleAccordion?: boolean - isMenuDialog?: boolean - titleLink?: Pick - items: NavigationItem[] - baseId: string - /** - * Render function for all links, useful for wrapping framework specific routing links - */ - renderLink?: NavigationTreeProps['renderLink'] - /** - * Wrap the link in a instead of a for passing in framework specific routing links - */ - asSpan?: NavigationTreeProps['asSpan'] - titleProps?: NavigationItem - mobileNavigationButtonOpenLabel?: string - mobileNavigationButtonCloseLabel?: string -} - -// The sidebar nav is not designed to show more than 2 levels. -type Level = keyof typeof styles.level - -const MAX_LEVELS = 2 - -const basePadding = { - paddingY: 1, - paddingX: 3, -} as Pick - -const defaultLinkRender: NavigationTreeProps['renderLink'] = (link) => link - -const toggleId = (arr: Array = [], id: string, single = false) => - arr.includes(id) ? arr.filter((i) => i !== id) : [...(single ? [] : arr), id] - -export const Navigation: FC> = ({ - title = 'Efnisyfirlit', - titleLink, - titleIcon, - activeItemTitle, - label, - colorScheme = 'blue', - expand, - singleAccordion = false, - renderLink = defaultLinkRender, - isMenuDialog = false, - items, - titleProps, - baseId, - asSpan, - mobileNavigationButtonOpenLabel = 'Open', - mobileNavigationButtonCloseLabel = 'Close', -}) => { - const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - - const [activeAccordions, setActiveAccordions] = useState>( - () => { - const initialActivePathIndex = items?.findIndex( - (item) => item.active && item.accordion, - ) - - if (initialActivePathIndex > 0) { - //first level only - return [ - `1-${items?.findIndex( - (item) => - item.active && - item.accordion && - item.items?.some((child) => child.active), - )}`, - ] - } - - return [] - }, - ) - - const color = colorSchemeColors[colorScheme]['color'] - const activeColor = colorSchemeColors[colorScheme]['activeColor'] - const backgroundColor = colorSchemeColors[colorScheme]['backgroundColor'] - const dividerColor = colorSchemeColors[colorScheme]['dividerColor'] - - const menu = useMenuState({ - animated: true, - baseId, - visible: false, - }) - - useEffect(() => { - setMobileMenuOpen(menu.visible) - }, [menu.visible]) - - const titleLinkProps = titleLink - ? { - href: titleLink.href, - } - : null - - const toggleAccordion = (id: string) => { - setActiveAccordions(toggleId(activeAccordions, id, singleAccordion)) - } - - const Title: MobileNavigationDialogProps['Title'] = titleLinkProps ? ( - renderLink( - - {({ isFocused, isHovered }) => { - const textColor = - titleLink?.active || isFocused || isHovered ? activeColor : color - - return ( - <> - - {title} - - - {title} - - - ) - }} - , - titleProps, - ) - ) : ( - - - {titleIcon && ( - - - - )} - - {title} - - - - ) - const [isScrolled, setIsScrolled] = useState(false) - - useEffect(() => { - const handleScroll = () => { - const element = document.getElementById('menuDialog-mobile-test') - if (element) { - const rect = element.getBoundingClientRect() - - setIsScrolled(rect.top === 0) - } - } - - window.addEventListener('scroll', handleScroll) - return () => { - window.removeEventListener('scroll', handleScroll) - } - }, [baseId, isScrolled]) - - return ( - - {isMenuDialog ? ( - - menu.show} - aria-label={title} - > - - - - { - menu.hide() - }} - /> - - - ) : ( - - {Title} - - - - - - )} - - ) -} - -const MobileNavigationDialog = ({ - Title, - colorScheme, - items, - renderLink, - onClick, - menuState, - asSpan, - mobileNavigationButtonCloseLabel, -}: MobileNavigationDialogProps) => { - return ( - - - - {Title} - - - - {mobileNavigationButtonCloseLabel} - - - - - - - - - - - - ) -} -interface MobileButtonProps { - title: string - titleIcon?: Pick - colorScheme: keyof typeof styles.colorScheme - mobileNavigationButtonOpenLabel?: string -} - -const MobileButton = ({ - title, - colorScheme, - titleIcon, - mobileNavigationButtonOpenLabel, -}: MobileButtonProps) => { - const [isShaking, setIsShaking] = useState(false) - const time = 15000 // 15 seconds - const idleTimerRef = useRef(null) - - useEffect(() => { - const handleIdle = () => { - setIsShaking(true) - setTimeout(() => setIsShaking(false), 1000) // Stop shaking after 1 second - } - - const resetTimer = () => { - if (idleTimerRef.current) { - clearTimeout(idleTimerRef.current) - } - idleTimerRef.current = setTimeout(handleIdle, time) - } - - resetTimer() // Initialize the timer - - window.addEventListener('mousemove', resetTimer) - window.addEventListener('keydown', resetTimer) - - return () => { - if (idleTimerRef.current) { - clearTimeout(idleTimerRef.current) - } - window.removeEventListener('mousemove', resetTimer) - window.removeEventListener('keydown', resetTimer) - } - }, []) - - return ( - - - {titleIcon && ( - - - - )} - - {title} - - - - - - {mobileNavigationButtonOpenLabel} - - - - - ) -} - -export const NavigationTree: FC< - React.PropsWithChildren -> = ({ - items, - level = 1, - colorScheme = 'blue', - expand = false, - renderLink = defaultLinkRender, - menuState, - linkOnClick, - id = '', - labelId = '', - asSpan, -}: NavigationTreeProps) => { - return ( - - {({ baseId, activeAccordions, toggleAccordion }) => ( - 1 - ? theme.color[colorSchemeColors[colorScheme]['dividerColor']] - : undefined, - }} - > - {items.map((item, index) => { - const { title, href, items = [], active, accordion } = item - const nextLevel: Level = (level + 1) as Level - const isChildren = level > 1 - const showNextLevel = - (active || expand) && - items.length && - nextLevel <= MAX_LEVELS && - !accordion - const isAccordion = !!( - items.length && - nextLevel <= MAX_LEVELS && - accordion - ) - const accordionId = `${level}-${index}` - const activeAccordion = activeAccordions.includes(accordionId) - const labelId = `${baseId}-title-${accordionId}` - const ariaId = `${baseId}-tree-${accordionId}` - - const nextLevelTree = ( - - ) - - const parentItem = ( - { - if (linkOnClick && !isAccordion) { - linkOnClick() - } - if (isAccordion) { - toggleAccordion(accordionId) - } - }} - > - {({ isFocused, isHovered }) => { - const textColor = - active || isFocused || isHovered - ? colorSchemeColors[colorScheme]['activeColor'] - : colorSchemeColors[colorScheme]['color'] - - return ( - - - {title} - - - ) - }} - - ) - return ( -
  • - {/*Note: Need to review usage (e.g. PortalNavigation) if we change the rendered element to something other than FocusableBox.*/} - {isAccordion ? parentItem : renderLink(parentItem, item)} - {isAccordion && ( - { - toggleAccordion(accordionId) - }} - //background={colorSchemeColors[colorScheme]['dividerColor']} - marginRight={2} - aria-expanded={activeAccordion} - aria-controls={ariaId} - className={cn( - styles.accordionIcon, - styles.largerClickableArea, - )} - > - - - - )} - {isAccordion && ( - - {nextLevelTree} - - )} - {!!showNextLevel && nextLevelTree} -
  • - ) - })} -
    - )} -
    - ) -} diff --git a/libs/portals/my-pages/core/src/index.ts b/libs/portals/my-pages/core/src/index.ts index 2742a48abd88..f107e2eea54e 100644 --- a/libs/portals/my-pages/core/src/index.ts +++ b/libs/portals/my-pages/core/src/index.ts @@ -48,7 +48,6 @@ export * from './components/StackOrTableBlock/StackOrTableBlock' export * from './components/IntroWrapper/IntroWrapper' export * from './components/InstitutionPanel/InstitutionPanel' export * from './components/MobileTable/MobileTable' -export * from './components/Navigation/Navigation' export * from './utils/utils' export * from './utils/amountFormat' export * from './utils/numberFormat'