Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.

Commit 87f4dc3

Browse files
authored
feat: add language picker to sidebar (#1193)
* feat: add language picker to sidebar * fix: fix use of wrong parameter name
1 parent 42ed67c commit 87f4dc3

8 files changed

Lines changed: 130 additions & 66 deletions

File tree

customer/components/layout/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export type HeaderProps = {
1313
*/
1414
export const Header = ({ leading, leftSide, rightSide, className }: HeaderProps) => {
1515
return (
16-
<div
16+
<header
1717
className={tx(
18-
'@(sticky top-0 flex flex-row items-center justify-between px-2 py-2 min-h-[64px] max-h-[64px] shadow-md bg-white)',
18+
'@(sticky top-0 flex flex-row items-center justify-between px-2 py-2 w-full min-h-[64px] max-h-[64px] shadow-md bg-white)',
1919
className
2020
)}
2121
>
@@ -33,6 +33,6 @@ export const Header = ({ leading, leftSide, rightSide, className }: HeaderProps)
3333
{rightSide}
3434
</div>
3535
)}
36-
</div>
36+
</header>
3737
)
3838
}
Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import type { Languages } from '@helpwave/common/hooks/useLanguage'
2+
import { languagesLocalNames } from '@helpwave/common/hooks/useLanguage'
23
import { useLanguage } from '@helpwave/common/hooks/useLanguage'
34
import { tw, tx } from '@twind/core'
45
import { useRouter } from 'next/router'
56
import Link from 'next/link'
67
import { noop } from '@helpwave/common/util/noop'
7-
import { ArrowRightIcon, X } from 'lucide-react'
8+
import { ArrowRightIcon, ArrowRightLeft, X } from 'lucide-react'
89
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
910
import type { NavItem } from '@/components/layout/NavigationSidebar'
11+
import { Avatar } from '@helpwave/common/components/Avatar'
12+
import { LanguageModal } from '@helpwave/common/components/modals/LanguageModal'
13+
import { useState } from 'react'
1014

1115
type MobileNavigationOverlayTranslation = { navigation: string }
1216

