diff --git a/packages/core/basic/Field/index.tsx b/packages/core/basic/Field/index.tsx index b322ac1d7..d6c843449 100755 --- a/packages/core/basic/Field/index.tsx +++ b/packages/core/basic/Field/index.tsx @@ -19,6 +19,7 @@ function Field(props: Types.PrivateProps, ref: React.ForwardedRef `${'Field'}-${Math.random().toString(16).slice(2)}`, diff --git a/packages/core/basic/Field/types.ts b/packages/core/basic/Field/types.ts index 7a3a0a504..7cafba168 100755 --- a/packages/core/basic/Field/types.ts +++ b/packages/core/basic/Field/types.ts @@ -4,6 +4,11 @@ declare namespace FieldTypes { interface PrivateProps extends Props { name?: string children?: React.ReactNode + + /** + * Removes tabIndex=0 + */ + notFocusable?: boolean } interface Props< diff --git a/packages/core/control/Button/PolymorphicButton.tsx b/packages/core/control/Button/PolymorphicButton.tsx new file mode 100644 index 000000000..5bd4f6be3 --- /dev/null +++ b/packages/core/control/Button/PolymorphicButton.tsx @@ -0,0 +1,67 @@ +/* eslint-disable react/button-has-type */ +/* eslint-disable react/destructuring-assignment */ +import React, { + forwardRef, + ForwardRefRenderFunction, + HTMLProps, + ReactChild, + ReactNode, + Ref, + useState, +} from 'react' + +import { useSystem } from '@stage-ui/system' + +import Spinner from '../../content/Spinner' + +import createClasses from './styles' +import Types from './types' +import { jsx } from '@emotion/react' +import ButtonTypes from './types' + +type PossibleElementType = Extract + +type AsProp = { + as?: E +} +type PropsToOmit = keyof (AsProp & P) + +type PolymorphicRef = React.ComponentPropsWithRef['ref'] + +type Props = ButtonTypes.PolymorphicProps & + AsProp & + Omit, PropsToOmit> + +const PolymorphicButtonComponent = ( + props: Props, + ref: PolymorphicRef, +) => { + let { leftChild, rightChild, children, as = 'button', style, ...restProps } = props + const { classes, attributes, events, styleProps } = useSystem( + 'PolymorphicButton', + props, + createClasses, + ) + + return jsx(as, { + ...attributes, + ...events, + css: [classes.container, styleProps.all], + + children: ( + <> + {leftChild &&
{leftChild}
} + {children} + {rightChild &&
{rightChild}
} + + ), + ref, + ...restProps, + }) +} + +export const PolymorphicButton = forwardRef(PolymorphicButtonComponent) as < + E extends PossibleElementType = 'button', +>( + props: Props & { ref?: PolymorphicRef }, +) => React.ReactElement diff --git a/packages/core/control/Button/styles.ts b/packages/core/control/Button/styles.ts index 2971abcea..76fe1c83f 100755 --- a/packages/core/control/Button/styles.ts +++ b/packages/core/control/Button/styles.ts @@ -3,7 +3,10 @@ import { colorProp } from '@stage-ui/system' import Types from './types' -const styles: Stage.CreateClasses = (theme, props) => { +const styles: Stage.CreateClasses = ( + theme, + props, +) => { const { size = 'm', decoration = 'filled', diff --git a/packages/core/control/Button/types.ts b/packages/core/control/Button/types.ts index 293213ed6..d5bcd98ec 100755 --- a/packages/core/control/Button/types.ts +++ b/packages/core/control/Button/types.ts @@ -105,6 +105,22 @@ declare namespace ButtonTypes { loadingComponent?: React.ReactNode } + interface PolymorphicProps + extends Pick< + Props, + | 'leftChild' + | 'rightChild' + | 'type' + | 'size' + | 'shape' + | 'disabled' + | 'uppercase' + | 'decoration' + | 'color' + | 'children' + | 'style' + > {} + type Classes = { /** * Root element diff --git a/packages/core/control/TextField/index.tsx b/packages/core/control/TextField/index.tsx index a20f6a35f..3a75565e5 100755 --- a/packages/core/control/TextField/index.tsx +++ b/packages/core/control/TextField/index.tsx @@ -26,6 +26,7 @@ const TextField: ForwardRefRenderFunction = (props, ref) leftChildNumber, clearable = false, onFocus, + isInputElementDirectFocusable, defaultValue, value, @@ -144,7 +145,9 @@ const TextField: ForwardRefRenderFunction = (props, ref) leftChild={leftChildNumber && multiline ? : props.leftChild} onClear={onClear} onFocus={(e) => { - inputRef.current?.focus() + if (!isInputElementDirectFocusable) { + inputRef.current?.focus() + } onFocus?.(e) }} onEsc={() => { @@ -178,11 +181,12 @@ const TextField: ForwardRefRenderFunction = (props, ref) ], rightChild: [propOverridesClasses.rightChild, classes.rightChild], })} + notFocusable={isInputElementDirectFocusable} > {jsx(props.multiline ? 'textarea' : 'input', { ref: inputRef, css: classes.input({ size, multiline, disabled }), - tabIndex: -1, + ...(!isInputElementDirectFocusable && { tabIndex: -1 }), onChange: (e: React.ChangeEvent) => { if (leftCountLineRef.current) { setLeftCountLineState({ diff --git a/packages/core/control/TextField/types.ts b/packages/core/control/TextField/types.ts index 35843e8ec..3a34dbb61 100755 --- a/packages/core/control/TextField/types.ts +++ b/packages/core/control/TextField/types.ts @@ -1,4 +1,4 @@ -import { ChangeEventHandler } from 'react' +import { ChangeEventHandler, FocusEventHandler } from 'react' import FieldTypes from '../../basic/Field/types' @@ -43,6 +43,11 @@ declare namespace TextFieldTypes { * @default false */ leftChildNumber?: ((index: number) => React.ReactNode) | boolean + + /** + * Removes tabIndex=-1 from and removes tabIndex=0 from wrapper + */ + isInputElementDirectFocusable?: boolean } interface InputProps { diff --git a/packages/core/index.ts b/packages/core/index.ts index 0b2282e5e..f7b435257 100755 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -21,6 +21,7 @@ export { default as Toast } from './content/Toast' * Control */ export { default as Button } from './control/Button' +export { PolymorphicButton } from './control/Button/PolymorphicButton' export { default as Calendar } from './control/Calendar' export { default as Checkbox } from './control/Checkbox' export { default as DatePicker } from './control/DatePicker' diff --git a/packages/testbed/src/index.tsx b/packages/testbed/src/index.tsx index 14503769c..ebfa7d29d 100755 --- a/packages/testbed/src/index.tsx +++ b/packages/testbed/src/index.tsx @@ -1,7 +1,15 @@ import { light } from './theme' -import { DatePicker, Modal, modal, Viewport } from '@stage-ui/core' +import { + Button, + DatePicker, + Modal, + modal, + PolymorphicButton, + TextField, + Viewport, +} from '@stage-ui/core' import ReactDOM from 'react-dom' -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' const App: React.FC = () => { const handleOpen = () => { @@ -14,7 +22,27 @@ const App: React.FC = () => { }) } - return null + const ref = useRef(null) + + return ( + <> + + + + + ({ textDecoration: 'none' })} + ref={ref} + > + we woke + + + + + ) } ReactDOM.render(