Skip to content

Commit

Permalink
feat: デスクトップサイズの AppHeader を実装
Browse files Browse the repository at this point in the history
  • Loading branch information
nabeliwo committed Dec 17, 2024
1 parent 6772cb8 commit d04f30e
Show file tree
Hide file tree
Showing 21 changed files with 1,173 additions and 0 deletions.
147 changes: 147 additions & 0 deletions packages/smarthr-ui/src/components/AppHeader/AppHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { action } from '@storybook/addon-actions'
import { Meta, StoryObj } from '@storybook/react/*'
import React, { FC, PropsWithChildren } from 'react'

import { AppHeader } from './AppHeader'

const CustomLink: FC<PropsWithChildren<{ to: string; className?: string }>> = (props) => (
<a {...props} href={props.to} className={props.className ?? ''}>
{props.children}
</a>
)

const AdditionalContent: FC<PropsWithChildren> = ({ children }) => (
<div style={{ background: 'rgb(242 242 242)', padding: '4px 8px' }}>{children}</div>
)

const meta = {
title: 'Navigation(ナビゲーション)/AppHeader',
component: AppHeader,
args: {
children: <AdditionalContent>children</AdditionalContent>,
appName: '勤怠管理',
tenants: [
{
id: 'tenant-1',
name: '株式会社テストテナント壱',
},
{
id: 'tenant-2',
name: '株式会社テストテナント弐',
},
],
currentTenantId: 'tenant-1',
onTenantSelect: action('テナント選択'),
schoolUrl: 'https://exmaple.com',
helpPageUrl: 'https://exmaple.com',
locale: {
selectedLocale: 'ja',
onSelectLocale: action('locale'),
},
userInfo: {
email: '[email protected]',
empCode: '001',
firstName: '須磨',
lastName: '栄子',
accountUrl: 'https://exmaple.com',
},
desktopAdditionalContent: <AdditionalContent>desktopAdditionalContent</AdditionalContent>,
navigations: [
{
children: 'aタグ',
href: 'https://exmaple.com',
},
{
children: 'カスタムタグ',
elementAs: CustomLink,
to: 'https://exmaple.com',
},
{
children: 'ボタン',
onClick: action('AppNavボタンクリック'),
},
{
children: 'ドロップダウン',
childNavigations: [
{
children: 'aタグ',
href: 'https://exmaple.com',
},
{
children: 'カスタムタグ',
elementAs: CustomLink,
to: 'https://exmaple.com',
},
{
children: 'ボタン',
onClick: action('ボタンクリック'),
},
],
},
{
children: 'グループ',
childNavigations: [
{
title: 'グループ1',
childNavigations: [
{
children: 'グループ1_アイテム1',
href: 'https://exmaple.com',
current: true,
},
{
children: 'グループ1_アイテム2',
href: 'https://exmaple.com',
},
],
},
{
title: 'グループ2',
childNavigations: [
{
children: 'グループ2_アイテム1',
href: 'https://exmaple.com',
},
{
children: 'グループ2_アイテム2',
href: 'https://exmaple.com',
},
],
},
],
},
],
desktopNavigationAdditionalContent: (
<AdditionalContent>desktopNavigationAdditionalContent</AdditionalContent>
),
releaseNote: {
links: [
{
title: 'リリースノート1',
url: 'https://exmaple.com',
},
{
title: 'リリースノート2',
url: 'https://exmaple.com',
},
{
title: 'リリースノート3',
url: 'https://exmaple.com',
},
],
indexUrl: 'https://exmaple.com',
},
},
} satisfies Meta<typeof AppHeader>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {}

export const EnableNew: Story = {
args: {
enableNew: true,
},
}
22 changes: 22 additions & 0 deletions packages/smarthr-ui/src/components/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { FC } from 'react'

import { DesktopHeader } from './components/desktop/DesktopHeader'
import { LocaleContextProvider } from './hooks/useLocale'
import { mediaQuery, useMediaQuery } from './hooks/useMediaQuery'
import { MobileHeader } from './components/mobile/MobileHeader'
import { HeaderProps } from './types'

