Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: テキスト系のシンプルなコンポーネントをmemo化する #5406

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { FaCircleExclamationIcon } from '../Icon'
import { Cluster, Stack } from '../Layout'
import { StatusLabel } from '../StatusLabel'
import { Text, TextProps } from '../Text'
import { VisuallyHiddenText, visuallyHiddenText } from '../VisuallyHiddenText'
import { VisuallyHiddenText, visuallyHiddenTextClassNameGenerator } from '../VisuallyHiddenText'

import type { Gap } from '../../types'

Expand Down Expand Up @@ -191,7 +191,9 @@ export const ActualFormControl: React.FC<Props & ElementProps> = ({

return {
wrapperStyle: wrapper({ className }),
labelStyle: label({ className: dangerouslyTitleHidden ? visuallyHiddenText() : '' }),
labelStyle: label({
className: dangerouslyTitleHidden ? visuallyHiddenTextClassNameGenerator() : '',
}),
errorListStyle: errorList(),
errorIconStyle: errorIcon(),
underTitleStackStyle: underTitleStack(),
Expand Down
52 changes: 28 additions & 24 deletions packages/smarthr-ui/src/components/Heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import React, { ComponentProps, FC, PropsWithChildren, useContext, useMemo } from 'react'
import React, {
type ComponentProps,
type PropsWithChildren,
memo,
useContext,
useMemo,
} from 'react'
import { tv } from 'tailwind-variants'

import { LevelContext } from '../SectioningContent'
Expand Down Expand Up @@ -42,7 +48,7 @@ const generateTagProps = (level: number, tag?: HeadingTagTypes) => {
}
}

const heading = tv({
const classNameGenerator = tv({
base: 'smarthr-ui-Heading',
variants: {
visuallyHidden: {
Expand All @@ -54,27 +60,25 @@ const heading = tv({
},
})

export const Heading: FC<Props & ElementProps> = ({
tag,
type = 'sectionTitle',
className,
visuallyHidden,
...props
}) => {
const level = useContext(LevelContext)
const tagProps = useMemo(() => generateTagProps(level, tag), [level, tag])
const styles = useMemo(() => heading({ visuallyHidden, className }), [className, visuallyHidden])
const actualProps = {
...props,
...STYLE_TYPE_MAP[type],
...tagProps,
className: styles,
}
export const Heading = memo<Props & ElementProps>(
({ tag, type = 'sectionTitle', className, visuallyHidden, ...props }) => {
const level = useContext(LevelContext)
const tagProps = useMemo(() => generateTagProps(level, tag), [level, tag])
const actualClassName = useMemo(
() => classNameGenerator({ visuallyHidden, className }),
[className, visuallyHidden],
)
const Component = visuallyHidden ? VisuallyHiddenText : Text

return visuallyHidden ? <VisuallyHiddenText {...actualProps} /> : <Text {...actualProps} />
}
return (
<Component {...props} {...STYLE_TYPE_MAP[type]} {...tagProps} className={actualClassName} />
)
},
)

export const PageHeading: FC<Omit<Props & ElementProps, 'visuallyHidden' | 'tag'>> = ({
type = 'screenTitle',
...props
}) => <Heading {...props} type={type} tag="h1" /> // eslint-disable-line smarthr/a11y-heading-in-sectioning-content
export const PageHeading = memo<Omit<Props & ElementProps, 'visuallyHidden' | 'tag'>>(
({ type = 'screenTitle', ...props }) => (
// eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content
<Heading {...props} type={type} tag="h1" />
),
)
20 changes: 12 additions & 8 deletions packages/smarthr-ui/src/components/Text/Text.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ComponentProps, PropsWithChildren, useMemo } from 'react'
import React, { type ComponentProps, type PropsWithChildren, memo, useMemo } from 'react'
import { VariantProps, tv } from 'tailwind-variants'

type StyleType =
Expand All @@ -8,7 +8,7 @@ type StyleType =
| 'subBlockTitle'
| 'subSubBlockTitle'

export const STYLE_TYPE_MAP: { [key in StyleType]: VariantProps<typeof text> } = {
export const STYLE_TYPE_MAP: { [key in StyleType]: VariantProps<typeof classNameGenerator> } = {
screenTitle: {
size: 'XL',
leading: 'TIGHT',
Expand Down Expand Up @@ -44,7 +44,7 @@ const UNDEFINED_STYLE_VALUES = {
color: undefined,
}

const text = tv({
const classNameGenerator = tv({
variants: {
size: {
XXS: 'shr-text-2xs',
Expand Down Expand Up @@ -87,7 +87,9 @@ const text = tv({
})

// VariantProps を使うとコメントが書けない〜🥹
export type TextProps<T extends React.ElementType = 'span'> = VariantProps<typeof text> & {
export type TextProps<T extends React.ElementType = 'span'> = VariantProps<
typeof classNameGenerator
> & {
/** テキストコンポーネントの HTML タグ名。初期値は span */
as?: T
/** 強調するかどうかの真偽値。指定すると em 要素になる */
Expand All @@ -96,7 +98,7 @@ export type TextProps<T extends React.ElementType = 'span'> = VariantProps<typeo
styleType?: StyleType
}

export const Text = <T extends React.ElementType = 'span'>({
const ActualText = <T extends React.ElementType = 'span'>({
emphasis,
styleType,
weight = emphasis ? 'bold' : undefined,
Expand All @@ -109,12 +111,12 @@ export const Text = <T extends React.ElementType = 'span'>({
className,
...props
}: PropsWithChildren<TextProps<T> & ComponentProps<T>>) => {
const styles = useMemo(() => {
const actualClassName = useMemo(() => {
const styleTypeValues = styleType
? STYLE_TYPE_MAP[styleType as StyleType]
: UNDEFINED_STYLE_VALUES

return text({
return classNameGenerator({
size: size || styleTypeValues.size,
weight: weight || styleTypeValues.weight,
color: color || styleTypeValues.color,
Expand All @@ -125,5 +127,7 @@ export const Text = <T extends React.ElementType = 'span'>({
})
}, [size, weight, italic, color, leading, whiteSpace, className, styleType])

return <Component {...props} className={styles} />
return <Component {...props} className={actualClassName} />
}

export const Text = memo(ActualText) as typeof ActualText
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 changes: 10 additions & 7 deletions packages/smarthr-ui/src/components/TextLink/TextLink.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, {
ComponentPropsWithoutRef,
ElementType,
FC,
PropsWithoutRef,
ReactNode,
Ref,
type ComponentPropsWithoutRef,
type ElementType,
type FC,
type PropsWithoutRef,
type ReactNode,
type Ref,
forwardRef,
memo,
useMemo,
} from 'react'
import { tv } from 'tailwind-variants'
Expand Down Expand Up @@ -45,7 +46,7 @@ const { anchor, prefixWrapper, suffixWrapper } = textLink()
const prefixWrapperClassName = prefixWrapper()
const suffixWrapperClassName = suffixWrapper()

export const TextLink: TextLinkComponent = forwardRef(
const ActualTextLink: TextLinkComponent = forwardRef(
<T extends ElementType = 'a'>(
{
elementAs,
Expand Down Expand Up @@ -116,3 +117,5 @@ export const TextLink: TextLinkComponent = forwardRef(
)
},
)

export const TextLink = memo(ActualTextLink) as typeof ActualTextLink
35 changes: 18 additions & 17 deletions packages/smarthr-ui/src/components/Textarea/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@ const calculateIdealRows = (
if (!element) {
return 0
}

// 現在の入力値に応じた行数
const currentInputValueRows = Math.floor(
element.scrollHeight / (defaultHtmlFontSize * Number(lineHeight.normal)),
)

return currentInputValueRows < maxRows ? currentInputValueRows : maxRows
}

Expand Down Expand Up @@ -251,42 +253,41 @@ export const Textarea = forwardRef<HTMLTextAreaElement, Props & ElementProps>(
[autoResize, maxRows, onInput, rows],
)

const { textareaStyleProps, counterStyle, counterTextStyle } = useMemo(() => {
const textareaStyle = useMemo(
() => ({ width: typeof width === 'number' ? `${width}px` : width }),
[width],
)
const countError = maxLetters && count > maxLetters
const classNames = useMemo(() => {
const { textareaEl, counter, counterText } = textarea()

return {
textareaStyleProps: {
className: textareaEl({ className }),
style: { width: typeof width === 'number' ? `${width}px` : width },
},
counterStyle: counter(),
counterTextStyle: counterText({ error: !!(maxLetters && maxLetters - count < 0) }),
textarea: textareaEl({ className }),
counter: counter(),
counterText: counterText({ error: !!countError }),
}
}, [className, count, maxLetters, width])

const hasInputError = useMemo(() => {
const isCharLengthExceeded = maxLetters && count > maxLetters
return error || isCharLengthExceeded || undefined
}, [error, maxLetters, count])
}, [countError, className])

const body = (
<textarea
{...props}
{...textareaStyleProps}
{...(maxLetters && { 'aria-describedby': `${maxLettersNoticeId} ${actualMaxLettersId}` })}
data-smarthr-ui-input="true"
onChange={handleChange}
ref={textareaRef}
aria-invalid={hasInputError || undefined}
aria-invalid={error || countError || undefined}
rows={interimRows}
onInput={handleInput}
className={classNames.textarea}
style={textareaStyle}
/>
)

return maxLetters ? (
<span>
{body}
<span className={counterStyle}>
<span className={counterTextStyle} id={actualMaxLettersId} aria-hidden={true}>
<span className={classNames.counter}>
<span className={classNames.counterText} id={actualMaxLettersId} aria-hidden={true}>
{counterVisualMessage}
</span>
</span>
Expand Down
19 changes: 11 additions & 8 deletions packages/smarthr-ui/src/components/UpwardLink/UpwardLink.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { type ComponentProps, useMemo } from 'react'
import React, { type ComponentProps, memo, useMemo } from 'react'
import { VariantProps, tv } from 'tailwind-variants'

import { FaArrowLeftIcon } from '../Icon'
import { TextLink } from '../TextLink'

const styleGenerator = tv({
const classNameGenerator = tv({
base: 'shr-leading-none',
variants: {
indent: {
Expand All @@ -15,17 +15,20 @@ const styleGenerator = tv({
})

type Props = Omit<ComponentProps<typeof TextLink>, 'prefix' | 'suffix'> &
VariantProps<typeof styleGenerator> & {
VariantProps<typeof classNameGenerator> & {
/** `TextLink`に渡す `elementAs` をオプションで指定 */
elementAs?: ComponentProps<typeof TextLink>['elementAs']
}

export const UpwardLink: React.FC<Props> = ({ indent = true, className, elementAs, ...rest }) => {
const style = useMemo(() => styleGenerator({ indent, className }), [indent, className])
export const UpwardLink = memo<Props>(({ indent, className, ...rest }) => {
const actualClassName = useMemo(
() => classNameGenerator({ indent: indent ?? true, className }),
[indent, className],
)

return (
<div className={style}>
<TextLink {...rest} elementAs={elementAs} prefix={<FaArrowLeftIcon />} />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elementAsが消えているように見えますが、https://github.com/kufu/smarthr-ui/pull/5406/files#diff-5f096a050e1946adb3b1a45c9737159ebceea44f39788f072ea5b19eee4385baR23 でrestに含めるようにしているため、指定としては同じ状態になっています

<div className={actualClassName}>
<TextLink {...rest} prefix={<FaArrowLeftIcon />} />
</div>
)
}
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ComponentProps, PropsWithChildren, useMemo } from 'react'
import React, { type ComponentProps, type PropsWithChildren, memo, useMemo } from 'react'
import { tv } from 'tailwind-variants'

export const visuallyHiddenText = tv({
export const visuallyHiddenTextClassNameGenerator = tv({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visuallyHiddenText ではコンポーネントと区別がつきにくいため、明確にしました。
長くてちょっと...とも思いますが基本的にsmarthr-ui内に隠蔽される概念のため、空目・typoよりはマシかと...

base: 'shr-absolute -shr-top-px shr-left-0 shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap shr-border-0 shr-p-0 [clip-path:inset(100%)] [clip:rect(0_0_0_0)]',
})

Expand All @@ -10,12 +10,17 @@ type Props<T extends React.ElementType> = PropsWithChildren<{
}> &
ComponentProps<T>

export const VisuallyHiddenText = <T extends React.ElementType = 'span'>({
const ActualVisuallyHiddenText = <T extends React.ElementType = 'span'>({
as: Component = 'span',
className,
...props
}: Props<T>) => {
const styles = useMemo(() => visuallyHiddenText({ className }), [className])
const actualClassName = useMemo(
() => visuallyHiddenTextClassNameGenerator({ className }),
[className],
)

return <Component {...props} className={styles} />
return <Component {...props} className={actualClassName} />
}

export const VisuallyHiddenText = memo(ActualVisuallyHiddenText) as typeof ActualVisuallyHiddenText
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { VisuallyHiddenText, visuallyHiddenText } from './VisuallyHiddenText'
export { VisuallyHiddenText, visuallyHiddenTextClassNameGenerator } from './VisuallyHiddenText'