radioPanelGroup.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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. function RadioPanelGroup<C extends string>({
  16. value,
  17. choices,
  18. label,
  19. onChange,
  20. ...props
  21. }: Props<C>) {
  22. return (
  23. <Container {...props} role="radiogroup" aria-labelledby={label}>
  24. {(choices || []).map(([id, name, extraContent], index) => (
  25. <RadioPanel key={index}>
  26. <RadioLineItem role="radio" index={index} aria-checked={value === id}>
  27. <Radio
  28. radioSize="small"
  29. aria-label={id}
  30. checked={value === id}
  31. onChange={(e: React.FormEvent<HTMLInputElement>) => onChange(id, e)}
  32. />
  33. <div>{name}</div>
  34. {extraContent}
  35. </RadioLineItem>
  36. </RadioPanel>
  37. ))}
  38. </Container>
  39. );
  40. }
  41. export default RadioPanelGroup;
  42. const Container = styled('div')`
  43. display: grid;
  44. gap: ${space(1)};
  45. grid-auto-flow: row;
  46. grid-auto-rows: max-content;
  47. grid-auto-columns: auto;
  48. `;
  49. const RadioLineItem = styled('label')<{
  50. index: number;
  51. }>`
  52. display: grid;
  53. gap: ${space(0.25)} ${space(1)};
  54. grid-template-columns: max-content auto max-content;
  55. align-items: center;
  56. cursor: pointer;
  57. outline: none;
  58. font-weight: ${p => p.theme.fontWeightNormal};
  59. margin: 0;
  60. color: ${p => p.theme.subText};
  61. transition: color 0.3s ease-in;
  62. padding: 0;
  63. position: relative;
  64. &:hover,
  65. &:focus {
  66. color: ${p => p.theme.textColor};
  67. }
  68. svg {
  69. display: none;
  70. opacity: 0;
  71. }
  72. &[aria-checked='true'] {
  73. color: ${p => p.theme.textColor};
  74. }
  75. `;
  76. const RadioPanel = styled('div')`
  77. margin: 0;
  78. `;