Common front-end components for Web using the Tesseract design system, written for React. https://make.modal.sh/tesseract/web/react/common
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

RadioButton.tsx 3.7 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. const Base = styled('div')({
  6. display: 'block',
  7. })
  8. const CaptureArea = styled('label')({
  9. marginTop: '0.25rem',
  10. '::after': {
  11. content: '""',
  12. clear: 'both',
  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.75rem',
  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.75rem',
  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. boxSizing: 'border-box',
  82. display: 'inline-grid',
  83. placeContent: 'center',
  84. borderRadius: '50%',
  85. transitionProperty: 'background-color, color, width, height, opacity',
  86. [`${Input}:checked + ${IndicatorWrapper} &`]: {
  87. width: `${(100 * 2) / 3}%`,
  88. height: `${(100 * 2) / 3}%`,
  89. opacity: 1,
  90. },
  91. })
  92. const Label = styled('span')({
  93. display: 'block',
  94. verticalAlign: 'top',
  95. float: 'right',
  96. width: 'calc(100% - 2.5rem)',
  97. fontFamily: 'var(--font-family-base, sans-serif)',
  98. pointerEvents: 'none',
  99. })
  100. const LabelContent = styled('span')({
  101. display: 'inline',
  102. pointerEvents: 'auto',
  103. })
  104. const propTypes = {
  105. /**
  106. * Group where the component belongs.
  107. */
  108. name: PropTypes.string.isRequired,
  109. /**
  110. * Short textual description indicating the nature of the component's value.
  111. */
  112. label: PropTypes.any,
  113. }
  114. type Props = PropTypes.InferProps<typeof propTypes>
  115. /**
  116. * Component for values which are to be selected from a few list of options.
  117. * @see {@link Checkbox} for a similar component on selecting values among very few choices.
  118. * @see {@link Select} for a similar component suitable for selecting more values.
  119. * @type {React.ComponentType<{readonly label?: string, readonly name?: string} & React.ClassAttributes<unknown>>}
  120. */
  121. const RadioButton = React.forwardRef<HTMLInputElement, Props>(
  122. (
  123. { label = '', name, ...etcProps },
  124. ref
  125. ) => (
  126. <Base>
  127. <CaptureArea>
  128. <Input {...etcProps} ref={ref} name={name} type="radio" />
  129. <IndicatorWrapper>
  130. <Border />
  131. <Indicator />
  132. </IndicatorWrapper>
  133. {typeof label! !== 'undefined' && label !== null && ' '}
  134. <Label>
  135. <LabelContent>{stringify(label)}</LabelContent>
  136. </Label>
  137. </CaptureArea>
  138. </Base>
  139. )
  140. )
  141. RadioButton.propTypes = propTypes
  142. RadioButton.displayName = 'RadioButton'
  143. export default RadioButton