Repository: zeit-ui/react
Pull request #200 (2020-05-12 16:37:33)

feat: add hooks for media query
## PR Checklist

- [x] Fix linting errors
- [x] Label has been added


## Change information

reoslved #186 



Patch:
diff --git a/components/utils-shared/index.ts b/components/utils-shared/index.ts
index 6c50528..0fe42ee 100644
--- a/components/utils-shared/index.ts
+++ b/components/utils-shared/index.ts
@@ -2,10 +2,12 @@ import { default as useBodyScroll } from './use-body-scroll'
 import { default as useClipboard } from './use-clipboard'
 import { default as useCurrentState } from './use-current-state'
 import { default as useClickAway } from './use-click-away'
+import { default as useMediaQuery } from './use-media-query'
 
 export default {
   useBodyScroll,
   useClipboard,
   useCurrentState,
   useClickAway,
+  useMediaQuery,
 }
diff --git a/components/utils-shared/use-media-query.ts b/components/utils-shared/use-media-query.ts
new file mode 100644
index 0000000..5a20a9b
--- /dev/null
+++ b/components/utils-shared/use-media-query.ts
@@ -0,0 +1,92 @@
+import { useEffect, useMemo, useState } from 'react'
+import useTheme from '../styles/use-theme'
+import { tuple } from '../utils/prop-types'
+import { BreakpointsItem, ZeitUIThemesBreakpoints } from '../styles/themes'
+
+const breakpoints = tuple('xs', 'sm', 'md', 'lg', 'xl', 'mobile')
+export type ResponsiveBreakpoint = typeof breakpoints[number]
+
+const matchType = tuple('up', 'down', 'default')
+export type ResponsiveMatchType = typeof matchType[number]
+export type ResponsiveOptions = {
+  match?: ResponsiveMatchType
+  ssrMatchMedia?: (query: string) => { matches: boolean }
+}
+
+const defaultResponsiveOptions = {
+  match: 'default' as ResponsiveMatchType,
+}
+
+const makeQueries = (
+  bp: ZeitUIThemesBreakpoints,
+  up: boolean,
+  down: boolean,
+): {
+  [key in ResponsiveBreakpoint]: string
+} => {
+  const queryString = (item: BreakpointsItem) => {
+    const upQuery = `(min-width: ${item.min})`
+    const downQuery = `(max-width: ${item.max})`
+    return up ? upQuery : down ? downQuery : `${upQuery} and ${downQuery}`
+  }
+  const xs = queryString(bp.xs)
+  return {
+    xs: xs,
+    mobile: xs,
+    sm: queryString(bp.sm),
+    md: queryString(bp.md),
+    lg: queryString(bp.lg),
+    xl: queryString(bp.xl),
+  }
+}
+
+const useMediaQuery = (
+  breakpoint: ResponsiveBreakpoint,
+  options: ResponsiveOptions = defaultResponsiveOptions,
+) => {
+  const { match: matchType = 'default', ssrMatchMedia = null } = options
+  const supportMedia = typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined'
+
+  const theme = useTheme()
+  const mediaQueries: {
+    [key in ResponsiveBreakpoint]: string
+  } = useMemo(() => {
+    const up = matchType === 'up'
+    const down = matchType === 'down'
+    return makeQueries(theme.breakpoints, up, down)
+  }, [theme.breakpoints, options])
+  const query = useMemo(() => mediaQueries[breakpoint], [mediaQueries, breakpoint])
+  const matchQuery = (q: string) => window.matchMedia(q)
+
+  /**
+   * Do nothing in the server-side rendering.
+   * If server match query fucntion is simulated, return user-defined value first.
+   */
+  const [state, setState] = useState<boolean>(() => {
+    if (supportMedia) return matchQuery(query).matches
+    if (ssrMatchMedia && typeof ssrMatchMedia === 'function') {
+      return ssrMatchMedia(query).matches
+    }
+    return false
+  })
+
+  useEffect(() => {
+    if (!supportMedia) return
+    const queryList = matchQuery(query)
+    const update = () => setState(matchQuery(query).matches)
+    update()
+
+    /**
+     * addListener is deprecated. EventTarget.addEventListener is recommended.
+     * But in some old browsers, MediaQueryList does not inherit from EventTarget.
+     */
+    queryList.addListener(update)
+    return () => {
+      queryList.removeListener(update)
+    }
+  }, [supportMedia])
+
+  return state
+}
+
+export default useMediaQuery
diff --git a/lib/data/metadata-en-us.json b/lib/data/metadata-en-us.json
index eafd5aa..ded3353 100644
--- a/lib/data/metadata-en-us.json
+++ b/lib/data/metadata-en-us.json
@@ -1,1 +1,1 @@
-[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}]
+[{"name":"guide","children":[{"name":"getting-started","children":[{"name":"introduction","url":"/en-us/guide/introduction","index":5,"group":"getting-started"},{"name":"installation","url":"/en-us/guide/installation","index":10,"group":"getting-started"},{"name":"Server Render","url":"/en-us/guide/server-render","index":15,"group":"getting-started"}]},{"name":"customization","children":[{"name":"Colors","url":"/en-us/guide/colors","index":100,"group":"customization"},{"name":"Themes","url":"/en-us/guide/themes","index":100,"group":"customization"}]}]},{"name":"components","children":[{"name":"General","children":[{"name":"text","url":"/en-us/components/text","index":10,"group":"General"},{"name":"button","url":"/en-us/components/button","index":100,"group":"General"},{"name":"Code","url":"/en-us/components/code","index":100,"group":"General"},{"name":"Icons","url":"/en-us/components/icons","index":100,"group":"General"}]},{"name":"layout","children":[{"name":"Grid","url":"/en-us/components/grid","index":100,"group":"layout"},{"name":"layout","url":"/en-us/components/layout","index":100,"group":"layout"},{"name":"Page","url":"/en-us/components/page","index":100,"group":"layout"},{"name":"Spacer","url":"/en-us/components/spacer","index":100,"group":"layout"}]},{"name":"Surfaces","children":[{"name":"card","url":"/en-us/components/card","index":100,"group":"Surfaces"},{"name":"collapse","url":"/en-us/components/collapse","index":100,"group":"Surfaces"},{"name":"fieldset","url":"/en-us/components/fieldset","index":100,"group":"Surfaces"}]},{"name":"Data Entry","children":[{"name":"Auto-Complete","url":"/en-us/components/auto-complete","index":100,"group":"Data Entry"},{"name":"Button-Group","url":"/en-us/components/button-group","index":100,"group":"Data Entry"},{"name":"checkbox","url":"/en-us/components/checkbox","index":100,"group":"Data Entry"},{"name":"Input","url":"/en-us/components/input","index":100,"group":"Data Entry"},{"name":"radio","url":"/en-us/components/radio","index":100,"group":"Data Entry"},{"name":"select","url":"/en-us/components/select","index":100,"group":"Data Entry"},{"name":"Slider","url":"/en-us/components/slider","index":100,"group":"Data Entry"},{"name":"textarea","url":"/en-us/components/textarea","index":100,"group":"Data Entry"},{"name":"Toggle","url":"/en-us/components/toggle","index":100,"group":"Data Entry"}]},{"name":"Data Display","children":[{"name":"avatar","url":"/en-us/components/avatar","index":100,"group":"Data Display"},{"name":"Badge","url":"/en-us/components/badge","index":100,"group":"Data Display"},{"name":"Capacity","url":"/en-us/components/capacity","index":100,"group":"Data Display"},{"name":"Description","url":"/en-us/components/description","index":100,"group":"Data Display"},{"name":"Display","url":"/en-us/components/display","index":100,"group":"Data Display"},{"name":"Dot","url":"/en-us/components/dot","index":100,"group":"Data Display"},{"name":"File-Tree","url":"/en-us/components/file-tree","index":100,"group":"Data Display"},{"name":"Image","url":"/en-us/components/image","index":100,"group":"Data Display"},{"name":"keyboard","url":"/en-us/components/keyboard","index":100,"group":"Data Display"},{"name":"Popover","url":"/en-us/components/popover","index":100,"group":"Data Display"},{"name":"Table","url":"/en-us/components/table","index":100,"group":"Data Display"},{"name":"Tag","url":"/en-us/components/tag","index":100,"group":"Data Display"},{"name":"Tooltip","url":"/en-us/components/tooltip","index":100,"group":"Data Display"},{"name":"User","url":"/en-us/components/user","index":100,"group":"Data Display"}]},{"name":"Feedback","children":[{"name":"Loading","url":"/en-us/components/loading","index":100,"group":"Feedback"},{"name":"modal","url":"/en-us/components/modal","index":100,"group":"Feedback"},{"name":"note","url":"/en-us/components/note","index":100,"group":"Feedback"},{"name":"Progress","url":"/en-us/components/progress","index":100,"group":"Feedback"},{"name":"Spinner","url":"/en-us/components/spinner","index":100,"group":"Feedback"},{"name":"toast","url":"/en-us/components/toast","index":100,"group":"Feedback"}]},{"name":"Navigation","children":[{"name":"link","url":"/en-us/components/link","index":100,"group":"Navigation"},{"name":"tabs","url":"/en-us/components/tabs","index":100,"group":"Navigation"},{"name":"button-dropdown","url":"/en-us/components/button-dropdown","index":101,"group":"Navigation"}]},{"name":"Others","children":[{"name":"Divider","url":"/en-us/components/divider","index":100,"group":"Others"},{"name":"Snippet","url":"/en-us/components/snippet","index":100,"group":"Others"}]},{"name":"Utils","children":[{"name":"use-body-scroll","url":"/en-us/components/use-body-scroll","index":100,"group":"Utils"},{"name":"use-click-away","url":"/en-us/components/use-click-away","index":100,"group":"Utils"},{"name":"use-clipboard","url":"/en-us/components/use-clipboard","index":100,"group":"Utils"},{"name":"use-current-state","url":"/en-us/components/use-current-state","index":100,"group":"Utils"},{"name":"use-media-query","url":"/en-us/components/use-media-query","index":100,"group":"Utils"}]}]},{"name":"customization","children":[]}]
diff --git a/lib/data/metadata-zh-cn.json b/lib/data/metadata-zh-cn.json
index a2be0e6..124e1e1 100644
--- a/lib/data/metadata-zh-cn.json
+++ b/lib/data/metadata-zh-cn.json
@@ -1,1 +1,1 @@
-[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}]
+[{"name":"guide","children":[{"name":"快速上手","children":[{"name":"什么是 ZEIT UI","url":"/zh-cn/guide/introduction","index":5,"group":"快速上手"},{"name":"安装","url":"/zh-cn/guide/installation","index":10,"group":"快速上手"},{"name":"服务端渲染","url":"/zh-cn/guide/server-render","index":15,"group":"快速上手"}]},{"name":"定制化","children":[{"name":"色彩","url":"/zh-cn/guide/colors","index":100,"group":"定制化"},{"name":"主题","url":"/zh-cn/guide/themes","index":100,"group":"定制化"}]}],"localeName":"上手指南"},{"name":"components","children":[{"name":"通用","children":[{"name":"文本 Text","url":"/zh-cn/components/text","index":10,"group":"通用"},{"name":"按钮 Button","url":"/zh-cn/components/button","index":100,"group":"通用"},{"name":"代码 Code","url":"/zh-cn/components/code","index":100,"group":"通用"},{"name":"图标 Icons","url":"/zh-cn/components/icons","index":100,"group":"通用"}]},{"name":"布局","children":[{"name":"栅格 Grid","url":"/zh-cn/components/grid","index":100,"group":"布局"},{"name":"布局 Layout","url":"/zh-cn/components/layout","index":100,"group":"布局"},{"name":"页面 Page","url":"/zh-cn/components/page","index":100,"group":"布局"},{"name":"间距 Spacer","url":"/zh-cn/components/spacer","index":100,"group":"布局"}]},{"name":"表面","children":[{"name":"卡片 Card","url":"/zh-cn/components/card","index":100,"group":"表面"},{"name":"折叠框 Collapse","url":"/zh-cn/components/collapse","index":100,"group":"表面"},{"name":"控件组 Fieldset","url":"/zh-cn/components/fieldset","index":100,"group":"表面"}]},{"name":"数据录入","children":[{"name":"按钮组 Button-Group","url":"/zh-cn/components/button-group","index":100,"group":"数据录入"},{"name":"复选框 Checkbox","url":"/zh-cn/components/checkbox","index":100,"group":"数据录入"},{"name":"输入框 Input","url":"/zh-cn/components/input","index":100,"group":"数据录入"},{"name":"单选框 Radio","url":"/zh-cn/components/radio","index":100,"group":"数据录入"},{"name":"选择器 Select","url":"/zh-cn/components/select","index":100,"group":"数据录入"},{"name":"滑动输入 Slider","url":"/zh-cn/components/slider","index":100,"group":"数据录入"},{"name":"文本输入框 Textarea","url":"/zh-cn/components/textarea","index":100,"group":"数据录入"},{"name":"开关 Toggle","url":"/zh-cn/components/toggle","index":100,"group":"数据录入"},{"name":"自动完成 Auto-Complete","url":"/zh-cn/components/auto-complete","index":104,"group":"数据录入"}]},{"name":"数据展示","children":[{"name":"头像 Avatar","url":"/zh-cn/components/avatar","index":100,"group":"数据展示"},{"name":"徽章 Badge","url":"/zh-cn/components/badge","index":100,"group":"数据展示"},{"name":"容量 Capacity","url":"/zh-cn/components/capacity","index":100,"group":"数据展示"},{"name":"描述 Description","url":"/zh-cn/components/description","index":100,"group":"数据展示"},{"name":"陈列框 Display","url":"/zh-cn/components/display","index":100,"group":"数据展示"},{"name":"点 Dot","url":"/zh-cn/components/dot","index":100,"group":"数据展示"},{"name":"文件树 File Tree","url":"/zh-cn/components/file-tree","index":100,"group":"数据展示"},{"name":"图片 Image","url":"/zh-cn/components/image","index":100,"group":"数据展示"},{"name":"键盘 keyboard","url":"/zh-cn/components/keyboard","index":100,"group":"数据展示"},{"name":"气泡卡片 Popover","url":"/zh-cn/components/popover","index":100,"group":"数据展示"},{"name":"表格 Table","url":"/zh-cn/components/table","index":100,"group":"数据展示"},{"name":"标签 Tag","url":"/zh-cn/components/tag","index":100,"group":"数据展示"},{"name":"文字提示 Tooltip","url":"/zh-cn/components/tooltip","index":100,"group":"数据展示"},{"name":"用户 User","url":"/zh-cn/components/user","index":100,"group":"数据展示"}]},{"name":"反馈","children":[{"name":"加载中 Loading","url":"/zh-cn/components/loading","index":100,"group":"反馈"},{"name":"对话框 Modal","url":"/zh-cn/components/modal","index":100,"group":"反馈"},{"name":"提示 Note","url":"/zh-cn/components/note","index":100,"group":"反馈"},{"name":"进度条 Progress","url":"/zh-cn/components/progress","index":100,"group":"反馈"},{"name":"指示器 Spinner","url":"/zh-cn/components/spinner","index":100,"group":"反馈"},{"name":"通知 Toast","url":"/zh-cn/components/toast","index":100,"group":"反馈"}]},{"name":"导航","children":[{"name":"链接 Link","url":"/zh-cn/components/link","index":100,"group":"导航"},{"name":"选项卡 Tabs","url":"/zh-cn/components/tabs","index":100,"group":"导航"},{"name":"下拉按钮 Btn Dropdown","url":"/zh-cn/components/button-dropdown","index":105,"group":"导航"}]},{"name":"其他","children":[{"name":"分割线 Divider","url":"/zh-cn/components/divider","index":100,"group":"其他"},{"name":"片段 Snippet","url":"/zh-cn/components/snippet","index":100,"group":"其他"}]},{"name":"工具包","children":[{"name":"锁定滚动 useBodyScroll","url":"/zh-cn/components/use-body-scroll","index":100,"group":"工具包"},{"name":"点击他处 useClickAway","url":"/zh-cn/components/use-click-away","index":100,"group":"工具包"},{"name":"剪切板 useClipboard","url":"/zh-cn/components/use-clipboard","index":100,"group":"工具包"},{"name":" 当前值 useCurrentState","url":"/zh-cn/components/use-current-state","index":100,"group":"工具包"},{"name":"媒体查询 useMediaQuery","url":"/zh-cn/components/use-media-query","index":100,"group":"工具包"}]}],"localeName":"所有组件"},{"name":"customization","children":[],"localeName":"定制化"}]
diff --git a/package.json b/package.json
index 73988fe..7472152 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
     "@zeit-ui/react-icons": "^1.2.2",
     "babel-jest": "^25.3.0",
     "babel-loader": "^8.0.6",
