From 10aeb7e13e61d068c0c2a7baafa08dac72c2a01d Mon Sep 17 00:00:00 2001 From: Qs-F Date: Thu, 23 Nov 2023 16:54:19 +0900 Subject: [PATCH 1/3] Add Element type to abstract ReactNode and JSX.Element --- .../src/system/{polyComponent.ts => componentType.ts} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename packages/for-ui/src/system/{polyComponent.ts => componentType.ts} (81%) diff --git a/packages/for-ui/src/system/polyComponent.ts b/packages/for-ui/src/system/componentType.ts similarity index 81% rename from packages/for-ui/src/system/polyComponent.ts rename to packages/for-ui/src/system/componentType.ts index 40f883dc..906c7dda 100644 --- a/packages/for-ui/src/system/polyComponent.ts +++ b/packages/for-ui/src/system/componentType.ts @@ -1,4 +1,4 @@ -import { ComponentPropsWithoutRef, ComponentPropsWithRef, ElementType } from 'react'; +import { ComponentPropsWithoutRef, ComponentPropsWithRef, ElementType, FC } from 'react'; import { PreservedOmit } from './preservedOmit'; export type Ref = ComponentPropsWithRef['ref']; @@ -12,7 +12,7 @@ export type AsProps = { as?: As; }; -export type ComponentProps = AsProps & +export type ComponentPropsWithAs = AsProps & Props & RefProps & PreservedOmit, keyof (Props & AsProps & RefProps)>; @@ -20,3 +20,5 @@ export type ComponentProps = AsPro export type ElementTypeToHTMLElement = Element extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Element] : Element; + +export type Element = ReturnType; From 810bc380dc2e790a64bdfcd4e588003b41e9575b Mon Sep 17 00:00:00 2001 From: Qs-F Date: Thu, 23 Nov 2023 16:54:51 +0900 Subject: [PATCH 2/3] Fix to use Element type to return React component --- packages/for-ui/src/button/Button.tsx | 295 ++++++++++++----------- packages/for-ui/src/chip/Chip.tsx | 6 +- packages/for-ui/src/chip/FullChip.tsx | 8 +- packages/for-ui/src/chip/LimitedChip.tsx | 8 +- packages/for-ui/src/table/TableCell.tsx | 6 +- 5 files changed, 164 insertions(+), 159 deletions(-) diff --git a/packages/for-ui/src/button/Button.tsx b/packages/for-ui/src/button/Button.tsx index 9fc82a08..02a1e1f8 100644 --- a/packages/for-ui/src/button/Button.tsx +++ b/packages/for-ui/src/button/Button.tsx @@ -1,15 +1,16 @@ -import { Children, ComponentPropsWithoutRef, ElementType, forwardRef, ReactNode, Ref, useMemo } from 'react'; +import { Children, ElementType, forwardRef, ReactNode, useMemo } from 'react'; import MuiButton, { ButtonUnstyledProps as MuiButtonProps } from '@mui/base/ButtonUnstyled'; import { LoadingButtonProps } from '@mui/lab/LoadingButton'; import { Loader } from '../loader'; +import { ComponentPropsWithAs, Element, Ref } from '../system/componentType'; import { fsx } from '../system/fsx'; import { walkChildren } from '../system/walkChildren'; // Iterable seems to contain string but cannot be excluded, so added as sum type. type Child = Exclude> | string; -export type ButtonProps = MuiButtonProps & - ComponentPropsWithoutRef & { +export type ButtonProps = ComponentPropsWithAs< + MuiButtonProps & { /** * 種類を指定 * @@ -97,7 +98,9 @@ export type ButtonProps = MuiButtonProps color?: 'primary' | 'secondary' | 'default'; className?: string; - }; + }, + As +>; const extractText = (root: ReactNode): string => { let ret = ''; @@ -118,148 +121,150 @@ const structures = ['text', 'icon', 'text-icon', 'icon-text'] as const; type Structure = (typeof structures)[number]; -const _Button = ({ - as, - variant = 'outlined', - intention: passedIntention = 'subtle', - size = 'large', - disabled = false, - loading = false, - startIcon, - endIcon, - color, - children, - _ref, - className, - ...rest -}: ButtonProps & { _ref?: Ref }): JSX.Element => { - const component = as || 'button'; - const childTexts = useMemo(() => Children.map(children, extractText) || [], [children]); - const label = childTexts.join(''); - const structure: Structure = useMemo(() => { - if ((childTexts.at(0) && !childTexts.at(-1)) || (endIcon && children)) { - return 'text-icon'; - } - if ((!childTexts.at(0) && childTexts.at(-1)) || (startIcon && children)) { - return 'icon-text'; - } - if (!childTexts.at(0) || (startIcon && !children)) { - return 'icon'; - } - return 'text'; - }, [startIcon, endIcon, children, childTexts]); +type ButtonComponent = (props: ButtonProps) => Element; - // Legacy support for color props - // If not needed, rename the passedIntention to intention. +export const Button: ButtonComponent = forwardRef( + ( + { + as, + variant = 'outlined', + intention: passedIntention = 'subtle', + size = 'large', + disabled = false, + loading = false, + startIcon, + endIcon, + color, + children, + className, + ...rest + }: ButtonProps, + ref: Ref, + ): JSX.Element => { + const component = as || 'button'; + const childTexts = useMemo(() => Children.map(children, extractText) || [], [children]); + const label = childTexts.join(''); + const structure: Structure = useMemo(() => { + if ((childTexts.at(0) && !childTexts.at(-1)) || (endIcon && children)) { + return 'text-icon'; + } + if ((!childTexts.at(0) && childTexts.at(-1)) || (startIcon && children)) { + return 'icon-text'; + } + if (!childTexts.at(0) || (startIcon && !children)) { + return 'icon'; + } + return 'text'; + }, [startIcon, endIcon, children, childTexts]); - const intention = color - ? ( - { - primary: 'primary', - secondary: 'secondary', - default: 'primary', - } as const - )[color] - : passedIntention; + // Legacy support for color props + // If not needed, rename the passedIntention to intention. - return ( - - component={component} - ref={_ref} - disabled={disabled || loading} - aria-label={label || rest['aria-label'] || 'button'} - aria-busy={loading} - className={fsx([ - `rounded-1.5 focus-visible:shadow-focused relative flex h-fit w-fit shrink-0 flex-row items-center justify-center font-sans outline-none disabled:cursor-not-allowed [&_svg]:fill-inherit`, - { - text: { - large: `px-4 py-2 gap-1`, - medium: `px-4 py-1 gap-1`, - small: `px-2 py-0.5 gap-0.5`, - }, - icon: { - large: `p-2 gap-1 [&_svg]:w-6 [&_svg]:h-6`, - medium: `p-1 gap-1 [&_svg]:w-6 [&_svg]:h-6`, - small: `p-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, - }, - 'text-icon': { - large: `pl-4 pr-3 py-2 gap-1 [&_svg]:w-4 [&_svg]:h-4`, - medium: `pl-4 pr-2 py-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, - small: `pl-2 pr-1 py-0.5 gap-0.5 [&_svg]:w-3 [&_svg]:h-3`, - }, - 'icon-text': { - large: `pl-3 pr-4 py-2 gap-1 [&_svg]:w-4 [&_svg]:h-4`, - medium: `pl-2 pr-4 py-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, - small: `pl-1 pr-2 py-0.5 gap-0.5 [&_svg]:w-3 [&_svg]:h-3`, - }, - }[structure][size], - { - large: `text-r`, - medium: `text-r`, - small: `text-s`, - }[size], - { - filled: `font-bold disabled:bg-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, - outlined: `font-regular outline outline-1 -outline-offset-1 disabled:bg-shade-dark-disabled disabled:outline-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, - text: `font-bold disabled:bg-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, - }[variant], - { - subtle: { - filled: [ - `bg-shade-light-default hover:bg-shade-light-hover focus-visible:bg-shade-light-hover text-shade-medium-default fill-shade-medium-default`, - structure === 'icon' && `fill-shade-dark-default`, - ], - outlined: [ - `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-shade-medium-default text-shade-dark-default fill-shade-medium-default`, - structure === 'icon' && `fill-shade-dark-default`, - ], - text: [ - `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-shade-medium-default fill-shade-medium-default`, - structure === 'icon' && `fill-shade-dark-default`, - ], - }, - primary: { - filled: `bg-primary-dark-default hover:bg-primary-dark-hover focus-visible:bg-primary-dark-hover text-shade-white-default fill-shade-white-default`, - outlined: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-primary-dark-default text-primary-dark-default fill-primary-dark-default`, - text: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-primary-dark-default fill-primary-dark-default`, - }, - secondary: { - filled: `bg-secondary-dark-default hover:bg-secondary-dark-hover focus-visible:bg-secondary-dark-hover text-shade-white-default fill-shade-white-default`, - outlined: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-secondary-dark-default text-secondary-dark-default fill-secondary-dark-default`, - text: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-secondary-dark-default fill-secondary-dark-default`, - }, - shade: { - filled: `bg-shade-dark-default hover:bg-shade-dark-hover text-shade-white-default fill-shade-white-default`, - outlined: `bg-shade-white-default hover:bg-shade-white-hover outline-shade-dark-default text-shade-dark-default fill-shade-dark-default`, - text: `bg-shade-white-default hover:bg-shade-white-hover text-shade-dark-default fill-shade-dark-default`, - }, - negative: { - filled: `bg-negative-dark-default hover:bg-negative-dark-hover text-shade-white-default fill-shade-white-default`, - outlined: `bg-shade-white-default hover:bg-shade-white-hover outline-negative-dark-default text-negative-dark-default fill-negative-dark-default`, - text: `bg-shade-white-default hover:bg-shade-white-hover text-negative-dark-default fill-negative-dark-default`, - }, - }[intention][variant], - className, - ])} - // FIXME: Avoid unintended type error, maybe MUI's problem? - {...(rest as MuiButtonProps)} - > - {startIcon} - {children} - {endIcon} - {loading && ( -
- -
- )} - - ); -}; + const intention = color + ? ( + { + primary: 'primary', + secondary: 'secondary', + default: 'primary', + } as const + )[color] + : passedIntention; -export const Button = forwardRef((props: ButtonProps, ref: Ref) => ( - <_Button _ref={ref} {...props} /> -)) as (props: ButtonProps) => JSX.Element; + return ( + + component={component} + ref={ref} + disabled={disabled || loading} + aria-label={label || rest['aria-label'] || 'button'} + aria-busy={loading} + className={fsx([ + `rounded-1.5 focus-visible:shadow-focused relative flex h-fit w-fit shrink-0 flex-row items-center justify-center font-sans outline-none disabled:cursor-not-allowed [&_svg]:fill-inherit`, + { + text: { + large: `px-4 py-2 gap-1`, + medium: `px-4 py-1 gap-1`, + small: `px-2 py-0.5 gap-0.5`, + }, + icon: { + large: `p-2 gap-1 [&_svg]:w-6 [&_svg]:h-6`, + medium: `p-1 gap-1 [&_svg]:w-6 [&_svg]:h-6`, + small: `p-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, + }, + 'text-icon': { + large: `pl-4 pr-3 py-2 gap-1 [&_svg]:w-4 [&_svg]:h-4`, + medium: `pl-4 pr-2 py-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, + small: `pl-2 pr-1 py-0.5 gap-0.5 [&_svg]:w-3 [&_svg]:h-3`, + }, + 'icon-text': { + large: `pl-3 pr-4 py-2 gap-1 [&_svg]:w-4 [&_svg]:h-4`, + medium: `pl-2 pr-4 py-1 gap-1 [&_svg]:w-4 [&_svg]:h-4`, + small: `pl-1 pr-2 py-0.5 gap-0.5 [&_svg]:w-3 [&_svg]:h-3`, + }, + }[structure][size], + { + large: `text-r`, + medium: `text-r`, + small: `text-s`, + }[size], + { + filled: `font-bold disabled:bg-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, + outlined: `font-regular outline outline-1 -outline-offset-1 disabled:bg-shade-dark-disabled disabled:outline-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, + text: `font-bold disabled:bg-shade-dark-disabled disabled:text-shade-white-disabled disabled:fill-shade-dark-disabled`, + }[variant], + { + subtle: { + filled: [ + `bg-shade-light-default hover:bg-shade-light-hover focus-visible:bg-shade-light-hover text-shade-medium-default fill-shade-medium-default`, + structure === 'icon' && `fill-shade-dark-default`, + ], + outlined: [ + `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-shade-medium-default text-shade-dark-default fill-shade-medium-default`, + structure === 'icon' && `fill-shade-dark-default`, + ], + text: [ + `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-shade-medium-default fill-shade-medium-default`, + structure === 'icon' && `fill-shade-dark-default`, + ], + }, + primary: { + filled: `bg-primary-dark-default hover:bg-primary-dark-hover focus-visible:bg-primary-dark-hover text-shade-white-default fill-shade-white-default`, + outlined: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-primary-dark-default text-primary-dark-default fill-primary-dark-default`, + text: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-primary-dark-default fill-primary-dark-default`, + }, + secondary: { + filled: `bg-secondary-dark-default hover:bg-secondary-dark-hover focus-visible:bg-secondary-dark-hover text-shade-white-default fill-shade-white-default`, + outlined: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover outline-secondary-dark-default text-secondary-dark-default fill-secondary-dark-default`, + text: `bg-shade-white-default hover:bg-shade-white-hover focus-visible:bg-shade-white-hover text-secondary-dark-default fill-secondary-dark-default`, + }, + shade: { + filled: `bg-shade-dark-default hover:bg-shade-dark-hover text-shade-white-default fill-shade-white-default`, + outlined: `bg-shade-white-default hover:bg-shade-white-hover outline-shade-dark-default text-shade-dark-default fill-shade-dark-default`, + text: `bg-shade-white-default hover:bg-shade-white-hover text-shade-dark-default fill-shade-dark-default`, + }, + negative: { + filled: `bg-negative-dark-default hover:bg-negative-dark-hover text-shade-white-default fill-shade-white-default`, + outlined: `bg-shade-white-default hover:bg-shade-white-hover outline-negative-dark-default text-negative-dark-default fill-negative-dark-default`, + text: `bg-shade-white-default hover:bg-shade-white-hover text-negative-dark-default fill-negative-dark-default`, + }, + }[intention][variant], + className, + ])} + // FIXME: Avoid unintended type error, maybe MUI's problem? + {...(rest as MuiButtonProps)} + > + {startIcon} + {children} + {endIcon} + {loading && ( +
+ +
+ )} + + ); + }, +); diff --git a/packages/for-ui/src/chip/Chip.tsx b/packages/for-ui/src/chip/Chip.tsx index cbfb5ac7..4f80065b 100644 --- a/packages/for-ui/src/chip/Chip.tsx +++ b/packages/for-ui/src/chip/Chip.tsx @@ -1,9 +1,9 @@ import { ElementType, forwardRef, MouseEvent, ReactNode } from 'react'; -import { ComponentProps, ElementTypeToHTMLElement, Ref } from '../system/polyComponent'; +import { ComponentPropsWithAs, Element, ElementTypeToHTMLElement, Ref } from '../system/componentType'; import { FullChip } from './FullChip'; import { LimitedChip } from './LimitedChip'; -export type ChipProps = ComponentProps< +export type ChipProps = ComponentPropsWithAs< { /** * ユーザーに提示したい意図 (e.g. エラーならばnegative) を指定 @@ -46,7 +46,7 @@ export type ChipProps = ComponentProps< As >; -type ChipComponent = (props: ChipProps) => ReactNode; +type ChipComponent = (props: ChipProps) => Element; export const Chip: ChipComponent = forwardRef( ({ clickableArea = 'full', ...props }: ChipProps, ref?: Ref) => diff --git a/packages/for-ui/src/chip/FullChip.tsx b/packages/for-ui/src/chip/FullChip.tsx index e84e2bc2..0fea7c63 100644 --- a/packages/for-ui/src/chip/FullChip.tsx +++ b/packages/for-ui/src/chip/FullChip.tsx @@ -1,15 +1,15 @@ -import { ElementType, forwardRef, ReactNode } from 'react'; +import { ElementType, forwardRef } from 'react'; +import { Element, Ref } from '../system/componentType'; import { fsx } from '../system/fsx'; -import { Ref } from '../system/polyComponent'; import { Text } from '../text'; import { ChipProps } from './Chip'; type FullChipProps = Omit, 'clickableArea'>; -type FullChipComponent = (props: FullChipProps) => ReactNode; +type FullChipComponent = (props: FullChipProps) => Element; export const FullChip: FullChipComponent = forwardRef( - (props: FullChipProps, ref: Ref): JSX.Element => { + (props: FullChipProps, ref: Ref) => { const { as, label, icon, intention = 'shade', className, ...rest } = props; const Component: ElementType = as || 'button'; return ( diff --git a/packages/for-ui/src/chip/LimitedChip.tsx b/packages/for-ui/src/chip/LimitedChip.tsx index a3bf3f75..a90fc8b9 100644 --- a/packages/for-ui/src/chip/LimitedChip.tsx +++ b/packages/for-ui/src/chip/LimitedChip.tsx @@ -1,16 +1,16 @@ -import { ElementType, forwardRef, ReactNode } from 'react'; +import { ElementType, forwardRef } from 'react'; import { MdClose } from 'react-icons/md'; +import { Element, Ref } from '../system/componentType'; import { fsx } from '../system/fsx'; -import { Ref } from '../system/polyComponent'; import { Text } from '../text'; import { ChipProps } from './Chip'; type LimitedChipProps = Omit, 'clickableArea'>; -type LimitedChipComponent = (props: LimitedChipProps) => ReactNode; +type LimitedChipComponent = (props: LimitedChipProps) => Element; export const LimitedChip: LimitedChipComponent = forwardRef( - (props: LimitedChipProps, ref: Ref): JSX.Element => { + (props: LimitedChipProps, ref: Ref) => { const { as, label, icon = , intention = 'shade', onClick, className, ...rest } = props; const Component: ElementType = as || 'button'; diff --git a/packages/for-ui/src/table/TableCell.tsx b/packages/for-ui/src/table/TableCell.tsx index 34879ba1..c8689bf8 100644 --- a/packages/for-ui/src/table/TableCell.tsx +++ b/packages/for-ui/src/table/TableCell.tsx @@ -1,10 +1,10 @@ import { FC, forwardRef, HTMLAttributes, ReactNode } from 'react'; import { MdArrowDownward, MdArrowUpward } from 'react-icons/md'; +import { ComponentPropsWithAs, Element, Ref } from '../system/componentType'; import { fsx } from '../system/fsx'; -import { ComponentProps, Ref } from '../system/polyComponent'; import { Text } from '../text'; -export type TableCellProps = ComponentProps< +export type TableCellProps = ComponentPropsWithAs< { /** * @deprecated `as` propを使ってください @@ -16,7 +16,7 @@ export type TableCellProps = ComponentProps< As >; -type TableCellComponent = (props: TableCellProps) => ReactNode; +type TableCellComponent = (props: TableCellProps) => Element; export const TableCell: TableCellComponent = forwardRef( ({ component = 'td', as, className, ...rest }: TableCellProps, ref?: Ref) => { From 5784232a1519ccc978a1a617dc94b14a5d6444c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=B5=E3=81=BF?= Date: Thu, 23 Nov 2023 16:58:37 +0900 Subject: [PATCH 3/3] Add change log --- .changeset/spotty-carpets-perform.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-carpets-perform.md diff --git a/.changeset/spotty-carpets-perform.md b/.changeset/spotty-carpets-perform.md new file mode 100644 index 00000000..556bb692 --- /dev/null +++ b/.changeset/spotty-carpets-perform.md @@ -0,0 +1,5 @@ +--- +"@4design/for-ui": patch +--- + +fix: ReactNodeを返していた部分をTypeScript 5.1<向けに修正