@@ -32,34 +36,58 @@ export type MobileNavigationOverlayProps = {
3236
*/
3337
export const MobileNavigationOverlay = ({ items, onCloseClick = noop, className }: MobileNavigationOverlayProps) => {
3438
const { language } = useLanguage()
39+
const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false)
3540
const translation = useTranslation(defaultMobileNavigationOverlayTranslation)
3641
const router = useRouter()
3742

3843
return (
39-
<nav
40-
className={tx('@(flex flex-col bg-gray-200 h-full w-full top-0 absolute px-8 py-6 gap-y-4 items-center z-[100] not-mobile:hidden)', className)}>
41-
<div className={tw('flex flex-row w-full items-center justify-between mb-2')}>
42-
<h2 className={tw('font-bold font-space text-2xl')}>{translation.navigation}</h2>
43-
<button className={tw('rounded-md bg-gray-300 hover:bg-gray-400 p-1')} onClick={onCloseClick}>
44-
<X size={24}/>
44+
<div
45+
className={tw('@(flex flex-col bg-gray-200 h-full w-full top-0 absolute px-8 py-6 z-[100] not-mobile:hidden justify-between)')}>
46+
<LanguageModal
47+
id="language-modal-mobile"
48+
isOpen={isLanguageModalOpen}
49+
onCloseClick={() => setIsLanguageModalOpen(false)}
50+
onBackgroundClick={() => setIsLanguageModalOpen(false)}
51+
onDone={() => setIsLanguageModalOpen(false)}
52+
containerClassName={tw('z-[102]')}
53+
/>
54+
<nav className={tx('@(flex flex-col gap-y-4 items-center)', className)}>
55+
<div className={tw('flex flex-row w-full items-center justify-between mb-2')}>
56+
<h2 className={tw('font-bold font-space text-2xl')}>{translation.navigation}</h2>
57+
<button className={tw('rounded-md bg-gray-300 hover:bg-gray-400 p-1')} onClick={onCloseClick}>
58+
<X size={24}/>
59+
</button>
60+
</div>
61+
{items.map((item, i) => (
62+
<Link
63+
href={item.url}
64+
key={i}
65+
className={tx(
66+
'flex flex-row justify-between items-center px-4 py-2 hover:bg-hw-primary-500/40 w-full text-lg font-semibold rounded-md',
67+
{ 'bg-hw-primary-500/30': router.pathname === item.url, 'bg-white': router.pathname !== item.url }
68+
)}
69+
>
70+
<div className={tw('flex flex-row gap-x-2 items-center')}>
71+
{item.icon}
72+
{item.name[language]}
73+
</div>
74+
<ArrowRightIcon size={24}/>
75+
</Link>
76+
))}
77+
</nav>
78+
<div className={tw('flex flex-col gap-y-4 items-center w-full')}>
79+
<button
80+
className={tw('flex flex-row w-full justify-between items-center px-4 py-2 bg-gray-50 hover:bg-hw-primary-500/40 font-semibold rounded-md')}
81+
onClick={() => setIsLanguageModalOpen(true)}
82+
>
83+
{languagesLocalNames[language]}
84+
<ArrowRightLeft size={24}/>
85+
</button>
86+
<button className={tw('flex flex-row w-full gap-x-2 items-center p-4 bg-gray-50 hover:bg-hw-primary-500/40 font-semibold rounded-md')}>
87+
<Avatar avatarUrl="https://helpwave.de/favicon.ico" alt="" size="small"/>
88+
{'Max Mustermann'}
4589
</button>
4690
</div>
47-
{items.map((item, i) => (
48-
<Link
49-
href={item.url}
50-
key={i}
51-
className={tx(
52-
'flex flex-row justify-between items-center px-4 py-2 hover:bg-hw-primary-500/40 w-full text-lg font-semibold rounded-md',
53-
{ 'bg-hw-primary-500/30': router.pathname === item.url, 'bg-white': router.pathname !== item.url }
54-
)}
55-
>
56-
<div className={tw('flex flex-row gap-x-2 items-center')}>
57-
{item.icon}
58-
{item.name[language]}
59-
</div>
60-
<ArrowRightIcon size={24}/>
61-
</Link>
62-
))}
63-
</nav>
91+
</div>
6492
)
6593
}
Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type { Languages } from '@helpwave/common/hooks/useLanguage'
2+
import { languagesLocalNames } from '@helpwave/common/hooks/useLanguage'
23
import { useLanguage } from '@helpwave/common/hooks/useLanguage'
3-
import { tx } from '@twind/core'
4+
import { tw, tx } from '@twind/core'
45
import { useRouter } from 'next/router'
56
import Link from 'next/link'
67
import type { ReactNode } from 'react'
8+
import { useState } from 'react'
9+
import { Avatar } from '@helpwave/common/components/Avatar'
10+
import { LanguageModal } from '@helpwave/common/components/modals/LanguageModal'
11+
import { ArrowRightLeft } from 'lucide-react'
712

