chevron.tsx 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import styled from '@emotion/styled';
  2. import {motion} from 'framer-motion';
  3. import theme from 'sentry/utils/theme';
  4. interface ChevronProps extends React.SVGAttributes<SVGSVGElement> {
  5. direction?: 'up' | 'right' | 'down' | 'left';
  6. /**
  7. * The size of the checkbox. Defaults to 'sm'.
  8. */
  9. size?: 'large' | 'medium' | 'small';
  10. weight?: 'regular' | 'medium';
  11. }
  12. const rubikWeightFactor: Record<NonNullable<ChevronProps['weight']>, number> = {
  13. regular: 1,
  14. medium: 1.4,
  15. };
  16. const chevronSizeMap: Record<NonNullable<ChevronProps['size']>, string> = {
  17. small: theme.fontSizeSmall,
  18. medium: theme.fontSizeMedium,
  19. large: theme.fontSizeLarge,
  20. };
  21. function getPath(direction: NonNullable<ChevronProps['direction']>) {
  22. // Base values for a downward chevron
  23. const base = [
  24. [3.5, 5.5],
  25. [7, 9],
  26. [10.5, 5.5],
  27. ];
  28. switch (direction) {
  29. case 'right':
  30. // Switch X and Y axis (so `base[0][1]` goes before `base[0][1]`)
  31. return `M${base[0][1]} ${base[0][0]}L${base[1][1]} ${base[1][0]}L${base[2][1]} ${base[2][0]}`;
  32. case 'left':
  33. // Switch X and Y axis, then flip X axis (so 14 - …)
  34. return `M${14 - base[0][1]} ${base[0][0]}L${14 - base[1][1]} ${base[1][0]}L${14 - base[2][1]} ${base[2][0]}`;
  35. case 'up':
  36. // Flip Y axis (so 14 - …)
  37. return `M${base[0][0]} ${14 - base[0][1]}L${base[1][0]} ${14 - base[1][1]}L${base[2][0]} ${14 - base[2][1]}`;
  38. case 'down':
  39. default:
  40. return `M${base[0][0]} ${base[0][1]}L${base[1][0]} ${base[1][1]}L${base[2][0]} ${base[2][1]}`;
  41. }
  42. }
  43. function Chevron({
  44. size = 'medium',
  45. weight = 'regular',
  46. direction = 'down',
  47. ...props
  48. }: ChevronProps) {
  49. return (
  50. <VariableWeightIcon
  51. viewBox="0 0 14 14"
  52. size={chevronSizeMap[size]}
  53. weightFactor={rubikWeightFactor[weight]}
  54. {...props}
  55. >
  56. <motion.path
  57. animate={{d: getPath(direction)}}
  58. transition={{ease: 'easeOut', duration: 0.25}}
  59. initial={false}
  60. />
  61. </VariableWeightIcon>
  62. );
  63. }
  64. const VariableWeightIcon = styled('svg')<{size: string; weightFactor: number}>`
  65. width: ${p => p.size};
  66. height: ${p => p.size};
  67. fill: none;
  68. stroke: currentColor;
  69. stroke-linecap: round;
  70. stroke-linejoin: round;
  71. stroke-width: calc(${p => p.size} * 0.0875 * ${p => p.weightFactor});
  72. `;
  73. export {Chevron};