radioPanelGroup.tsx 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import styled from '@emotion/styled';
  2. import Radio from 'sentry/components/radio';
  3. import space from 'sentry/styles/space';
  4. type RadioPanelGroupProps<C extends string> = {
  5. /**
  6. * An array of [id, name]
  7. */
  8. choices: [C, React.ReactNode, React.ReactNode?][];
  9. label: string;
  10. onChange: (id: C, e: React.FormEvent<HTMLInputElement>) => void;
  11. value: string | null;
  12. };
  13. type Props<C extends string> = RadioPanelGroupProps<C> &
  14. Omit<React.HTMLAttributes<HTMLDivElement>, keyof RadioPanelGroupProps<C>>;
  15. const RadioPanelGroup = <C extends string>({
  16. value,
  17. choices,
  18. label,
  19. onChange,
  20. ...props
  21. }: Props<C>) => (
  22. <Container {...props} role="radiogroup" aria-labelledby={label}>
  23. {(choices || []).map(([id, name, extraContent], index) => (
  24. <RadioPanel key={index}>
  25. <RadioLineItem role="radio" index={index} aria-checked={value === id}>
  26. <Radio
  27. radioSize="small"
  28. aria-label={id}
  29. checked={value === id}
  30. onChange={(e: React.FormEvent<HTMLInputElement>) => onChange(id, e)}
  31. />
  32. <div>{name}</div>
  33. {extraContent}
  34. </RadioLineItem>
  35. </RadioPanel>
  36. ))}
  37. </Container>
  38. );
  39. export default RadioPanelGroup;
  40. const Container = styled('div')`
  41. display: grid;
  42. gap: ${space(1)};
  43. grid-auto-flow: row;
  44. grid-auto-rows: max-content;
  45. grid-auto-columns: auto;
  46. `;
  47. const RadioLineItem = styled('label')<{
  48. index: number;
  49. }>`
  50. display: grid;
  51. gap: ${space(0.25)} ${space(1)};
  52. grid-template-columns: max-content auto max-content;
  53. align-items: center;
  54. cursor: pointer;
  55. outline: none;
  56. font-weight: normal;
  57. margin: 0;
  58. color: ${p => p.theme.subText};
  59. transition: color 0.3s ease-in;
  60. padding: 0;
  61. position: relative;
  62. &:hover,
  63. &:focus {
  64. color: ${p => p.theme.textColor};
  65. }
  66. svg {
  67. display: none;
  68. opacity: 0;
  69. }
  70. &[aria-checked='true'] {
  71. color: ${p => p.theme.textColor};
  72. }
  73. `;
  74. const RadioPanel = styled('div')`
  75. margin: 0;
  76. `;