-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,173 additions
and
0 deletions.
There are no files selected for viewing
147 changes: 147 additions & 0 deletions
147
packages/smarthr-ui/src/components/AppHeader/AppHeader.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
22
packages/smarthr-ui/src/components/AppHeader/AppHeader.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
73 changes: 73 additions & 0 deletions
73
packages/smarthr-ui/src/components/AppHeader/components/common/CommonButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
packages/smarthr-ui/src/components/AppHeader/components/common/Translate.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
)) |
102 changes: 102 additions & 0 deletions
102
packages/smarthr-ui/src/components/AppHeader/components/desktop/DesktopHeader.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
/> | ||
)} | ||
</> | ||
) | ||
} |
Oops, something went wrong.