Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

209 lines
5.0 KiB

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