+    "css-mediaquery": "^0.1.2",
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.2",
     "eslint": "^6.8.0",
diff --git a/pages/en-us/components/use-media-query.mdx b/pages/en-us/components/use-media-query.mdx
new file mode 100644
index 0000000..6b3577c
--- /dev/null
+++ b/pages/en-us/components/use-media-query.mdx
@@ -0,0 +1,100 @@
+import { Layout, Playground, Attributes } from 'lib/components'
+import { Utils, Link, Code, Spacer } from 'components'
+import NextLink from 'next/link'
+import PlaygroundTitle from 'lib/components/playground/title'
+
+export const meta = {
+  title: 'use-media-query',
+  group: 'Utils',
+}
+
+## Use MediaQuery
+
+CSS media query hooks, it is implemented through the `MediaQuery` API.
+If you just want to build the layout, try <NextLink href="/en-us/components/grid"><Link color>Grid Component</Link></NextLink>.
+
+This is custom React Hooks, you need to follow the <Link target="_blank" color href="https://reactjs.org/docs/hooks-rules.html">Basic Rules</Link> when you use it.
+
+<Spacer y={1.5} />
+
+### Import
+
+```jsx
+import { Utils } from '@zeit-ui/react'
+const { useMediaQuery } = Utils
+```
+
+<Playground
+  scope={{ Utils, Code }}
+  code={`
+() => {
+  const { useMediaQuery } = Utils
+  const isXS = useMediaQuery('xs')
+  const isSM = useMediaQuery('sm')
+  const isMD = useMediaQuery('md')
+  const isLG = useMediaQuery('lg')
+  const isXL = useMediaQuery('xl')
+  return (
+    <>
+      MediaQuery match: <Code>{isXS && 'xs'}{isSM && 'sm'}{isMD && 'md'}{isLG && 'lg'}{isXL && 'xl'}</Code>.
+    </>
+  )
+}
+`} />
+
+<Playground
+  title="Match Type"
+  desc="You can choose to match up or down."
+  scope={{ Utils, Code }}
+  code={`
+() => {
+  const { useMediaQuery } = Utils
+  const upMD = useMediaQuery('md', { match: 'up' })
+  return (
+    <>
+      Current screen width <b>{upMD ? '>=' : '<='}</b> <Code>md</Code>.
+    </>
+  )
+}
+`} />
+
+
+<PlaygroundTitle title="custom breakpoints" desc="Override the default breakpoints of the `@zeit-ui/react`." />
+
+```tsx
+const breakpoints: ZeitUIThemesBreakpoints = {
+  xs: { min: '0', max: '650px' },
+  sm: { min: '650px', max: '900px' },
+  md: { min: '900px', max: '1280px' },
+  lg: { min: '1280px', max: '1920px' },
+  xl: { min: '1920px', max: '10000px' },
+}
+
+const App = () => (
+  <ZEITUIProvider theme={{ breakpoints: breakpoints }}>
+    <CSSBaseline />
+    <AppComponent />
+  </ZEITUIProvider>
+)
+```
+
+<Attributes edit="/pages/en-us/components/use-media-query.mdx">
+<Attributes.Title>useMediaQuery</Attributes.Title>
+
+```ts
+type ResponsiveBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'mobile'
+
+type ResponsiveOptions = {
+ match?: 'up'| 'down',
+ ssrMatchMedia?: (query: string) => { matches: boolean }
+}
+
+const useMediaQuery = (
+  breakpoint: ResponsiveBreakpoint,
+  options?: ResponsiveOptions,
+) => boolean
+```
+
+</Attributes>
+
+export default ({ children }) => <Layout meta={meta}>{children}</Layout>
diff --git a/pages/zh-cn/components/use-media-query.mdx b/pages/zh-cn/components/use-media-query.mdx
new file mode 100644
index 0000000..0aa2525
--- /dev/null
+++ b/pages/zh-cn/components/use-media-query.mdx
@@ -0,0 +1,99 @@
+import { Layout, Playground, Attributes } from 'lib/components'
+import { Utils, Link, Code, Spacer } from 'components'
+import NextLink from 'next/link'
+import PlaygroundTitle from 'lib/components/playground/title'
+
+export const meta = {
+  title: '媒体查询 useMediaQuery',
+  group: '工具包',
+}
+
+## Use MediaQuery / 媒体查询
+
+用于 CSS 媒体查询的钩子，钩子通过 `MediaQuery` API 实现。如果你仅仅想要构建响应式的布局，
+请尝试 <NextLink href="/en-us/components/grid"><Link color>栅格组件</Link></NextLink>。
+
+这是一个自定义的 React Hooks，你需要在使用时遵循 <Link target="_blank" color href="https://reactjs.org/docs/hooks-rules.html">钩子的基础规则</Link>。
+
+<Spacer y={1.5} />
+
+### 引入工具
+
+```jsx
+import { Utils } from '@zeit-ui/react'
+const { useMediaQuery } = Utils
+```
+
+<Playground
+  scope={{ Utils, Code }}
+  code={`
+() => {
+  const { useMediaQuery } = Utils
+  const isXS = useMediaQuery('xs')
+  const isSM = useMediaQuery('sm')
+  const isMD = useMediaQuery('md')
+  const isLG = useMediaQuery('lg')
+  const isXL = useMediaQuery('xl')
+  return (
+    <>
+      媒体查询当前匹配: <Code>{isXS && 'xs'}{isSM && 'sm'}{isMD && 'md'}{isLG && 'lg'}{isXL && 'xl'}</Code>.
+    </>
+  )
+}
+`} />
+
+<Playground
+  title="匹配类型"
+  desc="你可以选择向上或是向下匹配媒体查询。"
+  scope={{ Utils, Code }}
+  code={`
+() => {
+  const { useMediaQuery } = Utils
+  const upMD = useMediaQuery('md', { match: 'up' })
+  return (
+    <>
+      当前屏幕宽度 <b>{upMD ? '大于' : '小于'}</b> <Code>md</Code>.
+    </>
+  )
+}
+`} />
+
+<PlaygroundTitle title="定制断点" desc="您可以覆盖 `@zeit-ui/react` 组件库默认的断点值。" />
+
+```tsx
+const breakpoints: ZeitUIThemesBreakpoints = {
+  xs: { min: '0', max: '650px' },
+  sm: { min: '650px', max: '900px' },
+  md: { min: '900px', max: '1280px' },
+  lg: { min: '1280px', max: '1920px' },
+  xl: { min: '1920px', max: '10000px' },
+}
+
+const App = () => (
+  <ZEITUIProvider theme={{ breakpoints: breakpoints }}>
+    <CSSBaseline />
+    <AppComponent />
+  </ZEITUIProvider>
+)
+```
+
+<Attributes edit="/pages/zh-cn/components/use-media-query.mdx">
+<Attributes.Title>useMediaQuery</Attributes.Title>
+
+```ts
+type ResponsiveBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'mobile'
+
+type ResponsiveOptions = {
+ match?: 'up'| 'down',
+ ssrMatchMedia?: (query: string) => { matches: boolean }
+}
+
+const useMediaQuery = (
+  breakpoint: ResponsiveBreakpoint,
+  options?: ResponsiveOptions,
+) => boolean
+```
+
+</Attributes>
+
+export default ({ children }) => <Layout meta={meta}>{children}</Layout>
diff --git a/yarn.lock b/yarn.lock
index c261a6d..763d8f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3124,6 +3124,11 @@ css-loader@3.5.3:
     schema-utils "^2.6.6"
     semver "^6.3.0"
 
