From d79b6e788164e5049d3e0793b3ab84d14ce5ecee Mon Sep 17 00:00:00 2001 From: Klink <85062+dogmar@users.noreply.github.com> Date: Thu, 8 Jun 2023 08:27:50 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20Issue=20with=20typing=20of=20=E2=80=98si?= =?UTF-8?q?ze=E2=80=99=20props=20(#493)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/AppIcon.tsx | 98 +++++++++++++++++++----------------- src/components/Card.tsx | 8 +-- src/components/Chip.tsx | 88 ++++++++++++++++---------------- src/components/Modal.tsx | 48 ++++++++++-------- src/components/StackCard.tsx | 10 ++-- 6 files changed, 135 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index 688aa4ce..7485f1a9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "firebase:start": "yarn build:storybook && npx firebase emulators:start" }, "engines": { - "node": "18.12.1" + "node": "18.16.0" }, "dependencies": { "@floating-ui/react-dom-interactions": "0.13.3", diff --git a/src/components/AppIcon.tsx b/src/components/AppIcon.tsx index ba302208..e2bb3619 100644 --- a/src/components/AppIcon.tsx +++ b/src/components/AppIcon.tsx @@ -3,19 +3,31 @@ import PropTypes from 'prop-types' import { type ReactNode, type Ref, forwardRef } from 'react' import { last } from 'lodash-es' -import { styledTheme as theme } from '../theme' +import { type DefaultTheme, useTheme } from 'styled-components' -import { type CSSObject } from '../types' +import { type styledTheme as theme } from '../theme' import { type FillLevel, useFillLevel } from './contexts/FillLevelContext' -type Hue = 'default' | 'lighter' | 'lightest' -type Size = 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' - -type AppIconProps = DivProps & { - size?: Size - spacing?: 'none' | 'padding' | string - hue?: 'default' | 'lighter' | 'lightest' +const HUES = ['default', 'lighter', 'lightest'] as const +const SIZES = [ + 'xxsmall', + 'xsmall', + 'small', + 'medium', + 'large', + 'xlarge', +] as const +const SPACINGS = ['none', 'padding'] as const + +type AppIconHue = (typeof HUES)[number] +type AppIconSize = (typeof SIZES)[number] +type AppIconSpacing = (typeof SPACINGS)[number] + +type AppIconProps = Omit & { + size?: AppIconSize + spacing?: AppIconSpacing + hue?: AppIconHue clickable?: boolean url?: string icon?: ReactNode @@ -23,75 +35,68 @@ type AppIconProps = DivProps & { name?: string initials?: string onClose?: () => void + [x: string]: unknown } const propTypes = { - size: PropTypes.oneOf([ - 'xxsmall', - 'xsmall', - 'small', - 'medium', - 'large', - 'xlarge', - ]), - spacing: PropTypes.oneOf(['none', 'padding']), - hue: PropTypes.oneOf(['default', 'lighter', 'lightest']), + size: PropTypes.oneOf(SIZES), + spacing: PropTypes.oneOf(SPACINGS), + hue: PropTypes.oneOf(HUES), clickable: PropTypes.bool, url: PropTypes.string, IconComponent: PropTypes.elementType, alt: PropTypes.string, -} +} as const -const parentFillLevelToHue: Record = { +const parentFillLevelToHue = { 0: 'default', 1: 'lighter', 2: 'lightest', 3: 'lightest', -} +} as const satisfies Record -const sizeToWidth: Record = { +const sizeToWidth = { xxsmall: 32, xsmall: 48, small: 64, medium: 96, large: 140, xlarge: 160, -} +} as const satisfies Record -const sizeToIconWidth: Record = { +const sizeToIconWidth = { xxsmall: 16, xsmall: 32, small: 48, medium: 64, large: 76, xlarge: 96, -} +} as const satisfies Record -const hueToColor: Record = { +const hueToColor = { default: 'fill-one', lighter: 'fill-two', lightest: 'fill-three', -} +} as const satisfies Record -const hueToBorderColor: { - [key in 'default' | 'lighter' | 'lightest']: string -} = { +const hueToBorderColor = { default: 'border', lighter: 'border-fill-two', lightest: 'border-fill-three', -} - -const sizeToFont: Record = { - xxsmall: { - ...theme.partials.text.body2Bold, - fontSize: 12, - }, - xsmall: theme.partials.text.body2Bold, - small: theme.partials.text.subtitle2, - medium: theme.partials.text.subtitle1, - large: theme.partials.text.title2, - xlarge: theme.partials.text.title2, -} +} as const satisfies Record + +const sizeToFont = (size: AppIconSize, theme: DefaultTheme) => + ({ + xxsmall: { + ...theme.partials.text.body2Bold, + fontSize: 12, + }, + xsmall: theme.partials.text.body2Bold, + small: theme.partials.text.subtitle2, + medium: theme.partials.text.subtitle1, + large: theme.partials.text.title2, + xlarge: theme.partials.text.title2, + }[size]) export function toInitials(name: string) { let initials = name @@ -131,6 +136,7 @@ function AppIconRef( const color = hueToColor[hue] const borderColor = hueToBorderColor[hue] const hasBorder = spacing === 'padding' + const theme = useTheme() return ( {initials || (name ? toInitials(name) : '')} @@ -180,7 +186,7 @@ function AppIconRef( const AppIcon = forwardRef(AppIconRef) -AppIcon.propTypes = propTypes as any +AppIcon.propTypes = propTypes export default AppIcon export type { AppIconProps } diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 20c28394..e269cc15 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -14,13 +14,15 @@ import { type CornerSize = 'medium' | 'large' type CardHue = 'default' | 'lighter' | 'lightest' -type CardProps = { +type BaseCardProps = { hue?: CardHue // Deprecated, prefer fillLevel fillLevel?: FillLevel cornerSize?: CornerSize clickable?: boolean selected?: boolean -} & DivProps +} + +type CardProps = DivProps & BaseCardProps const fillLevelToBGColor: Record = { 0: 'fill-one', @@ -126,4 +128,4 @@ const Card = forwardRef( ) export default Card -export type { CardProps, CornerSize as CardSize, CardHue } +export type { BaseCardProps, CardProps, CornerSize as CardSize, CardHue } diff --git a/src/components/Chip.tsx b/src/components/Chip.tsx index 61fd039e..662e303e 100644 --- a/src/components/Chip.tsx +++ b/src/components/Chip.tsx @@ -1,61 +1,76 @@ import { Flex, type FlexProps, Spinner } from 'honorable' import PropTypes from 'prop-types' import { type ReactElement, type Ref, forwardRef } from 'react' -import styled from 'styled-components' +import styled, { type DefaultTheme } from 'styled-components' -import Card, { type CardProps } from './Card' +import Card, { type BaseCardProps } from './Card' import { type FillLevel, useFillLevel } from './contexts/FillLevelContext' import CloseIcon from './icons/CloseIcon' -type Hue = 'default' | 'lighter' | 'lightest' -type Size = 'small' | 'medium' | 'large' -type Severity = - | 'neutral' - | 'info' - | 'success' - | 'warning' - | 'error' - | 'critical' - -export type ChipProps = FlexProps & { - size?: 'small' | 'medium' | 'large' - severity?: Severity - icon?: ReactElement - loading?: boolean - closeButton?: boolean - clickable?: boolean -} & CardProps - -const parentFillLevelToHue: Record = { +const HUES = ['default', 'lighter', 'lightest'] as const +const SIZES = ['small', 'medium', 'large'] as const +const SEVERITIES = [ + 'neutral', + 'info', + 'success', + 'warning', + 'error', + 'critical', +] as const + +type ChipHue = (typeof HUES)[number] +type ChipSize = (typeof SIZES)[number] +type ChipSeverity = (typeof SEVERITIES)[number] + +export type ChipProps = Omit & + BaseCardProps & { + size?: ChipSize + severity?: ChipSeverity + icon?: ReactElement + loading?: boolean + closeButton?: boolean + clickable?: boolean + [x: string]: unknown + } + +const propTypes = { + size: PropTypes.oneOf(SIZES), + severity: PropTypes.oneOf(SEVERITIES), + hue: PropTypes.oneOf(HUES), + icon: PropTypes.element, + loading: PropTypes.bool, +} as const + +const parentFillLevelToHue = { 0: 'default', 1: 'lighter', 2: 'lightest', 3: 'lightest', -} +} as const satisfies Record -const severityToColor: Record = { +const severityToColor = { neutral: 'text', info: 'text-primary-accent', success: 'text-success-light', warning: 'text-warning-light', error: 'text-danger-light', critical: 'text-danger', -} +} as const satisfies Record -const severityToIconColor: Record = { +const severityToIconColor = { neutral: 'icon-default', info: 'icon-info', success: 'icon-success', warning: 'icon-warning', error: 'icon-danger', critical: 'icon-danger-critical', -} +} as const satisfies Record -const sizeToCloseHeight: Record = { +const sizeToCloseHeight = { small: 8, medium: 10, large: 12, -} +} as const satisfies Record const ChipCard = styled(Card)(({ theme }) => ({ '.closeIcon': { @@ -138,19 +153,6 @@ function ChipRef( const Chip = forwardRef(ChipRef) -Chip.propTypes = { - size: PropTypes.oneOf(['small', 'medium', 'large']), - severity: PropTypes.oneOf([ - 'neutral', - 'info', - 'success', - 'warning', - 'error', - 'critical', - ]), - hue: PropTypes.oneOf(['default', 'lighter', 'lightest']), - icon: PropTypes.element, - loading: PropTypes.bool, -} +Chip.propTypes = propTypes export default Chip diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 1812eea1..fdc422fd 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -19,50 +19,54 @@ import WarningIcon from './icons/WarningIcon' import InfoIcon from './icons/InfoIcon' export const SEVERITIES = ['info', 'warning', 'success', 'danger'] as const +const SIZES = ['medium', 'large'] as const type ModalSeverity = Extract -type ModalPropsType = ModalProps & { +type ModalSize = (typeof SIZES)[number] + +type ModalPropsType = Omit & { form?: boolean - size?: 'medium' | 'large' + size?: ModalSize header?: ReactNode actions?: ReactNode severity?: ModalSeverity lockBody?: boolean + [x: string]: unknown } -const severityToIconColorKey: Readonly< - Record -> = { +const propTypes = { + form: PropTypes.bool, + size: PropTypes.oneOf(SIZES), + header: PropTypes.node, + actions: PropTypes.node, + severity: PropTypes.oneOf(SEVERITIES), + lockBody: PropTypes.bool, +} as const + +const severityToIconColorKey = { default: 'icon-default', info: 'icon-info', danger: 'icon-danger', warning: 'icon-warning', success: 'icon-success', -} +} as const satisfies Readonly> -const severityToIcon: Record< - ModalSeverity | 'default', - ReturnType | null | undefined -> = { - default: null, +const severityToIcon = { + default: null as null, info: InfoIcon, danger: ErrorIcon, warning: WarningIcon, success: CheckRoundedIcon, -} - -const propTypes = { - form: PropTypes.bool, - size: PropTypes.oneOf(['medium', 'large']), - header: PropTypes.node, - actions: PropTypes.node, -} +} as const satisfies Record< + ModalSeverity | 'default', + ReturnType | null | undefined +> -const sizeToWidth: { [key in 'medium' | 'large']: number } = { +const sizeToWidth = { medium: 480, large: 608, -} +} as const satisfies Record function ModalRef( { @@ -154,6 +158,6 @@ function ModalRef( const Modal = forwardRef(ModalRef) -Modal.propTypes = propTypes as any +Modal.propTypes = propTypes export default Modal diff --git a/src/components/StackCard.tsx b/src/components/StackCard.tsx index f3f93a8e..8159a7fd 100644 --- a/src/components/StackCard.tsx +++ b/src/components/StackCard.tsx @@ -8,11 +8,13 @@ import Tooltip from './Tooltip' import Chip from './Chip' import StackIcon from './icons/StackIcon' +const HUES = ['neutral', 'red', 'green', 'blue', 'yellow'] as const + type StackCardProps = DivProps & { title?: string description?: string apps?: App[] - hue?: 'neutral' | 'red' | 'green' | 'blue' | 'yellow' + hue?: (typeof HUES)[number] } type App = { @@ -37,8 +39,8 @@ const propTypes = { imageUrl: PropTypes.string.isRequired, }).isRequired ).isRequired, - hue: PropTypes.oneOf(['neutral', 'red', 'green', 'blue', 'yellow']), -} + hue: PropTypes.oneOf(HUES), +} as const function StackCardRef( { title, description, apps = [], hue = 'neutral', ...props }: StackCardProps, @@ -138,6 +140,6 @@ function StackCardRef( const StackCard = forwardRef(StackCardRef) -StackCard.propTypes = propTypes as any +StackCard.propTypes = propTypes export default StackCard