From 48b79195ce06b805086cd8df9d8be4254ff3fe76 Mon Sep 17 00:00:00 2001 From: Yash raj chhabra <95337653+ychhabra-eightfold@users.noreply.github.com> Date: Thu, 5 May 2022 17:02:10 +0530 Subject: [PATCH] feat: abstract out common component props as oc base props, add ability to forward ref (#106) * feat: extract out common component props as oc base props * feat: extend HTMLAttributes * feat: use OcBaseProps in button * feat: use base prop in all components * chore: update snapshots --- src/components/Badge/Badge.tsx | 3 +- src/components/Badge/Badge.types.tsx | 11 +- src/components/Button/Button.types.ts | 3 +- .../Button/PrimaryButton/PrimaryButton.tsx | 2 + .../SecondaryButton/SecondaryButton.tsx | 2 + .../Dialog/BaseDialog/BaseDialog.tsx | 154 ++++++++++-------- .../Dialog/BaseDialog/BaseDialog.types.ts | 10 +- src/components/Dialog/Dialog.tsx | 110 +++++++------ src/components/InfoBar/InfoBar.tsx | 118 ++++++++------ src/components/InfoBar/InfoBar.types.ts | 12 +- src/components/Inputs/Input.types.ts | 22 ++- src/components/Inputs/SearchBox/SearchBox.tsx | 2 + src/components/Inputs/TextArea/TextArea.tsx | 4 +- src/components/Inputs/TextInput/TextInput.tsx | 4 +- src/components/Label/Label.tsx | 3 +- src/components/Label/Label.types.ts | 3 +- src/components/List/List.tsx | 3 +- src/components/List/List.types.ts | 11 +- src/components/MatchScore/MatchScore.tsx | 81 +++++---- src/components/MatchScore/MatchScore.types.ts | 7 +- src/components/Menu/Menu.tsx | 4 +- src/components/Modal/Modal.tsx | 82 +++++----- src/components/OcBase/Base.types.ts | 12 ++ src/components/OcBase/index.ts | 1 + src/components/Panel/Panel.tsx | 2 + src/components/Panel/Panel.types.ts | 3 +- src/components/Pills/Pill.tsx | 144 ++++++++-------- src/components/Pills/Pills.types.ts | 3 +- src/components/Snackbar/Snackbar.test.tsx | 1 - src/components/Snackbar/Snackbar.types.ts | 2 +- src/components/Snackbar/SnackbarContainer.tsx | 2 +- src/components/Tabs/Tab/Tab.tsx | 104 ++++++------ src/components/Tabs/Tabs.types.ts | 18 +- src/components/Tabs/Tabs/AnimatedTabs.tsx | 61 ++++--- src/components/Tabs/Tabs/index.tsx | 22 ++- src/components/Tooltip/Tooltip.tsx | 2 + src/components/Tooltip/Tooltip.types.ts | 11 +- 37 files changed, 556 insertions(+), 483 deletions(-) create mode 100644 src/components/OcBase/Base.types.ts create mode 100644 src/components/OcBase/index.ts diff --git a/src/components/Badge/Badge.tsx b/src/components/Badge/Badge.tsx index 800bcd184..72894ca44 100644 --- a/src/components/Badge/Badge.tsx +++ b/src/components/Badge/Badge.tsx @@ -10,6 +10,7 @@ export const Badge: FC = ({ style, children, disruptive, + ...rest }) => { const badgeClasses: string = mergeClasses([ styles.badge, @@ -19,7 +20,7 @@ export const Badge: FC = ({ { [styles.active]: active }, ]); return ( - + {children} ); diff --git a/src/components/Badge/Badge.types.tsx b/src/components/Badge/Badge.types.tsx index 3ada2087c..8621015f6 100644 --- a/src/components/Badge/Badge.types.tsx +++ b/src/components/Badge/Badge.types.tsx @@ -1,6 +1,7 @@ import React from 'react'; +import { OcBaseProps } from '../OcBase'; -export interface BadgeProps { +export interface BadgeProps extends OcBaseProps { /** * Badge is in an active state or not */ @@ -9,12 +10,4 @@ export interface BadgeProps { * If badge is disruptive or not */ disruptive?: boolean; - /** - * Custom badge classNames - */ - classNames?: string; - /** - * Custom badge style - */ - style?: React.CSSProperties; } diff --git a/src/components/Button/Button.types.ts b/src/components/Button/Button.types.ts index 9f48a06c6..ccb70e69c 100644 --- a/src/components/Button/Button.types.ts +++ b/src/components/Button/Button.types.ts @@ -1,5 +1,6 @@ import React, { Ref } from 'react'; import { IconProps } from '../Icon'; +import { OcBaseProps } from '../OcBase'; export enum ButtonIconAlign { Left = 'left', @@ -52,7 +53,7 @@ export interface InternalButtonProps extends ButtonProps { ref?: Ref; } -export type NativeButtonProps = Omit, 'type'>; +export type NativeButtonProps = Omit, 'type'>; export interface SplitButtonProps extends Omit< diff --git a/src/components/Button/PrimaryButton/PrimaryButton.tsx b/src/components/Button/PrimaryButton/PrimaryButton.tsx index e0d4b443d..91dd41523 100644 --- a/src/components/Button/PrimaryButton/PrimaryButton.tsx +++ b/src/components/Button/PrimaryButton/PrimaryButton.tsx @@ -38,6 +38,7 @@ export const PrimaryButton: FC = React.forwardRef( theme, toggle, buttonWidth, + ...rest }, ref: Ref ) => { @@ -50,6 +51,7 @@ export const PrimaryButton: FC = React.forwardRef( return ( = React.forwardRef( style, toggle, buttonWidth, + ...rest }, ref: Ref ) => { @@ -49,6 +50,7 @@ export const SecondaryButton: FC = React.forwardRef( return ( = ({ - parent = document.body, - visible, - onClose, - maskClosable = true, - onVisibleChange, - height, - width, - zIndex, - header, - headerClassNames, - body, - bodyClassNames, - actions, - actionsClassNames, - dialogWrapperClassNames, - dialogClassNames, -}) => { - const labelId = uniqueId('dialog-label-'); +export const BaseDialog: FC = React.forwardRef( + ( + { + parent = document.body, + visible, + onClose, + maskClosable = true, + onVisibleChange, + height, + width, + zIndex, + header, + headerClassNames, + body, + bodyClassNames, + actions, + actionsClassNames, + dialogWrapperClassNames, + dialogClassNames, + ...rest + }, + ref: Ref + ) => { + const labelId = uniqueId('dialog-label-'); - const { lockScroll, unlockScroll } = useScrollLock(parent); + const { lockScroll, unlockScroll } = useScrollLock(parent); - const dialogBackdropClasses: string = mergeClasses([ - styles.dialogBackdrop, - dialogWrapperClassNames, - { [styles.visible]: visible }, - ]); + const dialogBackdropClasses: string = mergeClasses([ + styles.dialogBackdrop, + dialogWrapperClassNames, + { [styles.visible]: visible }, + ]); - const dialogClasses: string = mergeClasses([ - styles.dialog, - dialogClassNames, - ]); + const dialogClasses: string = mergeClasses([ + styles.dialog, + dialogClassNames, + ]); - const headerClasses: string = mergeClasses([ - styles.header, - headerClassNames, - ]); + const headerClasses: string = mergeClasses([ + styles.header, + headerClassNames, + ]); - const dialogStyle: React.CSSProperties = { - zIndex, - height, - width, - }; + const dialogStyle: React.CSSProperties = { + zIndex, + height, + width, + }; - useEffect(() => { - onVisibleChange?.(visible); - if (visible) { - lockScroll(); - } else { - unlockScroll(); - } - }, [visible]); + useEffect(() => { + onVisibleChange?.(visible); + if (visible) { + lockScroll(); + } else { + unlockScroll(); + } + }, [visible]); - const getDialog = (): JSX.Element => ( -
) => { - maskClosable && onClose?.(e); - }} - > + const getDialog = (): JSX.Element => (
) => { + maskClosable && onClose?.(e); + }} > -
- {header} - +
+
+ {header} + +
+
{body}
+ {actions && ( +
{actions}
+ )}
-
{body}
- {actions &&
{actions}
}
-
- ); - return parent}>{getDialog()}; -}; + ); + return parent}>{getDialog()}; + } +); diff --git a/src/components/Dialog/BaseDialog/BaseDialog.types.ts b/src/components/Dialog/BaseDialog/BaseDialog.types.ts index 3ec9eea8a..9372a939e 100644 --- a/src/components/Dialog/BaseDialog/BaseDialog.types.ts +++ b/src/components/Dialog/BaseDialog/BaseDialog.types.ts @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { Ref } from 'react'; +import { OcBaseProps } from '../../OcBase'; type EventType = | React.KeyboardEvent | React.MouseEvent; -export interface BaseDialogProps { +export interface BaseDialogProps + extends Omit, 'classNames'> { /** * Dialog is visible or not */ @@ -73,4 +75,8 @@ export interface BaseDialogProps { * @default HTMLBodyElement */ parent?: HTMLDivElement | HTMLBodyElement; + /** + * Ref for the dialog element + */ + ref?: Ref; } diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index a2cce9bb0..570fcf0eb 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, Ref } from 'react'; import { DialogProps, DialogSize } from './Dialog.types'; import { mergeClasses } from '../../shared/utilities'; import { DefaultButton, PrimaryButton } from '../Button'; @@ -6,58 +6,64 @@ import { BaseDialog } from './BaseDialog/BaseDialog'; import styles from './dialog.module.scss'; -export const Dialog: FC = ({ - parent = document.body, - size = DialogSize.medium, - headerClassNames, - bodyClassNames, - actionsClassNames, - dialogClassNames, - okButtonProps, - cancelButtonProps, - onOk, - onCancel, - ...rest -}) => { - const dialogClasses: string = mergeClasses([ - styles.dialog, - dialogClassNames, - { [styles.small]: size === DialogSize.small }, - { [styles.medium]: size === DialogSize.medium }, - ]); +export const Dialog: FC = React.forwardRef( + ( + { + parent = document.body, + size = DialogSize.medium, + headerClassNames, + bodyClassNames, + actionsClassNames, + dialogClassNames, + okButtonProps, + cancelButtonProps, + onOk, + onCancel, + ...rest + }, + ref: Ref + ) => { + const dialogClasses: string = mergeClasses([ + styles.dialog, + dialogClassNames, + { [styles.small]: size === DialogSize.small }, + { [styles.medium]: size === DialogSize.medium }, + ]); - const headerClasses: string = mergeClasses([ - styles.header, - headerClassNames, - ]); + const headerClasses: string = mergeClasses([ + styles.header, + headerClassNames, + ]); - const bodyClasses: string = mergeClasses([styles.body, bodyClassNames]); + const bodyClasses: string = mergeClasses([styles.body, bodyClassNames]); - const actionClasses: string = mergeClasses([ - styles.actions, - actionsClassNames, - ]); + const actionClasses: string = mergeClasses([ + styles.actions, + actionsClassNames, + ]); - return ( - - {cancelButtonProps && ( - - )} - {okButtonProps && ( - - )} - - } - /> - ); -}; + return ( + + {cancelButtonProps && ( + + )} + {okButtonProps && ( + + )} + + } + /> + ); + } +); diff --git a/src/components/InfoBar/InfoBar.tsx b/src/components/InfoBar/InfoBar.tsx index 02d635b3a..cb9e7501d 100644 --- a/src/components/InfoBar/InfoBar.tsx +++ b/src/components/InfoBar/InfoBar.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, Ref } from 'react'; import { InfoBarsProps, InfoBarType } from './InfoBar.types'; import { Icon, IconName } from '../Icon'; import { mergeClasses } from '../../shared/utilities'; @@ -6,58 +6,70 @@ import { NeutralButton } from '../Button'; import styles from './infoBar.module.scss'; -export const InfoBar: FC = ({ - content, - icon, - type = InfoBarType.neutral, - closable, - onClose, - style, - classNames, - closeIcon = IconName.mdiClose, - closeButtonProps, - actionButtonProps, - role = 'presentation', -}) => { - const infoBarClasses: string = mergeClasses([ - styles.infoBar, - classNames, - { [styles.neutral]: type === InfoBarType.neutral }, - { [styles.positive]: type === InfoBarType.positive }, - { [styles.warning]: type === InfoBarType.warning }, - { [styles.disruptive]: type === InfoBarType.disruptive }, - ]); +export const InfoBar: FC = React.forwardRef( + ( + { + content, + icon, + type = InfoBarType.neutral, + closable, + onClose, + style, + classNames, + closeIcon = IconName.mdiClose, + closeButtonProps, + actionButtonProps, + role = 'presentation', + ...rest + }, + ref: Ref + ) => { + const infoBarClasses: string = mergeClasses([ + styles.infoBar, + classNames, + { [styles.neutral]: type === InfoBarType.neutral }, + { [styles.positive]: type === InfoBarType.positive }, + { [styles.warning]: type === InfoBarType.warning }, + { [styles.disruptive]: type === InfoBarType.disruptive }, + ]); - const messageClasses: string = mergeClasses([styles.message, 'body2']); + const messageClasses: string = mergeClasses([styles.message, 'body2']); - const getIconName = (): IconName => { - if (icon) { - return icon; - } - switch (type) { - case InfoBarType.disruptive: - case InfoBarType.neutral: - return IconName.mdiInformation; - case InfoBarType.positive: - return IconName.mdiCheckCircle; - case InfoBarType.warning: - return IconName.mdiAlert; - } - }; + const getIconName = (): IconName => { + if (icon) { + return icon; + } + switch (type) { + case InfoBarType.disruptive: + case InfoBarType.neutral: + return IconName.mdiInformation; + case InfoBarType.positive: + return IconName.mdiCheckCircle; + case InfoBarType.warning: + return IconName.mdiAlert; + } + }; - return ( -
- -
{content}
- {actionButtonProps && } - {closable && ( - - )} -
- ); -}; + return ( +
+ +
{content}
+ {actionButtonProps && } + {closable && ( + + )} +
+ ); + } +); diff --git a/src/components/InfoBar/InfoBar.types.ts b/src/components/InfoBar/InfoBar.types.ts index 6f9876461..6372f7b05 100644 --- a/src/components/InfoBar/InfoBar.types.ts +++ b/src/components/InfoBar/InfoBar.types.ts @@ -1,6 +1,6 @@ -import React from 'react'; import { IconName } from '../Icon'; import { ButtonProps } from '../Button'; +import { OcBaseProps } from '../OcBase'; export type CloseButtonProps = Omit; @@ -11,7 +11,7 @@ export enum InfoBarType { disruptive = 'disruptive', } -export interface InfoBarsProps { +export interface InfoBarsProps extends OcBaseProps { /** * Content of the info bar */ @@ -26,14 +26,6 @@ export interface InfoBarsProps { * @default IconName.mdiInformation | IconName.mdiCheckCircle | IconName.mdiAlert */ icon?: IconName; - /** - * Custom style for the infoBar - */ - style?: React.CSSProperties; - /** - * Custom classnames for the infoBar - */ - classNames?: string; /** * If the infoBar is closable or not */ diff --git a/src/components/Inputs/Input.types.ts b/src/components/Inputs/Input.types.ts index 3df8680be..db4c66eca 100644 --- a/src/components/Inputs/Input.types.ts +++ b/src/components/Inputs/Input.types.ts @@ -3,6 +3,7 @@ import { Placement, Strategy } from '@floating-ui/react-dom'; import { IconName, IconProps } from '../Icon'; import { LabelProps } from '../Label'; import { TooltipTheme } from '../Tooltip'; +import { OcBaseProps } from '../OcBase'; export enum TextInputTheme { light = 'light', @@ -127,14 +128,15 @@ export interface InputLabelIconButtonProps { toolTipPositionStrategy?: Strategy; } -export interface SearchBoxProps extends Omit { +export interface SearchBoxProps + extends Omit, 'htmlType'> { /** * The search box value. */ value?: string; } -export interface TextAreaProps extends InputProps { +export interface TextAreaProps extends InputProps { /** * The text area is expandable. * @default false @@ -157,7 +159,7 @@ export interface TextAreaProps extends InputProps { textAreaRows?: number; } -export interface TextInputProps extends InputProps { +export interface TextInputProps extends InputProps { /** * The input html type. * @default 'text' @@ -175,7 +177,11 @@ export interface TextInputProps extends InputProps { required?: boolean; } -export interface InputProps { +export interface InputProps + extends Omit< + OcBaseProps, + 'onChange' | 'onFocus' | 'onBlur' | 'onKeyDown' + > { /** * Allows focus on the input when it's disabled. * @default false @@ -190,10 +196,6 @@ export interface InputProps { * @default false */ autoFocus?: boolean; - /** - * The input class names. - */ - classNames?: string; /** * The input clear button aria label text. */ @@ -267,10 +269,6 @@ export interface InputProps { * @default TextInputShape.Rectangle */ shape?: TextInputShape; - /** - * Style of the input. - */ - style?: React.CSSProperties; /** * Theme of the input. * @default TextInputTheme.light diff --git a/src/components/Inputs/SearchBox/SearchBox.tsx b/src/components/Inputs/SearchBox/SearchBox.tsx index b12e18bb6..793092181 100644 --- a/src/components/Inputs/SearchBox/SearchBox.tsx +++ b/src/components/Inputs/SearchBox/SearchBox.tsx @@ -36,9 +36,11 @@ export const SearchBox: FC = ({ theme = TextInputTheme.light, value, waitInterval = 500, + ...rest }) => (
= ({ theme = TextInputTheme.light, value, waitInterval = 10, + ...rest }) => { const [textAreaId] = useState(uniqueId(id || 'textarea-')); @@ -66,6 +67,7 @@ export const TextArea: FC = ({
{labelProps &&