+css-mediaquery@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
+  integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=
+
 css-select-base-adapter@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"


Test patch:
diff --git a/components/utils-shared/__tests__/media-query.test.tsx b/components/utils-shared/__tests__/media-query.test.tsx
new file mode 100644
index 0000000..25a574d
--- /dev/null
+++ b/components/utils-shared/__tests__/media-query.test.tsx
@@ -0,0 +1,79 @@
+import { Utils } from 'components'
+import { renderHook } from '@testing-library/react-hooks'
+// @ts-ignore
+import mediaQuery from 'css-mediaquery'
+const { useMediaQuery } = Utils
+
+const mediaListMock = (width: number) => {
+  ;(window as any).listeners = [] as Array<Function>
+  return (query: string) => {
+    return {
+      matches: mediaQuery.match(query, { width }),
+      addListener: (fn: Function) => (window as any).listeners.push(fn),
+      removeListener: () => {},
+    }
+  }
+}
+
+describe('UseMediaQuery with SSR', () => {
+  beforeAll(() => {
+    ;(window as any).matchMedia = undefined
+  })
+
+  it('should work correctly', () => {
+    const { result } = renderHook(() => useMediaQuery('xs'))
+    expect(result.current).toEqual(false)
+  })
+
+  it('should work correctly with ssr match', () => {
+    const ssrMatchMedia = (query: string) => ({
+      matches: mediaQuery.match(query, {
+        width: '100px',
+      }),
+    })
+    const { result } = renderHook(() =>
+      useMediaQuery('xs', {
+        ssrMatchMedia,
+      }),
+    )
+    expect(result.current).toEqual(true)
+
+    const { result: result2 } = renderHook(() =>
+      useMediaQuery('sm', {
+        ssrMatchMedia,
+      }),
+    )
+    expect(result2.current).toEqual(false)
+  })
+})
+
+describe('UseMediaQuery', () => {
+  beforeAll(() => {
+    ;(window as any).matchMedia = mediaListMock(1500)
+  })
+
+  it('should work correctly', () => {
+    const { result } = renderHook(() => useMediaQuery('xs'))
+    expect(result.current).toEqual(false)
+  })
+
+  it('should hit', () => {
+    const { result } = renderHook(() => useMediaQuery('lg'))
+    expect(result.current).toEqual(true)
+  })
+
+  it('different match types should be supported', () => {
+    const { result } = renderHook(() =>
+      useMediaQuery('md', {
+        match: 'up',
+      }),
+    )
+    expect(result.current).toEqual(true)
+    const { result: result2 } = renderHook(() =>
+      useMediaQuery('md', {
+        match: 'down',
+      }),
+    )
+    expect(result2.current).toEqual(false)
+  })
+})