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

237 строки
5.4 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')({
  51. ...buttonStyles,
  52. width: '100%',
  53. })
  54. Base.displayName = 'button'
  55. const LinkBase = styled('a')(buttonStyles)
  56. LinkBase.displayName = 'a'
  57. const DisabledLinkBase = styled('span')(disabledLinkButtonStyles)
  58. DisabledLinkBase.displayName = 'span'
  59. const Border = styled('span')({
  60. borderColor: 'var(--color-accent, blue)',
  61. boxSizing: 'border-box',
  62. display: 'inline-block',
  63. borderWidth: '0.125rem',
  64. borderStyle: 'solid',
  65. position: 'absolute',
  66. top: 0,
  67. left: 0,
  68. width: '100%',
  69. height: '100%',
  70. borderRadius: 'inherit',
  71. pointerEvents: 'none',
  72. transitionProperty: 'border-color',
  73. '::before': {
  74. position: 'absolute',
  75. top: 0,
  76. left: 0,
  77. width: '100%',
  78. height: '100%',
  79. content: "''",
  80. borderRadius: '0.125rem',
  81. opacity: 0.5,
  82. pointerEvents: 'none',
  83. },
  84. [`${Base}:focus &::before`]: {
  85. boxShadow: '0 0 0 0.375rem var(--color-accent, blue)',
  86. },
  87. })
  88. Border.displayName = 'span'
  89. const defaultVariantStyleSet: React.CSSProperties = {
  90. backgroundColor: 'transparent',
  91. color: 'var(--color-accent, blue)',
  92. }
  93. const variantStyleSets: Record<Variant, React.CSSProperties> = {
  94. outline: defaultVariantStyleSet,
  95. primary: {
  96. backgroundColor: 'var(--color-accent, blue)',
  97. color: 'var(--color-bg, white)',
  98. },
  99. }
  100. const propTypes = {
  101. /**
  102. * Size of the component.
  103. */
  104. size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
  105. /**
  106. * Variant of the component.
  107. */
  108. variant: PropTypes.oneOf<Variant>(['outline', 'primary']),
  109. /**
  110. * Text to identify the action associated upon activation of the component.
  111. */
  112. children: PropTypes.any,
  113. /**
  114. * Can the component be activated?
  115. */
  116. disabled: PropTypes.bool,
  117. /**
  118. * The corresponding HTML element of the component.
  119. */
  120. element: PropTypes.oneOf<ButtonElement>(['a', 'button']),
  121. /**
  122. * The URL of the page to navigate to, if element is set to "a".
  123. */
  124. href: PropTypes.string,
  125. /**
  126. * The target on where to display the page navigated to, if element is set to "a".
  127. */
  128. target: PropTypes.string,
  129. /**
  130. * The relationship of the current page to the referred page in "href", if element is set to "a".
  131. */
  132. rel: PropTypes.string,
  133. /**
  134. * The type of the button, if element is set to "button".
  135. */
  136. type: PropTypes.oneOf<ButtonType>(['submit', 'reset', 'button']),
  137. /**
  138. * Does the button display a border?
  139. */
  140. border: PropTypes.bool,
  141. }
  142. type Props = PropTypes.InferProps<typeof propTypes>
  143. const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>(
  144. (
  145. {
  146. size = 'medium',
  147. variant = 'outline',
  148. disabled = false,
  149. children,
  150. element = 'button',
  151. href,
  152. target,
  153. rel,
  154. type = 'button',
  155. border = false,
  156. ...etcProps
  157. },
  158. ref,
  159. ) => {
  160. const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
  161. const commonButtonStyles: React.CSSProperties = {
  162. ...theVariantStyleSet,
  163. minHeight: MIN_HEIGHTS[size!],
  164. }
  165. const buttonContent = (
  166. <React.Fragment>
  167. {
  168. border
  169. && (
  170. <Border />
  171. )
  172. }
  173. {stringify(children)}
  174. </React.Fragment>
  175. )
  176. switch (element) {
  177. case 'button':
  178. return (
  179. <Base {...etcProps} type={type!} ref={ref as React.Ref<HTMLButtonElement>} disabled={disabled!} style={commonButtonStyles}>
  180. {buttonContent}
  181. </Base>
  182. )
  183. case 'a':
  184. if (disabled) {
  185. return (
  186. <DisabledLinkBase {...etcProps} ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}>
  187. {buttonContent}
  188. </DisabledLinkBase>
  189. )
  190. }
  191. return (
  192. <LinkBase
  193. {...etcProps}
  194. href={href!}
  195. target={target!}
  196. rel={rel!}
  197. ref={ref as React.Ref<HTMLAnchorElement>}
  198. style={commonButtonStyles}
  199. >
  200. {buttonContent}
  201. </LinkBase>
  202. )
  203. default:
  204. break
  205. }
  206. return null
  207. },
  208. )
  209. Button.propTypes = propTypes
  210. Button.displayName = 'Button'
  211. export default Button