Design system.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

265 wiersze
6.6 KiB

  1. import * as React from 'react';
  2. import { Button, tailwind } from '@tesseract-design/web-base';
  3. const { tw } = tailwind;
  4. const LinkButtonDerivedElementComponent = 'a' as const;
  5. /**
  6. * Derived HTML element of the {@link LinkButton} component.
  7. */
  8. export type LinkButtonDerivedElement = HTMLElementTagNameMap[
  9. typeof LinkButtonDerivedElementComponent
  10. ];
  11. interface LinkButtonCommonProps 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. * Is the component unable to receive activation?
  42. */
  43. disabled?: boolean;
  44. /**
  45. * Graphical representation of the component.
  46. */
  47. icon?: React.ReactNode;
  48. /**
  49. * Should the graphical representation of the component be placed after the children?
  50. */
  51. iconAfterChildren?: boolean;
  52. }
  53. interface LinkButtonAnchorProps extends Pick<React.HTMLProps<LinkButtonDerivedElement>, 'href'> {
  54. component: typeof LinkButtonDerivedElementComponent;
  55. }
  56. interface LinkButtonComponentType {
  57. (props: { href?: string }): React.ReactNode;
  58. }
  59. interface LinkButtonFCProps<
  60. C extends LinkButtonComponentType = LinkButtonComponentType
  61. > {
  62. component: C;
  63. href: C extends (props: { href?: infer Href }) => React.ReactNode ? Href : never;
  64. }
  65. type LinkButtonAllProps<
  66. T extends LinkButtonComponentType = LinkButtonComponentType
  67. > =
  68. LinkButtonAnchorProps | LinkButtonFCProps<T>;
  69. /**
  70. * Props of the {@link LinkButton} component.
  71. */
  72. export type LinkButtonProps<
  73. T extends LinkButtonComponentType = LinkButtonComponentType
  74. > =
  75. LinkButtonCommonProps & LinkButtonAllProps<T>;
  76. /**
  77. * Component for performing a navigation action.
  78. */
  79. export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonProps>((
  80. {
  81. variant = 'bare' as const,
  82. subtext,
  83. badge,
  84. menuItem = false,
  85. children,
  86. size = 'medium' as const,
  87. compact = false,
  88. className,
  89. block = false,
  90. component: EnabledComponent = LinkButtonDerivedElementComponent,
  91. disabled = false,
  92. href,
  93. style,
  94. icon,
  95. iconAfterChildren = false as const,
  96. ...etcProps
  97. },
  98. forwardedRef,
  99. ) => {
  100. const Component = (disabled ? 'button' : EnabledComponent) as typeof LinkButtonDerivedElementComponent;
  101. const extraProps = {
  102. disabled: disabled || undefined,
  103. };
  104. return (
  105. <Component
  106. {...etcProps}
  107. {...extraProps}
  108. href={disabled ? undefined : href as string}
  109. type={disabled ? 'button' : undefined}
  110. ref={forwardedRef}
  111. className={tw(
  112. 'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline m-0',
  113. 'focus:outline-0 focus:ring-4',
  114. 'active:ring-tertiary/50',
  115. 'disabled:opacity-50 disabled:cursor-not-allowed',
  116. {
  117. 'flex w-full': block,
  118. 'inline-flex max-w-full align-middle': !block,
  119. },
  120. {
  121. 'pl-2 gap-2': compact,
  122. 'pl-4 gap-4': !compact,
  123. 'pr-4': !(compact || menuItem),
  124. 'pr-2': compact || menuItem,
  125. },
  126. {
  127. 'border-2 border-primary focus:border-secondary active:border-tertiary disabled:border-primary': variant !== 'bare',
  128. 'bg-negative text-primary focus:text-secondary active:text-tertiary disabled:text-primary': variant !== 'filled',
  129. 'bg-primary text-negative focus:bg-secondary active:bg-tertiary focus:text-negative active:text-negative disabled:bg-primary': variant === 'filled',
  130. },
  131. {
  132. 'h-10': size === 'small',
  133. 'h-12': size === 'medium',
  134. 'h-16': size === 'large',
  135. },
  136. className,
  137. )}
  138. data-testid="link"
  139. style={style}
  140. >
  141. <span
  142. data-testid="childrenParent"
  143. className={tw(
  144. 'flex-auto min-w-0 flex items-center gap-2',
  145. iconAfterChildren ? 'flex-row-reverse' : 'flex-row',
  146. )}
  147. >
  148. {icon && (
  149. <span
  150. data-testid="icon"
  151. >
  152. {icon}
  153. </span>
  154. )}
  155. {(children || subtext) && (
  156. <span
  157. className={tw(
  158. 'min-w-0 flex-auto',
  159. {
  160. 'text-left': compact || menuItem,
  161. 'text-center': !(compact || menuItem),
  162. },
  163. )}
  164. >
  165. {children && (
  166. <span
  167. className={tw(
  168. 'block uppercase font-bold w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded h-[1.1em]',
  169. )}
  170. data-testid="children"
  171. >
  172. {children}
  173. </span>
  174. )}
  175. {subtext && (
  176. <>
  177. <span className="sr-only">
  178. {' - '}
  179. </span>
  180. <span
  181. className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
  182. data-testid="subtext"
  183. >
  184. {subtext}
  185. </span>
  186. </>
  187. )}
  188. </span>
  189. )}
  190. </span>
  191. {badge && (
  192. <>
  193. <span className="sr-only">
  194. {' - '}
  195. </span>
  196. <span
  197. data-testid="badge"
  198. >
  199. {badge}
  200. </span>
  201. </>
  202. )}
  203. {menuItem && (
  204. <span
  205. data-testid="menuItemIndicator"
  206. >
  207. <svg
  208. className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
  209. viewBox="0 0 24 24"
  210. role="presentation"
  211. >
  212. <polyline points="9 18 15 12 9 6" />
  213. </svg>
  214. </span>
  215. )}
  216. </Component>
  217. );
  218. });
  219. LinkButton.displayName = 'LinkButton';
  220. LinkButton.defaultProps = {
  221. variant: 'bare' as const,
  222. size: 'medium' as const,
  223. compact: false as const,
  224. menuItem: false as const,
  225. badge: undefined,
  226. subtext: undefined,
  227. block: false as const,
  228. disabled: false as const,
  229. icon: undefined,
  230. iconAfterChildren: false as const,
  231. // eslint-disable-next-line react/default-props-match-prop-types
  232. component: LinkButtonDerivedElementComponent,
  233. // eslint-disable-next-line react/default-props-match-prop-types
  234. href: undefined,
  235. };
  236. // extend the component type here for defining new props
  237. // interface LinkButtonComponentType {
  238. // (props: { href?: number }): React.ReactNode;
  239. // }
  240. // const CustomLink = (props: { href?: string | number }) => {
  241. // return null;
  242. // };
  243. //
  244. // const a = (
  245. // <LinkButton
  246. // component="a"
  247. // href="string"
  248. // />
  249. // );