Design system.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

205 rader
5.6 KiB

  1. import * as React from 'react';
  2. import { Button, tailwind } from '@tesseract-design/web-base';
  3. import { useFallbackId } from '@modal-sh/react-utils';
  4. import { PluginCreator } from 'tailwindcss/types/config';
  5. const { tw } = tailwind;
  6. const RadioButtonDerivedElementComponent = 'input' as const;
  7. /**
  8. * Derived HTML element of the {@link RadioButton} component.
  9. */
  10. export type RadioButtonDerivedElement = HTMLElementTagNameMap[
  11. typeof RadioButtonDerivedElementComponent
  12. ];
  13. /**
  14. * Props of the {@link RadioButton} component.
  15. */
  16. export interface RadioButtonProps extends Omit<React.InputHTMLAttributes<RadioButtonDerivedElement>, 'type' | 'size'> {
  17. /**
  18. * Should the component occupy the whole width of its parent?
  19. */
  20. block?: boolean;
  21. /**
  22. * Should the component's content use minimal space?
  23. */
  24. compact?: boolean;
  25. /**
  26. * Size of the component.
  27. */
  28. size?: Button.Size;
  29. /**
  30. * Complementary content of the component.
  31. */
  32. subtext?: React.ReactNode;
  33. /**
  34. * Short complementary content displayed at the edge of the component.
  35. */
  36. badge?: React.ReactNode;
  37. /**
  38. * Variant of the component.
  39. */
  40. variant?: Button.Variant;
  41. }
  42. export const radioButtonPlugin: PluginCreator = ({ addComponents }) => {
  43. addComponents({
  44. '.radio-button': {
  45. '& + label > :first-child > :first-child': {
  46. 'display': 'none',
  47. },
  48. '&:checked + label > :first-child > :first-child': {
  49. 'display': 'block',
  50. },
  51. },
  52. });
  53. };
  54. /**
  55. * Component for selecting a single value from an array of choices grouped by name.
  56. *
  57. * This component is displayed as a regular button.
  58. */
  59. export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButtonProps>((
  60. {
  61. children,
  62. block = false as const,
  63. compact = false as const,
  64. size = 'medium' as const,
  65. id: idProp,
  66. className,
  67. subtext,
  68. badge,
  69. variant = 'bare' as const,
  70. style,
  71. ...etcProps
  72. },
  73. forwardedRef,
  74. ) => {
  75. const id = useFallbackId(idProp);
  76. return (
  77. <span
  78. className="contents"
  79. >
  80. <RadioButtonDerivedElementComponent
  81. {...etcProps}
  82. ref={forwardedRef}
  83. type="radio"
  84. id={id}
  85. className="sr-only peer radio-button"
  86. />
  87. <label
  88. style={style}
  89. htmlFor={id}
  90. data-testid="button"
  91. className={tw(
  92. 'items-center justify-start rounded overflow-hidden ring-secondary/50 leading-none select-none cursor-pointer',
  93. 'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50',
  94. 'active:ring-tertiary/50 active:ring-4',
  95. 'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0',
  96. {
  97. 'flex w-full': block,
  98. 'inline-flex max-w-full align-middle': !block,
  99. },
  100. {
  101. 'pl-2 gap-2 pr-2': compact,
  102. 'pl-4 gap-4 pr-4': !compact,
  103. },
  104. {
  105. 'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
  106. 'bg-negative text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary': variant !== 'filled',
  107. 'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
  108. },
  109. {
  110. 'h-10': size === 'small',
  111. 'h-12': size === 'medium',
  112. 'h-16': size === 'large',
  113. },
  114. className,
  115. )}
  116. >
  117. <span
  118. className={tw(
  119. 'w-6 h-6 block rounded-full border-2 p-0.5 box-border',
  120. {
  121. 'border-current': variant !== 'filled',
  122. 'border-negative': variant === 'filled',
  123. },
  124. )}
  125. >
  126. <span
  127. className={tw(
  128. 'w-full h-full rounded-full bg-current',
  129. {
  130. 'text-current': variant !== 'filled',
  131. 'text-negative': variant === 'filled',
  132. },
  133. )}
  134. />
  135. </span>
  136. <span
  137. className={tw(
  138. 'contents',
  139. {
  140. 'text-current': variant !== 'filled',
  141. 'text-negative': variant === 'filled',
  142. },
  143. )}
  144. >
  145. <span
  146. className={tw(
  147. 'flex-auto min-w-0',
  148. )}
  149. >
  150. <span
  151. className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
  152. data-testid="children"
  153. >
  154. {children}
  155. </span>
  156. {subtext && (
  157. <>
  158. <span className="sr-only">
  159. {' - '}
  160. </span>
  161. <span
  162. className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
  163. data-testid="subtext"
  164. >
  165. {subtext}
  166. </span>
  167. </>
  168. )}
  169. </span>
  170. {badge && (
  171. <>
  172. <span className="sr-only">
  173. {' - '}
  174. </span>
  175. <span
  176. data-testid="badge"
  177. >
  178. {badge}
  179. </span>
  180. </>
  181. )}
  182. </span>
  183. </label>
  184. </span>
  185. );
  186. });
  187. RadioButton.displayName = 'RadioButton' as const;
  188. RadioButton.defaultProps = {
  189. badge: undefined,
  190. block: false as const,
  191. compact: false as const,
  192. subtext: undefined,
  193. size: 'medium' as const,
  194. variant: 'bare' as const,
  195. };