@@ -6,7 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
66Please see LICENSE files in the repository root for full details.
77 */
88
9- import React , { ComponentProps , forwardRef , FunctionComponent , HTMLAttributes , InputHTMLAttributes , Ref } from "react" ;
9+ import React , {
10+ ComponentProps ,
11+ ComponentPropsWithoutRef ,
12+ forwardRef ,
13+ FunctionComponent ,
14+ ReactElement ,
15+ KeyboardEvent ,
16+ Ref ,
17+ } from "react" ;
1018import classnames from "classnames" ;
1119import { Tooltip } from "@vector-im/compound-web" ;
1220
@@ -38,20 +46,8 @@ export type AccessibleButtonKind =
3846 | "icon_primary"
3947 | "icon_primary_outline" ;
4048
41- /**
42- * This type construct allows us to specifically pass those props down to the element we’re creating that the element
43- * actually supports.
44- *
45- * e.g., if element is set to "a", we’ll support href and target, if it’s set to "input", we support type.
46- *
47- * To remain compatible with existing code, we’ll continue to support InputHTMLAttributes<Element>
48- */
49- type DynamicHtmlElementProps < T extends keyof JSX . IntrinsicElements > =
50- JSX . IntrinsicElements [ T ] extends HTMLAttributes < { } > ? DynamicElementProps < T > : DynamicElementProps < "div" > ;
51- type DynamicElementProps < T extends keyof JSX . IntrinsicElements > = Partial <
52- Omit < JSX . IntrinsicElements [ T ] , "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown" >
53- > &
54- Omit < InputHTMLAttributes < Element > , "onClick" > ;
49+ type ElementType = keyof HTMLElementTagNameMap ;
50+ const defaultElement = "div" ;
5551
5652type TooltipProps = ComponentProps < typeof Tooltip > ;
5753
@@ -60,7 +56,7 @@ type TooltipProps = ComponentProps<typeof Tooltip>;
6056 *
6157 * Extends props accepted by the underlying element specified using the `element` prop.
6258 */
63- type Props < T extends keyof JSX . IntrinsicElements > = DynamicHtmlElementProps < T > & {
59+ type Props < T extends ElementType = "div" > = {
6460 /**
6561 * The base element type. "div" by default.
6662 */
@@ -105,14 +101,12 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
105101 disableTooltip ?: TooltipProps [ "disabled" ] ;
106102} ;
107103
108- export type ButtonProps < T extends keyof JSX . IntrinsicElements > = Props < T > ;
104+ export type ButtonProps < T extends ElementType > = Props < T > & Omit < ComponentPropsWithoutRef < T > , keyof Props < T > > ;
109105
110106/**
111107 * Type of the props passed to the element that is rendered by AccessibleButton.
112108 */
113- interface RenderedElementProps extends React . InputHTMLAttributes < Element > {
114- ref ?: React . Ref < Element > ;
115- }
109+ type RenderedElementProps < T extends ElementType > = React . InputHTMLAttributes < Element > & RefProp < T > ;
116110
117111/**
118112 * AccessibleButton is a generic wrapper for any element that should be treated
@@ -124,9 +118,9 @@ interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
124118 * @param {Object } props react element properties
125119 * @returns {Object } rendered react
126120 */
127- const AccessibleButton = forwardRef ( function < T extends keyof JSX . IntrinsicElements > (
121+ const AccessibleButton = forwardRef ( function < T extends ElementType = typeof defaultElement > (
128122 {
129- element = "div" as T ,
123+ element,
130124 onClick,
131125 children,
132126 kind,
@@ -141,10 +135,10 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
141135 onTooltipOpenChange,
142136 disableTooltip,
143137 ...restProps
144- } : Props < T > ,
145- ref : Ref < HTMLElement > ,
138+ } : ButtonProps < T > ,
139+ ref : Ref < HTMLElementTagNameMap [ T ] > ,
146140) : JSX . Element {
147- const newProps : RenderedElementProps = restProps ;
141+ const newProps = restProps as RenderedElementProps < T > ;
148142 newProps [ "aria-label" ] = newProps [ "aria-label" ] ?? title ;
149143 if ( disabled ) {
150144 newProps [ "aria-disabled" ] = true ;
@@ -162,7 +156,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
162156 // And divs which we report as role button to assistive technologies.
163157 // Browsers handle space and enter key presses differently and we are only adjusting to the
164158 // inconsistencies here
165- newProps . onKeyDown = ( e ) => {
159+ newProps . onKeyDown = ( e : KeyboardEvent < never > ) => {
166160 const action = getKeyBindingsManager ( ) . getAccessibilityAction ( e ) ;
167161
168162 switch ( action ) {
@@ -178,7 +172,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
178172 onKeyDown ?.( e ) ;
179173 }
180174 } ;
181- newProps . onKeyUp = ( e ) => {
175+ newProps . onKeyUp = ( e : KeyboardEvent < never > ) => {
182176 const action = getKeyBindingsManager ( ) . getAccessibilityAction ( e ) ;
183177
184178 switch ( action ) {
@@ -207,7 +201,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
207201 } ) ;
208202
209203 // React.createElement expects InputHTMLAttributes
210- const button = React . createElement ( element , newProps , children ) ;
204+ const button = React . createElement ( element ?? defaultElement , newProps , children ) ;
211205
212206 if ( title ) {
213207 return (
@@ -233,4 +227,15 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
233227} ;
234228( AccessibleButton as FunctionComponent ) . displayName = "AccessibleButton" ;
235229
236- export default AccessibleButton ;
230+ interface RefProp < T extends ElementType > {
231+ ref ?: Ref < HTMLElementTagNameMap [ T ] > ;
232+ }
233+
234+ interface ButtonComponent {
235+ // With the explicit `element` prop
236+ < C extends ElementType > ( props : { element ?: C } & ButtonProps < C > & RefProp < C > ) : ReactElement ;
237+ // Without the explicit `element` prop
238+ ( props : ButtonProps < "div" > & RefProp < "div" > ) : ReactElement ;
239+ }
240+
241+ export default AccessibleButton as ButtonComponent ;
0 commit comments