Common front-end components for Web using the Tesseract design system, written for React. https://make.modal.sh/tesseract/web/react/common
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

RadioButton.tsx 3.7 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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)',
  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>(({ label = '', name, ...etcProps }, ref) => (
  122. <Base>
  123. <CaptureArea>
  124. <Input {...etcProps} ref={ref} name={name} type="radio" />
  125. <IndicatorWrapper>
  126. <Border />
  127. <Indicator />
  128. </IndicatorWrapper>
  129. {typeof label! !== 'undefined' && label !== null && ' '}
  130. <Label>
  131. <LabelContent>{stringify(label)}</LabelContent>
  132. </Label>
  133. </CaptureArea>
  134. </Base>
  135. ))
  136. RadioButton.propTypes = propTypes
  137. RadioButton.displayName = 'RadioButton'
  138. export default RadioButton