Browse Source

Update LinkButton types

Enable more type-safe approach to component and href props.
master
TheoryOfNekomata 1 year 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;

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

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

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,
subtext: undefined,
block: false,
disabled: false,
block: false as const,
disabled: false as const,
icon: undefined,
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,
};

// 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