Skip to content

Commit 2a955af

Browse files
committed
refactor(MediaConsumer): update API to use eq function for media queries and add debounce support
1 parent a7fe318 commit 2a955af

File tree

4 files changed

+139
-92
lines changed

4 files changed

+139
-92
lines changed

example/src/components/responsive/Media.stories.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {GridThemeProvider, MediaConsumer} from '@acrool/react-grid';
2+
import type {Meta, StoryObj} from '@storybook/react';
3+
import React from 'react';
4+
5+
const meta = {
6+
title: 'Responsive/MediaConsumer',
7+
parameters: {
8+
docs: {
9+
description: {
10+
component: 'Media Query 觀察者模式,提供特定尺寸JS狀態更新 size'
11+
},
12+
},
13+
},
14+
tags: ['autodocs'],
15+
decorators: (Story) => (
16+
<GridThemeProvider>
17+
<Story />
18+
</GridThemeProvider>
19+
),
20+
args: {},
21+
} satisfies Meta<typeof MediaConsumer>;
22+
23+
export default meta;
24+
type Story = StoryObj<typeof meta>;
25+
26+
27+
28+
export const Media: Story = {
29+
args: {},
30+
render: function Render(args) {
31+
return <MediaConsumer>
32+
{eq => {
33+
if(eq('xxl')){
34+
return <div className="bg-color-8 font-color-1">
35+
XXL
36+
</div>;
37+
}
38+
if(eq('xl')){
39+
return <div className="bg-color-7 font-color-1">
40+
XL
41+
</div>;
42+
}
43+
if(eq('lg')){
44+
return <div className="bg-color-6 font-color-1">
45+
LG
46+
</div>;
47+
}
48+
if(eq('md')){
49+
return <div className="bg-color-5">
50+
MD
51+
</div>;
52+
}
53+
54+
if(eq('sm')){
55+
return <div className="bg-color-4">
56+
SM
57+
</div>;
58+
}
59+
60+
61+
62+
63+
return <div className="bg-color-2">
64+
XS
65+
</div>;
66+
}}
67+
</MediaConsumer>;
68+
},
69+
};
Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {JSX} from 'react';
22

3-
import {NoXsMediaSize, TMediaSize} from '../types';
3+
import {NoXsMediaSize} from '../types';
44
import useMedia from './useMedia';
55

66

77
interface IProps {
8-
children: (mediaSize: TMediaSize) => JSX.Element|null
9-
sizes?: NoXsMediaSize[]
8+
children: (eq: (size: NoXsMediaSize) => boolean) => JSX.Element|null
9+
debounceDelay?: number
1010
}
1111

1212
/**
@@ -16,14 +16,11 @@ interface IProps {
1616
*/
1717
export const MediaConsumer = ({
1818
children,
19-
sizes
19+
debounceDelay,
2020
}: IProps) => {
21-
const currentSize = useMedia(sizes);
21+
const {eq} = useMedia(debounceDelay);
2222

23-
if(!currentSize){
24-
return null;
25-
}
26-
return children(currentSize);
23+
return children(eq);
2724
};
2825

2926
export default MediaConsumer;

src/MediaConsumer/useMedia.ts

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,82 @@
1-
import {useEffect, useState} from 'react';
1+
import {useCallback, useEffect, useRef,useState} from 'react';
22
import {useTheme} from 'styled-components';
33

44
import {themeName} from '../config';
5-
import {NoXsMediaSize, TMediaSize} from '../types';
5+
import {TMediaSize} from '../types';
66

77
/**
8-
* 取得目前尺寸
9-
* @param sizes 要監聽的尺寸陣列,如果未提供則監聽所有尺寸
8+
* 防抖 hook
9+
* @param callback 要執行的回調函數
10+
* @param delay 延遲時間(毫秒)
1011
*/
11-
const useMedia = (sizes?: NoXsMediaSize[]) => {
12+
const useDebounce = (callback: () => void, delay: number) => {
13+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
14+
15+
return useCallback(() => {
16+
if (timeoutRef.current) {
17+
clearTimeout(timeoutRef.current);
18+
}
19+
timeoutRef.current = setTimeout(callback, delay);
20+
}, [callback, delay]);
21+
};
22+
23+
/**
24+
* 媒體查詢 hook
25+
* @param debounceDelay 防抖延遲時間(毫秒),預設為 150ms
26+
* @returns 包含 isIn(size) 方法的物件
27+
*/
28+
const useMedia = (debounceDelay: number = 150) => {
1229
const theme = useTheme();
1330
const breakpoints = theme[themeName]?.gridBreakpoints;
31+
const [windowWidth, setWindowWidth] = useState<number>(window.innerWidth ?? 0);
1432

15-
const getResize = () => {
16-
if(typeof window === 'undefined'){
17-
return null;
18-
}
19-
const width = window.innerWidth;
20-
let size: TMediaSize = 'xs';
21-
22-
if ((!sizes || sizes?.includes('xxl')) && width >= breakpoints.xxl) {
23-
size = 'xxl';
24-
} else if ((!sizes || sizes?.includes('xl')) && width >= breakpoints.xl) {
25-
size = 'xl';
26-
} else if ((!sizes || sizes?.includes('lg')) && width >= breakpoints.lg) {
27-
size = 'lg';
28-
} else if ((!sizes || sizes?.includes('md')) && width >= breakpoints.md) {
29-
size = 'md';
30-
} else if ((!sizes || sizes?.includes('sm')) && width >= breakpoints.sm) {
31-
size = 'sm';
32-
}
33+
// 初始化窗口寬度
34+
useEffect(() => {
35+
handleResize();
36+
}, []);
3337

34-
return size;
35-
};
38+
const handleResize = useCallback(() => {
39+
if (typeof window !== 'undefined') {
40+
setWindowWidth(window.innerWidth);
41+
}
42+
}, []);
3643

37-
const [currentSize, setCurrentSize] = useState<TMediaSize|null>(getResize());
44+
const debouncedHandleResize = useDebounce(handleResize, debounceDelay);
3845

3946
useEffect(() => {
40-
const handleResize = () => {
41-
const size = getResize();
42-
setCurrentSize(size);
43-
};
47+
if (typeof window === 'undefined') return;
48+
49+
window.addEventListener('resize', debouncedHandleResize);
50+
return () => window.removeEventListener('resize', debouncedHandleResize);
51+
}, [debouncedHandleResize]);
4452

45-
window.addEventListener('resize', handleResize);
46-
return () => window.removeEventListener('resize', handleResize);
47-
}, [breakpoints, sizes]);
53+
/**
54+
* 檢查當前尺寸是否在指定斷點範圍內
55+
* @param size 要檢查的斷點尺寸
56+
* @returns 如果當前尺寸符合斷點範圍則回傳 true
57+
*/
58+
const eq = useCallback((size: TMediaSize): boolean => {
59+
if (!breakpoints || windowWidth === 0) return false;
60+
61+
const width = windowWidth;
62+
63+
switch (size) {
64+
case 'sm':
65+
return width >= breakpoints.sm;
66+
case 'md':
67+
return width >= breakpoints.md;
68+
case 'lg':
69+
return width >= breakpoints.lg;
70+
case 'xl':
71+
return width >= breakpoints.xl;
72+
case 'xxl':
73+
return width >= breakpoints.xxl;
74+
default:
75+
return true;
76+
}
77+
}, [breakpoints, windowWidth]);
4878

49-
return currentSize;
79+
return {eq};
5080
};
5181

5282
export default useMedia;

0 commit comments

Comments
 (0)