Design system.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

220 行
5.3 KiB

  1. import * as React from 'react';
  2. import clsx from 'clsx';
  3. import { Button } from '@tesseract-design/web-base';
  4. /**
  5. * Derived HTML element of the {@link LinkButton} component.
  6. */
  7. export type LinkButtonDerivedElement = HTMLAnchorElement;
  8. /**
  9. * Props of the {@link LinkButton} component.
  10. */
  11. export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButtonDerivedElement>, 'href' | 'size'> {
  12. /**
  13. * Should the component occupy the whole width of its parent?
  14. */
  15. block?: boolean;
  16. /**
  17. * Variant of the component.
  18. */
  19. variant?: Button.Variant;
  20. /**
  21. * Complementary content of the component.
  22. */
  23. subtext?: React.ReactNode;
  24. /**
  25. * Short complementary content displayed at the edge of the component.
  26. */
  27. badge?: React.ReactNode;
  28. /**
  29. * Is this component part of a menu?
  30. */
  31. menuItem?: boolean;
  32. /**
  33. * Size of the component.
  34. */
  35. size?: Button.Size;
  36. /**
  37. * Should the component's content use minimal space?
  38. */
  39. compact?: boolean;
  40. /**
  41. * Component to use in rendering.
  42. */
  43. component?: React.ElementType<T>;
  44. /**
  45. * Is the component unable to receive activation?
  46. */
  47. disabled?: boolean;
  48. /**
  49. * Graphical representation of the component.
  50. */
  51. icon?: React.ReactNode;
  52. /**
  53. * Should the graphical representation of the component be placed after the children?
  54. */
  55. iconAfterChildren?: boolean;
  56. /**
  57. * URL to navigate to.
  58. */
  59. href?: string | unknown;
  60. }
  61. /**
  62. * Component for performing a navigation action.
  63. */
  64. export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonProps>((
  65. {
  66. variant = 'bare' as const,
  67. subtext,
  68. badge,
  69. menuItem = false,
  70. children,
  71. size = 'medium' as const,
  72. compact = false,
  73. className,
  74. block = false,
  75. component: EnabledComponent = 'a',
  76. disabled = false,
  77. href,
  78. style,
  79. icon,
  80. iconAfterChildren = false as const,
  81. ...etcProps
  82. },
  83. forwardedRef,
  84. ) => {
  85. const Component = disabled ? 'button' : EnabledComponent;
  86. return (
  87. <Component
  88. {...etcProps}
  89. href={disabled ? undefined : href}
  90. type={disabled ? 'button' : undefined}
  91. ref={forwardedRef}
  92. disabled={disabled || undefined}
  93. className={clsx(
  94. 'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline m-0',
  95. 'focus:outline-0 focus:ring-4',
  96. 'active:ring-tertiary/50',
  97. 'disabled:opacity-50 disabled:cursor-not-allowed',
  98. {
  99. 'flex w-full': block,
  100. 'inline-flex max-w-full align-middle': !block,
  101. },
  102. {
  103. 'pl-2 gap-2': compact,
  104. 'pl-4 gap-4': !compact,
  105. 'pr-4': !(compact || menuItem),
  106. 'pr-2': compact || menuItem,
  107. },
  108. {
  109. 'border-2 border-primary focus:border-secondary active:border-tertiary disabled:border-primary': variant !== 'bare',
  110. 'bg-negative text-primary focus:text-secondary active:text-tertiary disabled:text-primary': variant !== 'filled',
  111. 'bg-primary text-negative focus:bg-secondary active:bg-tertiary focus:text-negative active:text-negative disabled:bg-primary': variant === 'filled',
  112. },
  113. {
  114. 'h-10': size === 'small',
  115. 'h-12': size === 'medium',
  116. 'h-16': size === 'large',
  117. },
  118. className,
  119. )}
  120. data-testid="link"
  121. style={style}
  122. >
  123. <span
  124. className={clsx(
  125. 'flex-auto min-w-0 flex items-center gap-2',
  126. iconAfterChildren ? 'flex-row-reverse' : 'flex-row',
  127. )}
  128. >
  129. {icon && (
  130. <span
  131. data-testid="icon"
  132. >
  133. {icon}
  134. </span>
  135. )}
  136. {(children || subtext) && (
  137. <span
  138. className={clsx(
  139. 'min-w-0 flex-auto',
  140. {
  141. 'text-left': compact || menuItem,
  142. 'text-center': !(compact || menuItem),
  143. },
  144. )}
  145. >
  146. {children && (
  147. <span
  148. className={clsx(
  149. 'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded h-[1.1em]',
  150. )}
  151. data-testid="children"
  152. >
  153. {children}
  154. </span>
  155. )}
  156. {subtext && (
  157. <>
  158. <span className="sr-only">
  159. {' - '}
  160. </span>
  161. <span
  162. className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
  163. data-testid="subtext"
  164. >
  165. {subtext}
  166. </span>
  167. </>
  168. )}
  169. </span>
  170. )}
  171. </span>
  172. {badge && (
  173. <>
  174. <span className="sr-only">
  175. {' - '}
  176. </span>
  177. <span
  178. data-testid="badge"
  179. >
  180. {badge}
  181. </span>
  182. </>
  183. )}
  184. {menuItem && (
  185. <span
  186. data-testid="menuItemIndicator"
  187. >
  188. <svg
  189. className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
  190. viewBox="0 0 24 24"
  191. role="presentation"
  192. >
  193. <polyline points="9 18 15 12 9 6" />
  194. </svg>
  195. </span>
  196. )}
  197. </Component>
  198. );
  199. });
  200. LinkButton.displayName = 'LinkButton';
  201. LinkButton.defaultProps = {
  202. variant: 'bare',
  203. size: 'medium',
  204. compact: false,
  205. menuItem: false,
  206. component: 'a',
  207. badge: undefined,
  208. subtext: undefined,
  209. block: false,
  210. disabled: false,
  211. icon: undefined,
  212. iconAfterChildren: false as const,
  213. href: undefined,
  214. };