Browse Source

Update LinkButton types

Enable more type-safe approach to component and href props.
master
TheoryOfNekomata 9 months ago
parent
commit
20b6c6e401
1 changed files with 94 additions and 55 deletions
  1. +94
    -55
      categories/web/navigation/react/src/components/LinkButton/index.tsx

+ 94
- 55
categories/web/navigation/react/src/components/LinkButton/index.tsx View File

@@ -7,46 +7,39 @@ import { Button } from '@tesseract-design/web-base';
*/ */
export type LinkButtonDerivedElement = HTMLAnchorElement; export type LinkButtonDerivedElement = HTMLAnchorElement;


/**
* Props of the {@link LinkButton} component.
*/
export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'href' | 'size'> {
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean;
/**
* Variant of the component.
*/
variant?: Button.Variant;
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode;
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode;
/**
* Is this component part of a menu?
*/
menuItem?: boolean;
/**
* Size of the component.
*/
size?: Button.Size;
/**
* Should the component's content use minimal space?
*/
compact?: boolean;
/**
* Component to use in rendering.
*/
component?: React.ElementType<T>;
/**
* Is the component unable to receive activation?
*/
disabled?: boolean;
interface LinkButtonCommonProps extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'href' | 'size'> {
/**
* Should the component occupy the whole width of its parent?
*/
block?: boolean;
/**
* Variant of the component.
*/
variant?: Button.Variant;
/**
* Complementary content of the component.
*/
subtext?: React.ReactNode;
/**
* Short complementary content displayed at the edge of the component.
*/
badge?: React.ReactNode;
/**
* Is this component part of a menu?
*/
menuItem?: boolean;
/**
* Size of the component.
*/
size?: Button.Size;
/**
* Should the component's content use minimal space?
*/
compact?: boolean;
/**
* Is the component unable to receive activation?
*/
disabled?: boolean;
/** /**
* Graphical representation of the component. * Graphical representation of the component.
*/ */
@@ -55,12 +48,36 @@ export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButto
* Should the graphical representation of the component be placed after the children? * Should the graphical representation of the component be placed after the children?
*/ */
iconAfterChildren?: boolean; iconAfterChildren?: boolean;
/**
* URL to navigate to.
*/
href?: string | unknown;
} }


interface LinkButtonAnchorProps extends Pick<React.HTMLProps<LinkButtonDerivedElement>, 'href'> {
component: 'a';
}

interface LinkButtonComponentType {
(props: { href?: string }): React.ReactNode;
}

interface LinkButtonFCProps<
C extends LinkButtonComponentType = LinkButtonComponentType
> {
component: C;
href: C extends (props: { href?: infer Href }) => React.ReactNode ? Href : never;
}

type LinkButtonAllProps<
T extends LinkButtonComponentType = LinkButtonComponentType
> =
LinkButtonAnchorProps | LinkButtonFCProps<T>;

/**
* Props of the {@link LinkButton} component.
*/
export type LinkButtonProps<
T extends LinkButtonComponentType = LinkButtonComponentType
> =
LinkButtonCommonProps & LinkButtonAllProps<T>;

/** /**
* Component for performing a navigation action. * Component for performing a navigation action.
*/ */
@@ -75,7 +92,7 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
compact = false, compact = false,
className, className,
block = false, block = false,
component: EnabledComponent = 'a',
component: EnabledComponent = 'a' as const,
disabled = false, disabled = false,
href, href,
style, style,
@@ -85,14 +102,18 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
}, },
forwardedRef, forwardedRef,
) => { ) => {
const Component = disabled ? 'button' : EnabledComponent;
const Component = (disabled ? 'button' : EnabledComponent) as 'a';
const extraProps = {
disabled: disabled || undefined,
};

return ( return (
<Component <Component
{...etcProps} {...etcProps}
href={disabled ? undefined : href}
{...extraProps}
href={disabled ? undefined : href as string}
type={disabled ? 'button' : undefined} type={disabled ? 'button' : undefined}
ref={forwardedRef} ref={forwardedRef}
disabled={disabled || undefined}
className={clsx( className={clsx(
'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline m-0', 'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline m-0',
'focus:outline-0 focus:ring-4', 'focus:outline-0 focus:ring-4',
@@ -205,16 +226,34 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
LinkButton.displayName = 'LinkButton'; LinkButton.displayName = 'LinkButton';


LinkButton.defaultProps = { LinkButton.defaultProps = {
variant: 'bare',
size: 'medium',
compact: false,
menuItem: false,
component: 'a',
variant: 'bare' as const,
size: 'medium' as const,
compact: false as const,
menuItem: false as const,
badge: undefined, badge: undefined,
subtext: undefined, subtext: undefined,
block: false,
disabled: false,
block: false as const,
disabled: false as const,
icon: undefined, icon: undefined,
iconAfterChildren: false as const, iconAfterChildren: false as const,
// eslint-disable-next-line react/default-props-match-prop-types
component: 'a' as const,
// eslint-disable-next-line react/default-props-match-prop-types
href: undefined, href: undefined,
}; };

// extend the component type here for defining new props
// interface LinkButtonComponentType {
// (props: { href?: number }): React.ReactNode;
// }

// const CustomLink = (props: { href?: string | number }) => {
// return null;
// };
//
// const a = (
// <LinkButton
// component="a"
// href="string"
// />
// );

Loading…
Cancel
Save