Skip to content
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
5 changes: 4 additions & 1 deletion packages/core/basic/Field/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function Field(props: Types.PrivateProps, ref: React.ForwardedRef<HTMLDivElement
labelType = 'outside',
label,
clearable = false,
notFocusable,
} = props

const {
Expand All @@ -28,7 +29,9 @@ function Field(props: Types.PrivateProps, ref: React.ForwardedRef<HTMLDivElement
events: { onClear, onEsc, onEnter, ...events },
} = useSystem(name || 'Field', props, createClasses)

attributes.tabIndex = attributes.tabIndex || 0
if (!notFocusable) {
attributes.tabIndex = attributes.tabIndex || 0
}

const htmlId = React.useMemo(
() => `${'Field'}-${Math.random().toString(16).slice(2)}`,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/basic/Field/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ declare namespace FieldTypes {
interface PrivateProps extends Props {
name?: string
children?: React.ReactNode

/**
* Removes tabIndex=0
*/
notFocusable?: boolean
}

interface Props<
Expand Down
67 changes: 67 additions & 0 deletions packages/core/control/Button/PolymorphicButton.tsx
Original file line number Diff line number Diff line change
@@ -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<React.ElementType, 'button' | 'a'>

type AsProp<E extends PossibleElementType> = {
as?: E
}
type PropsToOmit<E extends PossibleElementType, P> = keyof (AsProp<E> & P)

type PolymorphicRef<E extends PossibleElementType> = React.ComponentPropsWithRef<E>['ref']

type Props<E extends PossibleElementType> = ButtonTypes.PolymorphicProps &
AsProp<E> &
Omit<React.ComponentPropsWithoutRef<E>, PropsToOmit<E, ButtonTypes.PolymorphicProps>>

const PolymorphicButtonComponent = <E extends PossibleElementType = 'button'>(
props: Props<E>,
ref: PolymorphicRef<E>,
) => {
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 && <div css={classes.child({ align: 'left' })}>{leftChild}</div>}
{children}
{rightChild && <div css={classes.child({ align: 'right' })}>{rightChild} </div>}
</>
),
ref,
...restProps,
})
}

export const PolymorphicButton = forwardRef(PolymorphicButtonComponent) as <
E extends PossibleElementType = 'button',
>(
props: Props<E> & { ref?: PolymorphicRef<E> },
) => React.ReactElement
5 changes: 4 additions & 1 deletion packages/core/control/Button/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { colorProp } from '@stage-ui/system'

import Types from './types'

const styles: Stage.CreateClasses<Types.Classes, Types.Props> = (theme, props) => {
const styles: Stage.CreateClasses<Types.Classes, Types.Props | Types.PolymorphicProps> = (
theme,
props,
) => {
const {
size = 'm',
decoration = 'filled',
Expand Down
16 changes: 16 additions & 0 deletions packages/core/control/Button/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions packages/core/control/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const TextField: ForwardRefRenderFunction<Types.Ref, Types.Props> = (props, ref)
leftChildNumber,
clearable = false,
onFocus,
isInputElementDirectFocusable,

defaultValue,
value,
Expand Down Expand Up @@ -144,7 +145,9 @@ const TextField: ForwardRefRenderFunction<Types.Ref, Types.Props> = (props, ref)
leftChild={leftChildNumber && multiline ? <LeftCountLine /> : props.leftChild}
onClear={onClear}
onFocus={(e) => {
inputRef.current?.focus()
if (!isInputElementDirectFocusable) {
inputRef.current?.focus()
}
onFocus?.(e)
}}
onEsc={() => {
Expand Down Expand Up @@ -178,11 +181,12 @@ const TextField: ForwardRefRenderFunction<Types.Ref, Types.Props> = (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<HTMLInputElement>) => {
if (leftCountLineRef.current) {
setLeftCountLineState({
Expand Down
7 changes: 6 additions & 1 deletion packages/core/control/TextField/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeEventHandler } from 'react'
import { ChangeEventHandler, FocusEventHandler } from 'react'

import FieldTypes from '../../basic/Field/types'

Expand Down Expand Up @@ -43,6 +43,11 @@ declare namespace TextFieldTypes {
* @default false
*/
leftChildNumber?: ((index: number) => React.ReactNode) | boolean

/**
* Removes tabIndex=-1 from <input> and removes tabIndex=0 from wrapper
*/
isInputElementDirectFocusable?: boolean
}

interface InputProps {
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
34 changes: 31 additions & 3 deletions packages/testbed/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand All @@ -14,7 +22,27 @@ const App: React.FC = () => {
})
}

return null
const ref = useRef<HTMLAnchorElement | null>(null)

return (
<>
<button> 1</button>
<TextField isInputElementDirectFocusable decoration="outline" />
<button>2</button>

<PolymorphicButton
as="a"
href="https://google.com"
target="_blank"
style={(t) => ({ textDecoration: 'none' })}
ref={ref}
>
we woke
</PolymorphicButton>

<input></input>
</>
)
}

ReactDOM.render(
Expand Down