Design system.
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.
 
 
 

166 rivejä
4.6 KiB

  1. import * as React from 'react';
  2. import clsx from 'clsx';
  3. import { PluginCreator } from 'tailwindcss/types/config';
  4. import { useFallbackId } from '@modal-sh/react-utils';
  5. /**
  6. * Derived HTML element of the {@link ToggleTickBox} component.
  7. */
  8. export type ToggleTickBoxDerivedElement = HTMLInputElement;
  9. /**
  10. * Props of the {@link ToggleTickBox} component.
  11. */
  12. export interface ToggleTickBoxProps extends Omit<React.InputHTMLAttributes<ToggleTickBoxDerivedElement>, 'type' | 'size'> {
  13. /**
  14. * Should the component occupy the whole width of its parent?
  15. */
  16. block?: boolean;
  17. /**
  18. * Complementary content of the component.
  19. */
  20. subtext?: React.ReactNode;
  21. /**
  22. * Is the component in an indeterminate state?
  23. */
  24. indeterminate?: boolean;
  25. }
  26. export const toggleTickBoxPlugin: PluginCreator = ({ addComponents, }) => {
  27. addComponents({
  28. '.toggle-tick-box': {
  29. '& + label + label > :first-child > :first-child': {
  30. 'display': 'none',
  31. },
  32. '&:checked + label + label > :first-child > :first-child': {
  33. 'display': 'block',
  34. },
  35. '& + label + label > :first-child > :first-child + *': {
  36. 'display': 'none',
  37. },
  38. '&:indeterminate + label + label > :first-child > :first-child + *': {
  39. 'display': 'block',
  40. },
  41. },
  42. });
  43. };
  44. /**
  45. * Component for toggling a Boolean value with the appearance of a tick box.
  46. */
  47. export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, ToggleTickBoxProps>((
  48. {
  49. children,
  50. block = false,
  51. id: idProp,
  52. className,
  53. subtext,
  54. style,
  55. indeterminate = false,
  56. ...etcProps
  57. },
  58. forwardedRef,
  59. ) => {
  60. const defaultRef = React.useRef<ToggleTickBoxDerivedElement>(null);
  61. const ref = forwardedRef ?? defaultRef;
  62. const id = useFallbackId(idProp);
  63. React.useEffect(() => {
  64. if (typeof ref === 'function') {
  65. const defaultElement = defaultRef.current as ToggleTickBoxDerivedElement;
  66. defaultElement.indeterminate = indeterminate;
  67. ref(defaultElement);
  68. return;
  69. }
  70. const element = ref.current as ToggleTickBoxDerivedElement;
  71. element.indeterminate = indeterminate;
  72. }, [indeterminate, defaultRef, ref]);
  73. return (
  74. <div
  75. className={clsx(
  76. 'gap-x-4 flex-wrap',
  77. block && 'flex',
  78. !block && 'inline-flex align-center',
  79. className,
  80. )}
  81. style={style}
  82. data-testid="base"
  83. >
  84. <input
  85. {...etcProps}
  86. ref={typeof ref === 'function' ? defaultRef : ref}
  87. type="checkbox"
  88. id={id}
  89. className="sr-only peer/radio toggle-tick-box"
  90. />
  91. <label
  92. htmlFor={id}
  93. className="peer/children order-2 cursor-pointer peer-disabled/radio:cursor-not-allowed"
  94. >
  95. <span
  96. data-testid="children"
  97. >
  98. {children}
  99. </span>
  100. </label>
  101. <label
  102. htmlFor={id}
  103. className={clsx(
  104. 'order-1 block rounded ring-secondary/50 overflow-hidden gap-4 leading-none select-none cursor-pointer',
  105. 'peer-focus/radio:outline-0 peer-focus/radio:ring-4 peer-focus/radio:ring-secondary/50',
  106. 'active:ring-tertiary/50 active:ring-4',
  107. 'peer-active/children:ring-tertiary/50 peer-active/children:ring-4 peer-active/children:text-tertiary',
  108. 'peer-disabled/radio:opacity-50 peer-disabled/radio:cursor-not-allowed peer-disabled/radio:ring-0',
  109. 'text-primary peer-disabled/radio:text-primary peer-focus/radio:text-secondary peer-checked/radio:text-tertiary active:text-tertiary',
  110. )}
  111. >
  112. <span
  113. className={clsx(
  114. 'w-6 h-6 block rounded-inherit border-2 p-0.5 box-border border-current',
  115. )}
  116. >
  117. <svg
  118. className={clsx(
  119. 'w-full h-full fill-none stroke-3 linejoin-round linecap-round stroke-current',
  120. )}
  121. viewBox="0 0 24 24"
  122. role="presentation"
  123. >
  124. <polyline
  125. points="20 6 9 17 4 12"
  126. />
  127. </svg>
  128. <svg
  129. className={clsx(
  130. 'w-full h-full fill-none stroke-3 linejoin-round linecap-round stroke-current',
  131. )}
  132. viewBox="0 0 24 24"
  133. role="presentation"
  134. >
  135. <polyline
  136. points="20 12 4 12"
  137. />
  138. </svg>
  139. </span>
  140. </label>
  141. {subtext && (
  142. <div
  143. className="block w-full text-xs pl-10 order-3"
  144. data-testid="subtext"
  145. >
  146. {subtext}
  147. </div>
  148. )}
  149. </div>
  150. );
  151. });
  152. ToggleTickBox.displayName = 'ToggleTickBox';
  153. ToggleTickBox.defaultProps = {
  154. block: false,
  155. indeterminate: false,
  156. subtext: undefined,
  157. };