Common front-end components for Web using the Tesseract design system, written for React. https://make.modal.sh/tesseract/web/react/common
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.
 
 
 
 

178 regels
4.2 KiB

  1. import * as React from 'react'
  2. import * as PropTypes from 'prop-types'
  3. import styled from 'styled-components'
  4. import stringify from '../../services/stringify'
  5. import Icon from '../Icon/Icon'
  6. const Base = styled('div')({
  7. display: 'block',
  8. })
  9. const CaptureArea = styled('label')({
  10. marginTop: '0.25rem',
  11. '::after': {
  12. content: '""',
  13. },
  14. })
  15. CaptureArea.displayName = 'label'
  16. const Input = styled('input')({
  17. position: 'absolute',
  18. left: -999999,
  19. width: 1,
  20. height: 1,
  21. })
  22. Input.displayName = 'input'
  23. const IndicatorWrapper = styled('span')({
  24. borderColor: 'var(--color-accent, blue)',
  25. boxSizing: 'border-box',
  26. backgroundColor: 'transparent',
  27. borderRadius: '0.25rem',
  28. position: 'relative',
  29. width: '1.5rem',
  30. height: '1.5rem',
  31. minWidth: '1.5rem',
  32. maxWidth: '1.5rem',
  33. display: 'inline-flex',
  34. verticalAlign: 'top',
  35. justifyContent: 'center',
  36. alignItems: 'center',
  37. cursor: 'pointer',
  38. transitionProperty: 'border-color',
  39. [`${Input}:focus ~ &`]: {
  40. '--color-accent': 'var(--color-active, Highlight)',
  41. },
  42. [`${Input}:disabled ~ &`]: {
  43. cursor: 'not-allowed',
  44. opacity: 0.5,
  45. },
  46. })
  47. const Border = styled('span')({
  48. borderColor: 'var(--color-accent, blue)',
  49. boxSizing: 'border-box',
  50. display: 'inline-block',
  51. borderWidth: '0.125rem',
  52. borderStyle: 'solid',
  53. position: 'absolute',
  54. top: 0,
  55. left: 0,
  56. width: '100%',
  57. height: '100%',
  58. borderRadius: 'inherit',
  59. transitionProperty: 'border-color',
  60. '::before': {
  61. position: 'absolute',
  62. top: 0,
  63. left: 0,
  64. width: '100%',
  65. height: '100%',
  66. content: "''",
  67. borderRadius: '0.125rem',
  68. opacity: 0.5,
  69. pointerEvents: 'none',
  70. },
  71. [`${Base}:focus-within &::before`]: {
  72. boxShadow: '0 0 0 0.375rem var(--color-accent, blue)',
  73. },
  74. })
  75. const Indicator = styled('span')({
  76. backgroundColor: 'var(--color-accent, blue)',
  77. color: 'var(--color-bg, white)',
  78. width: 0,
  79. height: 0,
  80. opacity: 0,
  81. position: 'relative',
  82. boxSizing: 'border-box',
  83. display: 'inline-grid',
  84. placeContent: 'center',
  85. borderRadius: 'inherit',
  86. lineHeight: 1,
  87. transitionProperty: 'background-color, color, width, height, opacity',
  88. [`${Input}:checked + ${IndicatorWrapper} &`]: {
  89. opacity: 1,
  90. width: '100%',
  91. height: '100%',
  92. },
  93. })
  94. const Label = styled('span')({
  95. display: 'block',
  96. verticalAlign: 'top',
  97. float: 'right',
  98. width: 'calc(100% - 2.5rem)',
  99. fontFamily: 'var(--font-family-base, sans-serif)',
  100. pointerEvents: 'none',
  101. })
  102. const LabelContent = styled('span')({
  103. display: 'inline',
  104. pointerEvents: 'auto',
  105. })
  106. const propTypes = {
  107. /**
  108. * Short textual description indicating the nature of the component's value.
  109. */
  110. label: PropTypes.any,
  111. /**
  112. * Name of the form field associated with this component.
  113. */
  114. name: PropTypes.string,
  115. /**
  116. * Event handler triggered when the component is toggled.
  117. */
  118. onChange: PropTypes.func,
  119. /**
  120. * Event handler triggered when the component receives focus.
  121. */
  122. onFocus: PropTypes.func,
  123. /**
  124. * Event handler triggered when the component loses focus.
  125. */
  126. onBlur: PropTypes.func,
  127. }
  128. type Props = PropTypes.InferProps<typeof propTypes>
  129. /**
  130. * Component for values that have an on/off state.
  131. * @see {@link Select} for a similar component suitable for selecting more values.
  132. * @see {@link RadioButton} for a similar component on selecting a single value among very few choices.
  133. * @type {React.ComponentType<{readonly label?: string} & React.ClassAttributes<unknown>>}
  134. */
  135. const Checkbox = React.forwardRef<HTMLInputElement, Props>(({ label = '', name, onChange, onFocus, onBlur }, ref) => (
  136. <Base>
  137. <CaptureArea>
  138. <Input
  139. ref={ref}
  140. type="checkbox"
  141. name={name!}
  142. onChange={onChange as React.ChangeEventHandler}
  143. onFocus={onFocus as React.FocusEventHandler}
  144. onBlur={onBlur as React.FocusEventHandler}
  145. />
  146. <IndicatorWrapper>
  147. <Border />
  148. <Indicator>
  149. <Icon name="check" label="" />
  150. </Indicator>
  151. </IndicatorWrapper>
  152. {typeof label! !== 'undefined' && label !== null && ' '}
  153. <Label>
  154. <LabelContent>{stringify(label)}</LabelContent>
  155. </Label>
  156. </CaptureArea>
  157. </Base>
  158. ))
  159. Checkbox.propTypes = propTypes
  160. Checkbox.displayName = 'Checkbox'
  161. export default Checkbox