813
export type NavItem = {
914
name: Record<Languages, string>,
@@ -22,25 +27,49 @@ export type NavSidebarProps = {
2227
*/
2328
export const NavigationSidebar = ({ items, className }: NavSidebarProps) => {
2429
const { language } = useLanguage()
30+
const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false)
2531
const router = useRouter()
2632

2733
const width = 250
2834

2935
return (
30-
<nav className={tx(`@(flex flex-col h-full bg-gray-100 min-w-[${width}px] max-w-[${width}px] overflow-y-auto)`, className)}>
31-
{items.map((item, i) => (
32-
<Link
33-
href={item.url}
34-
key={i}
35-
className={tx(
36-
'px-4 py-2 hover:bg-hw-primary-500/40 flex flex-row gap-x-2 items-center',
37-
{ 'bg-hw-primary-500/30': router.pathname == item.url }
38-
)}
36+
<div
37+
className={tx(`@(flex flex-col justify-between grow bg-gray-200 min-w-[${width}px] max-w-[${width}px])`, className)}>
38+
<LanguageModal
39+
id="language-modal"
40+
isOpen={isLanguageModalOpen}
41+
onCloseClick={() => setIsLanguageModalOpen(false)}
42+
onBackgroundClick={() => setIsLanguageModalOpen(false)}
43+
onDone={() => setIsLanguageModalOpen(false)}
44+
/>
45+
<nav className={tw('@(flex flex-col overflow-y-auto)')}>
46+
{items.map((item, i) => (
47+
<Link
48+
href={item.url}
49+
key={i}
50+
className={tx(
51+
'px-4 py-2 bg-gray-50 hover:bg-hw-primary-500/40 flex flex-row gap-x-2 items-center',
52+
{ 'bg-hw-primary-500/30': router.pathname == item.url }
53+
)}
54+
>
55+
{item.icon}
56+
{item.name[language]}
57+
</Link>
58+
))}
59+
</nav>
60+
<div className={tw('flex flex-col')}>
61+
<button
62+
className={tw('flex flex-row justify-between items-center px-4 py-2 bg-gray-50 hover:bg-hw-primary-500/40')}
63+
onClick={() => setIsLanguageModalOpen(true)}
3964
>
40-
{item.icon}
41-
{item.name[language]}
42-
</Link>
43-
))}
44-
</nav>
65+
{languagesLocalNames[language]}
66+
<ArrowRightLeft size={24}/>
67+
</button>
68+
<button className={tw('flex flex-row gap-x-2 items-center p-4 bg-gray-50 hover:bg-hw-primary-500/40')}>
69+
<Avatar avatarUrl="https://helpwave.de/favicon.ico" alt="" size="small"/>
70+
{'Max Mustermann'}
71+
</button>
72+
</div>
73+
</div>
4574
)
4675
}

customer/components/layout/Page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const Page = ({
5353
const [isNavigationVisible, setIsNavigationVisible] = useState(false)
5454

5555
const mainContent = (
56-
<div className={tw('flex flex-col w-full h-full overflow-y-scroll')}>
56+
<div className={tw('flex flex-col justify-between w-full h-full overflow-y-scroll')}>
5757
<main className={tx('@(flex flex-col max-w-[1200px] gap-y-6)', mainContainerClassName)}>
5858
{children}
5959
</main>
@@ -62,7 +62,7 @@ export const Page = ({
6262
)
6363

6464
return (
65-
<div className={tw('relative flex flex-col w-screen h-screen overflow-hidden')}>
65+
<div className={tw('relative not-mobile:(grid grid-rows-[auto_1fr]) mobile:(flex flex-col) w-screen h-screen overflow-hidden')}>
6666
<Head>
6767
<title>{pageTitle}</title>
6868
</Head>
@@ -87,11 +87,11 @@ export const Page = ({
8787
{isNavigationVisible && (
8888
<MobileNavigationOverlay items={navItems} onCloseClick={() => setIsNavigationVisible(false)}/>
8989
)}
90-
<div className={tw('flex flex-row w-full h-full grow mobile:hidden')}>
90+
<div className={tw('flex flex-row grow mobile:hidden overflow-hidden')}>
9191
{<NavigationSidebar items={navItems}/>}
9292
{mainContent}
9393
</div>
94-
<div className={tw('not-mobile:hidden w-full h-full')}>
94+
<div className={tw('flex flex-col h-full w-full not-mobile:hidden')}>
9595
{mainContent}
9696
</div>
9797
</div>

customer/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const nextConfig = {
77
images: {
88
dangerouslyAllowSVG: true,
99
domains: ['cdn.helpwave.de', 'customer.helpwave.de', 'helpwave.de'],
10+
unoptimized: true,
1011
},
1112
}
1213

lib/coloring/util.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ export const getColoring = ({
2020
}
2121
const colorValue = colorStyleMapping[style]
2222
const hoverMapping: Record<ColoringStyle, string> = {
23-
'background': ` hover:bg-${color}-${modeShifted(500)}`,
23+
'background': `hover:bg-${color}-${modeShifted(500)}`,
2424
'tonal': `bg-${color}-${modeShifted(400)}/40 text-${color}-${modeShifted(800)}`,
2525
'tonal-opaque': `bg-${color}-${modeShifted(250)} text-${color}-${modeShifted(800)}`,
26-
'text': ` hover:text-${color}-${modeShifted(500)}`,
27-
'text-border': ` hover:text-${color}-${modeShifted(500)} hover:border-${color}-${modeShifted(500)}`
26+
'text': `hover:text-${color}-${modeShifted(500)}`,
27+
'text-border': `hover:text-${color}-${modeShifted(500)} hover:border-${color}-${modeShifted(500)}`
2828
}
2929
const hoverValue = hover ? hoverMapping[style] : ''
30-
return colorValue + hoverValue
30+
return `@(${colorValue} ${hoverValue})`
3131
}

lib/components/modals/Modal.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ export type ModalHeaderProps = {
3737
* A default Header to be used by modals to have a uniform design
3838
*/
3939
export const ModalHeader = ({
40-
overwriteTranslation,
41-
onCloseClick,
42-
title,
43-
titleText = '',
44-
description,
45-
descriptionText = ''
46-
}: PropsForTranslation<ModalHeaderTranslation, ModalHeaderProps>) => {
40+
overwriteTranslation,
41+
onCloseClick,
42+
title,
43+
titleText = '',
44+
description,
45+
descriptionText = ''
46+
}: PropsForTranslation<ModalHeaderTranslation, ModalHeaderProps>) => {
4747
const translation = useTranslation(defaultModalHeaderTranslation, overwriteTranslation)
4848
return (
4949
<div className={tw('flex flex-col')}>
@@ -76,6 +76,7 @@ export type ModalProps = {
7676
onBackgroundClick?: MouseEventHandler<HTMLDivElement>,
7777
backgroundClassName?: string,
7878
modalClassName?: string,
79+
containerClassName?: string,
7980
} & ModalHeaderProps
8081

8182
/**
@@ -86,14 +87,15 @@ export type ModalProps = {
8687
* DO NOT Conditionally render this always use the isOpen to ensure that the ModalRegister is working properly
8788
*/
8889
export const Modal = ({
89-
children,
90-
id,
91-
isOpen,
92-
onBackgroundClick,
93-
backgroundClassName = '',
94-
modalClassName = '',
95-
...modalHeaderProps
96-
}: PropsWithChildren<ModalProps>) => {
90+
children,
91+
id,
92+
isOpen,
93+
onBackgroundClick,
94+
backgroundClassName = '',
95+
modalClassName = '',
96+
containerClassName = '',
97+
...modalHeaderProps
98+
}: PropsWithChildren<ModalProps>) => {
9799
const modalRoot = typeof window !== 'undefined' ? document.getElementById(modalRootName) : null
98100
const {
99101
register,
@@ -119,7 +121,7 @@ export const Modal = ({
119121
const isSecondLast = register.length < 2 || register[register.length - 2] === id
120122

121123
return ReactDOM.createPortal(
122-
<div className={tx('fixed inset-0 overflow-y-auto z-[99]')} id={id}>
124+
<div className={tx('@(fixed inset-0 overflow-y-auto z-[99])', containerClassName)} id={id}>
123125
{isLast && (
124126
<div
125127
className={tx('fixed inset-0 h-screen w-screen', backgroundClassName, {

lib/hooks/useLanguage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import useLocalStorage from './useLocalStorage'
44

55
export const languages = ['en', 'de'] as const
66
export type Languages = typeof languages[number]
7+
export const languagesLocalNames: Record<Languages, string> = {
8+
en: 'English',
9+
de: 'Deutsch',
10+
}
711

812
export const DEFAULT_LANGUAGE = 'en'
913

0 commit comments

Comments
 (0)