device.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { useEffect } from 'react';
  2. import { Breakpoint } from '@growi/ui/dist/interfaces';
  3. import {
  4. addBreakpointListener,
  5. cleanupBreakpointListener,
  6. } from '@growi/ui/dist/utils';
  7. import { atom, useAtom } from 'jotai';
  8. // Device state atoms
  9. export const isDeviceLargerThanXlAtom = atom(false);
  10. export const isDeviceLargerThanLgAtom = atom(false);
  11. export const isDeviceLargerThanMdAtom = atom(false);
  12. export const isMobileAtom = atom(false);
  13. export const useDeviceLargerThanXl = () => {
  14. const [isLargerThanXl, setIsLargerThanXl] = useAtom(isDeviceLargerThanXlAtom);
  15. useEffect(() => {
  16. const xlOrAboveHandler = function (this: MediaQueryList): void {
  17. // lg -> xl: matches will be true
  18. // xl -> lg: matches will be false
  19. setIsLargerThanXl(this.matches);
  20. };
  21. const mql = addBreakpointListener(Breakpoint.XL, xlOrAboveHandler);
  22. // initialize
  23. setIsLargerThanXl(mql.matches);
  24. return () => {
  25. cleanupBreakpointListener(mql, xlOrAboveHandler);
  26. };
  27. }, [setIsLargerThanXl]);
  28. return [isLargerThanXl, setIsLargerThanXl] as const;
  29. };
  30. export const useDeviceLargerThanLg = () => {
  31. const [isLargerThanLg, setIsLargerThanLg] = useAtom(isDeviceLargerThanLgAtom);
  32. useEffect(() => {
  33. const lgOrAboveHandler = function (this: MediaQueryList): void {
  34. // md -> lg: matches will be true
  35. // lg -> md: matches will be false
  36. setIsLargerThanLg(this.matches);
  37. };
  38. const mql = addBreakpointListener(Breakpoint.LG, lgOrAboveHandler);
  39. // initialize
  40. setIsLargerThanLg(mql.matches);
  41. return () => {
  42. cleanupBreakpointListener(mql, lgOrAboveHandler);
  43. };
  44. }, [setIsLargerThanLg]);
  45. return [isLargerThanLg, setIsLargerThanLg] as const;
  46. };
  47. export const useDeviceLargerThanMd = () => {
  48. const [isLargerThanMd, setIsLargerThanMd] = useAtom(isDeviceLargerThanMdAtom);
  49. useEffect(() => {
  50. const mdOrAboveHandler = function (this: MediaQueryList): void {
  51. // sm -> md: matches will be true
  52. // md -> sm: matches will be false
  53. setIsLargerThanMd(this.matches);
  54. };
  55. const mql = addBreakpointListener(Breakpoint.MD, mdOrAboveHandler);
  56. // initialize
  57. setIsLargerThanMd(mql.matches);
  58. return () => {
  59. cleanupBreakpointListener(mql, mdOrAboveHandler);
  60. };
  61. }, [setIsLargerThanMd]);
  62. return [isLargerThanMd, setIsLargerThanMd] as const;
  63. };
  64. export const useIsMobile = () => {
  65. const [isMobile, setIsMobile] = useAtom(isMobileAtom);
  66. useEffect(() => {
  67. // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#mobile_device_detection
  68. let hasTouchScreen = false;
  69. hasTouchScreen =
  70. 'maxTouchPoints' in navigator ? navigator?.maxTouchPoints > 0 : false;
  71. if (!hasTouchScreen) {
  72. const mQ = matchMedia?.('(pointer:coarse)');
  73. if (mQ?.media === '(pointer:coarse)') {
  74. hasTouchScreen = !!mQ.matches;
  75. } else {
  76. // Only as a last resort, fall back to user agent sniffing
  77. const UA = navigator.userAgent;
  78. hasTouchScreen =
  79. /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
  80. /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA);
  81. }
  82. }
  83. // Initialize with detected value
  84. setIsMobile(hasTouchScreen);
  85. }, [setIsMobile]);
  86. return [isMobile, setIsMobile] as const;
  87. };