Common front-end components for Web using the Tesseract design system, written for React. https://make.modal.sh/tesseract/web/react/common
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

232 рядки
5.3 KiB

  1. import * as React from 'react'
  2. import * as PropTypes from 'prop-types'
  3. import styled, { CSSObject } from 'styled-components'
  4. import { Size, SizeMap } from '../../services/utilities'
  5. import stringify from '../../services/stringify'
  6. export type Variant = 'outline' | 'primary'
  7. export type ButtonElement = 'a' | 'button'
  8. type ButtonType = 'submit' | 'reset' | 'button'
  9. const MIN_HEIGHTS: SizeMap<string | number> = {
  10. small: '2.5rem',
  11. medium: '3rem',
  12. large: '4rem',
  13. }
  14. const disabledButtonStyles: CSSObject = {
  15. opacity: 0.5,
  16. cursor: 'not-allowed',
  17. }
  18. const buttonStyles: CSSObject = {
  19. display: 'grid',
  20. appearance: 'none',
  21. padding: '0 1rem',
  22. font: 'inherit',
  23. fontFamily: 'var(--font-family-base, sans-serif)',
  24. textTransform: 'uppercase',
  25. fontWeight: 'bolder',
  26. borderRadius: '0.25rem',
  27. placeContent: 'center',
  28. position: 'relative',
  29. cursor: 'pointer',
  30. border: 0,
  31. userSelect: 'none',
  32. textDecoration: 'none',
  33. transitionProperty: 'background-color, color',
  34. whiteSpace: 'nowrap',
  35. lineHeight: 1,
  36. ':focus': {
  37. '--color-accent': 'var(--color-active, Highlight)',
  38. outline: 0,
  39. },
  40. ':disabled': disabledButtonStyles,
  41. '::-moz-focus-inner': {
  42. outline: 0,
  43. border: 0,
  44. },
  45. }
  46. const disabledLinkButtonStyles: CSSObject = {
  47. ...buttonStyles,
  48. ...disabledButtonStyles,
  49. }
  50. const Base = styled('button')(buttonStyles)
  51. Base.displayName = 'button'
  52. const LinkBase = styled('a')(buttonStyles)
  53. LinkBase.displayName = 'a'
  54. const DisabledLinkBase = styled('span')(disabledLinkButtonStyles)
  55. DisabledLinkBase.displayName = 'span'
  56. const Border = styled('span')({
  57. borderColor: 'var(--color-accent, blue)',
  58. boxSizing: 'border-box',
  59. display: 'inline-block',
  60. borderWidth: '0.125rem',
  61. borderStyle: 'solid',
  62. position: 'absolute',
  63. top: 0,
  64. left: 0,
  65. width: '100%',
  66. height: '100%',
  67. borderRadius: 'inherit',
  68. pointerEvents: 'none',
  69. transitionProperty: 'border-color',
  70. '::before': {
  71. position: 'absolute',
  72. top: 0,
  73. left: 0,
  74. width: '100%',
  75. height: '100%',
  76. content: "''",
  77. borderRadius: '0.125rem',
  78. opacity: 0.5,
  79. pointerEvents: 'none',
  80. },
  81. [`${Base}:focus &::before`]: {
  82. boxShadow: '0 0 0 0.375rem var(--color-accent, blue)',
  83. },
  84. })
  85. Border.displayName = 'span'
  86. const defaultVariantStyleSet: React.CSSProperties = {
  87. backgroundColor: 'transparent',
  88. color: 'var(--color-accent, blue)',
  89. }
  90. const variantStyleSets: Record<Variant, React.CSSProperties> = {
  91. outline: defaultVariantStyleSet,
  92. primary: {
  93. backgroundColor: 'var(--color-accent, blue)',
  94. color: 'var(--color-bg, white)',
  95. },
  96. }
  97. const propTypes = {
  98. /**
  99. * Size of the component.
  100. */
  101. size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
  102. /**
  103. * Variant of the component.
  104. */
  105. variant: PropTypes.oneOf<Variant>(['outline', 'primary']),
  106. /**
  107. * Text to identify the action associated upon activation of the component.
  108. */
  109. children: PropTypes.any,
  110. /**
  111. * Can the component be activated?
  112. */
  113. disabled: PropTypes.bool,
  114. /**
  115. * The corresponding HTML element of the component.
  116. */
  117. element: PropTypes.oneOf<ButtonElement>(['a', 'button']),
  118. /**
  119. * The URL of the page to navigate to, if element is set to "a".
  120. */
  121. href: PropTypes.string,
  122. /**
  123. * The target on where to display the page navigated to, if element is set to "a".
  124. */
  125. target: PropTypes.string,
  126. /**
  127. * The relationship of the current page to the referred page in "href", if element is set to "a".
  128. */
  129. rel: PropTypes.string,
  130. /**
  131. * The type of the button, if element is set to "button".
  132. */
  133. type: PropTypes.oneOf<ButtonType>(['submit', 'reset', 'button']),
  134. /**
  135. * Does the button display a border?
  136. */
  137. border: PropTypes.bool,
  138. }
  139. type Props = PropTypes.InferProps<typeof propTypes>
  140. const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>(
  141. (
  142. {
  143. size = 'medium',
  144. variant = 'outline',
  145. disabled = false,
  146. children,
  147. element = 'button',
  148. href,
  149. target,
  150. rel,
  151. type = 'button',
  152. border = false,
  153. },
  154. ref,
  155. ) => {
  156. const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
  157. const commonButtonStyles: React.CSSProperties = {
  158. ...theVariantStyleSet,
  159. minHeight: MIN_HEIGHTS[size!],
  160. }
  161. const buttonContent = (
  162. <React.Fragment>
  163. {
  164. border
  165. && (
  166. <Border />
  167. )
  168. }
  169. {stringify(children)}
  170. </React.Fragment>
  171. )
  172. switch (element) {
  173. case 'button':
  174. return (
  175. <Base type={type!} ref={ref as React.Ref<HTMLButtonElement>} disabled={disabled!} style={commonButtonStyles}>
  176. {buttonContent}
  177. </Base>
  178. )
  179. case 'a':
  180. if (disabled) {
  181. return (
  182. <DisabledLinkBase ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}>
  183. {buttonContent}
  184. </DisabledLinkBase>
  185. )
  186. }
  187. return (
  188. <LinkBase
  189. href={href!}
  190. target={target!}
  191. rel={rel!}
  192. ref={ref as React.Ref<HTMLAnchorElement>}
  193. style={commonButtonStyles}
  194. >
  195. {buttonContent}
  196. </LinkBase>
  197. )
  198. default:
  199. break
  200. }
  201. return null
  202. },
  203. )
  204. Button.propTypes = propTypes
  205. Button.displayName = 'Button'
  206. export default Button