export const AppHeader: FC<HeaderProps> = ({ locale, children, ...props }) => {
// NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、
// wovn の言語切替カスタム UI の挿入対象となる DOM ("wovn-embedded-widget-anchor" クラスを持った div) が複数描画されていると、
// wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。
const isDesktop = useMediaQuery(mediaQuery.desktop)
const isMobile = useMediaQuery(mediaQuery.mobile)

return (
<LocaleContextProvider locale={locale}>
<DesktopHeader {...props}>{isDesktop && children}</DesktopHeader>
<MobileHeader {...props}>{isMobile && children}</MobileHeader>
</LocaleContextProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { ComponentPropsWithoutRef, FC, ReactNode } from 'react'
import { tv } from 'tailwind-variants'

export const commonButton = tv({
base: [
'[&&]:shr-flex [&&]:shr-items-center [&&]:shr-w-full [&&]:shr-px-1 [&&]:shr-py-0.5 [&&]:shr-box-border [&&]:shr-bg-transparent [&&]:shr-text-base [&&]:shr-text-black [&&]:shr-leading-normal [&&]:shr-no-underline [&&]:shr-rounded-m [&&]:shr-cursor-pointer [&&]:shr-border-none',
'[&&]:hover:shr-bg-white-darken',
'[&&]:focus-visible:shr-bg-white-darken',
],
variants: {
prefix: {
true: ['[&&]:shr-gap-0.5'],
},
current: {
true: ['[&&]:shr-bg-white-darken'],
},
boldWhenCurrent: {
true: null,
false: ['[&&]:shr-font-normal'],
},
},
compoundVariants: [
{
boldWhenCurrent: true,
current: true,
className: ['[&&]:shr-font-bold'],
},
],
})

type AnchorProps = Omit<ComponentPropsWithoutRef<'a'>, 'prefix'>
type ButtonProps = Omit<ComponentPropsWithoutRef<'button'>, 'prefix'>

type Props = (({ elementAs: 'a' } & AnchorProps) | ({ elementAs: 'button' } & ButtonProps)) & {
prefix?: ReactNode
current?: boolean
boldWhenCurrent?: boolean
}

export const CommonButton: FC<Props> = ({
elementAs,
prefix,
current,
boldWhenCurrent,
className,
...props
}) => {
const commonButtonStyle = commonButton({
prefix: Boolean(prefix),
current,
boldWhenCurrent,
className,
})

if (elementAs === 'a') {
return (
<a {...(props as AnchorProps)} className={commonButtonStyle}>
{prefix}
{props.children}
</a>
)
} else if (elementAs === 'button') {
return (
// eslint-disable-next-line smarthr/best-practice-for-button-element
<button {...(props as ButtonProps)} className={commonButtonStyle}>
{prefix}
{props.children}
</button>
)
} else {
throw new Error(elementAs satisfies never)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React, { PropsWithChildren, memo } from 'react'

export const Translate = memo<PropsWithChildren>(({ children }) => (
<span data-wovn-enable="true">{children}</span>
))
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { FC } from 'react'

import { Header, HeaderLink, LanguageSwitcher } from '../../../Header'
import { FaCircleQuestionIcon, FaGraduationCapIcon, FaRegCircleQuestionIcon } from '../../../Icon'
import { Cluster } from '../../../Layout'
import { useLocale } from '../../hooks/useLocale'
import { useTranslate } from '../../hooks/useTranslate'
import { localeMap } from '../../multilingualization'
import { HeaderProps } from '../../types'
import { Translate } from '../common/Translate'

import { Navigation } from './Navigation'
import { UserInfo } from './UserInfo.tsx'

export const DesktopHeader: FC<HeaderProps> = ({
enableNew,
className = '',
appName,
tenants,
currentTenantId,
schoolUrl,
helpPageUrl,
children,
userInfo,
desktopAdditionalContent,
navigations,
desktopNavigationAdditionalContent,
releaseNote,
...props
}) => {
const translate = useTranslate()
const { locale } = useLocale()

return (
<>
<Header
{...props}
enableNew={enableNew}
className={`${className} max-[751px]:!shr-hidden`}
featureName={appName}
tenants={tenants}
currentTenantId={currentTenantId}
>
<Cluster align="center" className="shr--me-0.25">
{!enableNew && schoolUrl && (
<HeaderLink
href={schoolUrl}
prefix={<FaGraduationCapIcon />}
className="shr-flex shr-items-center shr-py-0.75 shr-leading-none"
>
<Translate>{translate('common/school')}</Translate>
</HeaderLink>
)}

{helpPageUrl && (
<HeaderLink
href={helpPageUrl}
prefix={enableNew ? <FaRegCircleQuestionIcon /> : <FaCircleQuestionIcon />}
className={
enableNew ? undefined : 'shr-flex shr-items-center shr-py-0.75 shr-leading-none'
}
enableNew={enableNew}
>
<Translate>{translate('common/help')}</Translate>
</HeaderLink>
)}

{locale && (
<LanguageSwitcher
localeMap={localeMap}
locale={locale.selectedLocale}
onLanguageSelect={locale.onSelectLocale as (locale: string) => void}
enableNew={enableNew}
/>
)}

{children}

{userInfo && (
<UserInfo
{...userInfo}
tenants={tenants}
currentTenantId={currentTenantId}
desktopAdditionalContent={desktopAdditionalContent}
enableNew={enableNew}
/>
)}
</Cluster>
</Header>

{navigations && (
<Navigation
appName={appName}
navigations={navigations}
additionalContent={desktopNavigationAdditionalContent}
releaseNote={releaseNote}
enableNew={enableNew}
/>
)}
</>
)
}
Loading

0 comments on commit d04f30e

Please sign in to comment.