match-breakpoint 1.0.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/src/components/Breakpoint/Breakpoint.d.ts +17 -0
- package/dist/src/components/Breakpoint/Breakpoint.js +103 -0
- package/dist/src/components/Breakpoint/BreakpointChild.d.ts +15 -0
- package/dist/src/components/Breakpoint/BreakpointChild.js +155 -0
- package/dist/src/components/Breakpoint/index.d.ts +1 -0
- package/dist/src/components/Breakpoint/index.js +1 -0
- package/dist/src/components/BreakpointsProvider/BreakpointProvider.d.ts +16 -0
- package/dist/src/components/BreakpointsProvider/BreakpointProvider.js +34 -0
- package/dist/src/components/BreakpointsProvider/BreakpointsProvider.d.ts +20 -0
- package/dist/src/components/BreakpointsProvider/BreakpointsProvider.js +95 -0
- package/dist/src/components/BreakpointsProvider/index.d.ts +1 -0
- package/dist/src/components/BreakpointsProvider/index.js +1 -0
- package/dist/src/utils/screen-width.d.ts +3 -0
- package/dist/src/utils/screen-width.js +12 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Oleg Putseiko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# match-breakpoint
|
|
4
|
+
|
|
5
|
+
[](https://github.com/oleg-putseiko/match-breakpoint/releases)
|
|
6
|
+
[](https://www.npmjs.com/package/match-breakpoint)
|
|
7
|
+
[](https://www.npmjs.com/package/match-breakpoint)
|
|
8
|
+
[](https://github.com/oleg-putseiko/match-breakpoint/blob/main/LICENSE.md)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
A library of optimized React components and hooks for matching screen widths.
|
|
13
|
+
|
|
14
|
+
# Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add match-breakpoint@latest
|
|
18
|
+
|
|
19
|
+
// or
|
|
20
|
+
|
|
21
|
+
npm install match-breakpoint@latest
|
|
22
|
+
|
|
23
|
+
// or
|
|
24
|
+
|
|
25
|
+
pnpm install match-breakpoint@latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
# Advantages
|
|
29
|
+
|
|
30
|
+
❇️ ⚡️ The library's components and hooks use a finite and predefined number of media match listeners, which reduces the computational load.
|
|
31
|
+
|
|
32
|
+
❇️ ✨ Using `Breakpoint` components allows you to avoid cumulative layout shift.
|
|
33
|
+
|
|
34
|
+
❇️ 💫 Conditional content display is compatible with most CSS frameworks, preprocessors, etc. In particular, it's possible to use the Tailwind CSS class name preset out of the box.
|
|
35
|
+
|
|
36
|
+
❇️ 🛡️ The library supports breakpoint typing used in all components and hooks from one place.
|
|
37
|
+
|
|
38
|
+
# Getting started
|
|
39
|
+
|
|
40
|
+
First of all, you need to add a breakpoints provider to your application:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { BreakpointsProvider } from 'match-breakpoint';
|
|
44
|
+
|
|
45
|
+
const App: FC = () => (
|
|
46
|
+
<BreakpointsProvider breakpoints={/* … */}>
|
|
47
|
+
{/* … */}
|
|
48
|
+
{/* … */}
|
|
49
|
+
</BreakpointsProvider>
|
|
50
|
+
);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Next you need to define the breakpoints that will be used in the application:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const BREAKPOINTS = {
|
|
57
|
+
// …
|
|
58
|
+
md: '768px',
|
|
59
|
+
lg: '1024px',
|
|
60
|
+
xl: '1280px',
|
|
61
|
+
// …
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const App: FC = () => (
|
|
65
|
+
<BreakpointsProvider breakpoints={BREAKPOINTS}>
|
|
66
|
+
{/* … */}
|
|
67
|
+
{/* … */}
|
|
68
|
+
</BreakpointsProvider>
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also pass a value from some configuration as breakpoints, for example from the Tailwind CSS configuration:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import tailwindConfig from '../../tailwind.config.ts';
|
|
76
|
+
|
|
77
|
+
const DEFAULT_BREAKPOINTS = {
|
|
78
|
+
// …
|
|
79
|
+
md: '768px',
|
|
80
|
+
lg: '1024px',
|
|
81
|
+
xl: '1280px',
|
|
82
|
+
// …
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const breakpoints = tailwindConfig.theme?.screens ?? DEFAULT_BREAKPOINTS;
|
|
86
|
+
|
|
87
|
+
const App: FC = () => (
|
|
88
|
+
<BreakpointsProvider breakpoints={breakpoints}>
|
|
89
|
+
{/* … */}
|
|
90
|
+
{/* … */}
|
|
91
|
+
</BreakpointsProvider>
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
After that you can use the `Breakpoint` component, for example:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Breakpoint } from 'match-breakpoint';
|
|
99
|
+
|
|
100
|
+
const Content: FC = () => (
|
|
101
|
+
<>
|
|
102
|
+
<Breakpoint size="md">Desktop Device Content</Breakpoint>
|
|
103
|
+
|
|
104
|
+
<Breakpoint size="md" matchTo="max">
|
|
105
|
+
Mobile Device Content
|
|
106
|
+
</Breakpoint>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or using the `useBreakpoint` hook:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { useBreakpoint } from 'match-breakpoint';
|
|
115
|
+
|
|
116
|
+
const Content: FC = () => {
|
|
117
|
+
const isScreenMinMd = useBreakpoint('md');
|
|
118
|
+
|
|
119
|
+
if (isScreenMinMd) {
|
|
120
|
+
return 'Desktop Device Content';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return 'Mobile Device Content';
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
# Components
|
|
128
|
+
|
|
129
|
+
## BreakpointsProvider
|
|
130
|
+
|
|
131
|
+
Defined breakpoint contexts.
|
|
132
|
+
|
|
133
|
+
### Usage:
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
const BREAKPOINTS = {
|
|
139
|
+
md: 768, // Same as '768px'
|
|
140
|
+
lg: '1024px',
|
|
141
|
+
xl: {
|
|
142
|
+
min: '1280px',
|
|
143
|
+
minClassName: 'max-xl:hidden',
|
|
144
|
+
},
|
|
145
|
+
combined: {
|
|
146
|
+
max: '768px',
|
|
147
|
+
maxClassName: 'md:hidden',
|
|
148
|
+
|
|
149
|
+
min: '1280px',
|
|
150
|
+
minClassName: 'max-xl:hidden',
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const App: FC = () => (
|
|
155
|
+
<BreakpointsProvider breakpoints={BREAKPOINTS}>
|
|
156
|
+
{/* … */}
|
|
157
|
+
{/* … */}
|
|
158
|
+
</BreakpointsProvider>
|
|
159
|
+
);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Properties:
|
|
163
|
+
|
|
164
|
+
| Property | Type | Default value | Description |
|
|
165
|
+
| -------------------- | ---------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
166
|
+
| breakpoints | `Record<string, string \| number \| BreakpointData>` | - | Defines the set of breakpoints used in the application. |
|
|
167
|
+
| classNamePreset | `'tailwind' \| undefined` | `undefined` | Defines a preset of class names used to hide elements. |
|
|
168
|
+
| mergeClassesFunction | `MergeClassesFunction \| undefined` | `(...classes) => classes.filter(Boolean).join(' ')` | Defines a function for merging classes when adding them to elements. For example, you can pass a function to prevent class name conflicts. |
|
|
169
|
+
| children | `ReactNode` | - | Required child elements. |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ElementType, type ReactNode } from 'react';
|
|
2
|
+
import { type Breakpoints, type MatchTo, type ScreenSize } from '../../types/breakpoints';
|
|
3
|
+
import { type ExtractRef } from '../../types/react';
|
|
4
|
+
type BreakpointControlledProps<T extends ElementType> = {
|
|
5
|
+
as?: T;
|
|
6
|
+
size: ScreenSize;
|
|
7
|
+
breakpoints?: Breakpoints;
|
|
8
|
+
matchTo?: MatchTo;
|
|
9
|
+
isDefaultMatches?: boolean;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
type BreakpointComponentNativeProps<T extends ElementType> = Omit<ComponentPropsWithoutRef<T>, keyof BreakpointControlledProps<T>>;
|
|
13
|
+
type BreakpointProps<T extends ElementType> = BreakpointComponentNativeProps<T> & BreakpointControlledProps<T>;
|
|
14
|
+
export declare const Breakpoint: <T extends ElementType = import("react").ExoticComponent<{
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}>>(props: BreakpointProps<T>, ref: ExtractRef<BreakpointComponentNativeProps<T>>) => import("react/jsx-runtime").JSX.Element[] | null | undefined;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _object_spread(target) {
|
|
15
|
+
for(var i = 1; i < arguments.length; i++){
|
|
16
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
17
|
+
var ownKeys = Object.keys(source);
|
|
18
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
19
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
20
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
ownKeys.forEach(function(key) {
|
|
24
|
+
_define_property(target, key, source[key]);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
function ownKeys(object, enumerableOnly) {
|
|
30
|
+
var keys = Object.keys(object);
|
|
31
|
+
if (Object.getOwnPropertySymbols) {
|
|
32
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
33
|
+
if (enumerableOnly) {
|
|
34
|
+
symbols = symbols.filter(function(sym) {
|
|
35
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
keys.push.apply(keys, symbols);
|
|
39
|
+
}
|
|
40
|
+
return keys;
|
|
41
|
+
}
|
|
42
|
+
function _object_spread_props(target, source) {
|
|
43
|
+
source = source != null ? source : {};
|
|
44
|
+
if (Object.getOwnPropertyDescriptors) {
|
|
45
|
+
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
46
|
+
} else {
|
|
47
|
+
ownKeys(Object(source)).forEach(function(key) {
|
|
48
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return target;
|
|
52
|
+
}
|
|
53
|
+
function _object_without_properties(source, excluded) {
|
|
54
|
+
if (source == null) return {};
|
|
55
|
+
var target = _object_without_properties_loose(source, excluded);
|
|
56
|
+
var key, i;
|
|
57
|
+
if (Object.getOwnPropertySymbols) {
|
|
58
|
+
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
|
59
|
+
for(i = 0; i < sourceSymbolKeys.length; i++){
|
|
60
|
+
key = sourceSymbolKeys[i];
|
|
61
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
62
|
+
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
|
63
|
+
target[key] = source[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return target;
|
|
67
|
+
}
|
|
68
|
+
function _object_without_properties_loose(source, excluded) {
|
|
69
|
+
if (source == null) return {};
|
|
70
|
+
var target = {};
|
|
71
|
+
var sourceKeys = Object.keys(source);
|
|
72
|
+
var key, i;
|
|
73
|
+
for(i = 0; i < sourceKeys.length; i++){
|
|
74
|
+
key = sourceKeys[i];
|
|
75
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
76
|
+
target[key] = source[key];
|
|
77
|
+
}
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
81
|
+
import { Children, forwardRef, Fragment } from "react";
|
|
82
|
+
import { BreakpointChild } from "./BreakpointChild";
|
|
83
|
+
import { useMatchBreakpoint } from "../BreakpointsProvider";
|
|
84
|
+
const DEFAULT_COMPONENT = Fragment;
|
|
85
|
+
const BreakpointRenderFunction = (props, ref)=>{
|
|
86
|
+
const { as = DEFAULT_COMPONENT, size, matchTo = 'min', isDefaultMatches = true, children } = props, componentProps = _object_without_properties(props, [
|
|
87
|
+
"as",
|
|
88
|
+
"size",
|
|
89
|
+
"matchTo",
|
|
90
|
+
"isDefaultMatches",
|
|
91
|
+
"children"
|
|
92
|
+
]);
|
|
93
|
+
const shouldRenderChildren = useMatchBreakpoint(size, matchTo, isDefaultMatches);
|
|
94
|
+
if (!shouldRenderChildren) return null;
|
|
95
|
+
return Children.map(children, (child)=>/*#__PURE__*/ _jsx(BreakpointChild, _object_spread_props(_object_spread({}, componentProps), {
|
|
96
|
+
ref: ref,
|
|
97
|
+
as: as,
|
|
98
|
+
size: size,
|
|
99
|
+
matchTo: matchTo,
|
|
100
|
+
child: child
|
|
101
|
+
})));
|
|
102
|
+
};
|
|
103
|
+
export const Breakpoint = /*#__PURE__*/ forwardRef(BreakpointRenderFunction);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ComponentProps, type ElementType, type ReactNode } from 'react';
|
|
2
|
+
import { type MatchTo, type ScreenSize } from '../../types/breakpoints';
|
|
3
|
+
import { type ExtractRef } from '../../types/react';
|
|
4
|
+
type ControlledProps<E extends ElementType> = {
|
|
5
|
+
as?: E;
|
|
6
|
+
size: ScreenSize;
|
|
7
|
+
matchTo: MatchTo;
|
|
8
|
+
child: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
type ComponentNativeProps<E extends ElementType> = Omit<ComponentProps<E>, 'children' | keyof ControlledProps<E>>;
|
|
11
|
+
type TailwindBreakpointProps<E extends ElementType> = ComponentNativeProps<E> & ControlledProps<E>;
|
|
12
|
+
export declare const BreakpointChild: <T extends ElementType = import("react").ExoticComponent<{
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}>>(props: TailwindBreakpointProps<T>, ref: ExtractRef<ComponentNativeProps<T>>) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _object_spread(target) {
|
|
15
|
+
for(var i = 1; i < arguments.length; i++){
|
|
16
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
17
|
+
var ownKeys = Object.keys(source);
|
|
18
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
19
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
20
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
ownKeys.forEach(function(key) {
|
|
24
|
+
_define_property(target, key, source[key]);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
function ownKeys(object, enumerableOnly) {
|
|
30
|
+
var keys = Object.keys(object);
|
|
31
|
+
if (Object.getOwnPropertySymbols) {
|
|
32
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
33
|
+
if (enumerableOnly) {
|
|
34
|
+
symbols = symbols.filter(function(sym) {
|
|
35
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
keys.push.apply(keys, symbols);
|
|
39
|
+
}
|
|
40
|
+
return keys;
|
|
41
|
+
}
|
|
42
|
+
function _object_spread_props(target, source) {
|
|
43
|
+
source = source != null ? source : {};
|
|
44
|
+
if (Object.getOwnPropertyDescriptors) {
|
|
45
|
+
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
46
|
+
} else {
|
|
47
|
+
ownKeys(Object(source)).forEach(function(key) {
|
|
48
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return target;
|
|
52
|
+
}
|
|
53
|
+
function _object_without_properties(source, excluded) {
|
|
54
|
+
if (source == null) return {};
|
|
55
|
+
var target = _object_without_properties_loose(source, excluded);
|
|
56
|
+
var key, i;
|
|
57
|
+
if (Object.getOwnPropertySymbols) {
|
|
58
|
+
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
|
59
|
+
for(i = 0; i < sourceSymbolKeys.length; i++){
|
|
60
|
+
key = sourceSymbolKeys[i];
|
|
61
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
62
|
+
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
|
63
|
+
target[key] = source[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return target;
|
|
67
|
+
}
|
|
68
|
+
function _object_without_properties_loose(source, excluded) {
|
|
69
|
+
if (source == null) return {};
|
|
70
|
+
var target = {};
|
|
71
|
+
var sourceKeys = Object.keys(source);
|
|
72
|
+
var key, i;
|
|
73
|
+
for(i = 0; i < sourceKeys.length; i++){
|
|
74
|
+
key = sourceKeys[i];
|
|
75
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
76
|
+
target[key] = source[key];
|
|
77
|
+
}
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
81
|
+
import { cloneElement, forwardRef, Fragment, isValidElement } from "react";
|
|
82
|
+
import { getMaxScreenWidth, getMinScreenWidth } from "../../utils/screen-width";
|
|
83
|
+
import { useBreakpointsContext } from "../BreakpointsProvider/BreakpointsProvider";
|
|
84
|
+
const DEFAULT_COMPONENT = Fragment;
|
|
85
|
+
const CLASS_NAME_PRESETS = {
|
|
86
|
+
tailwind: {
|
|
87
|
+
maxClassName: 'min-[var(--max-screen-width)]:hidden',
|
|
88
|
+
minClassName: 'max-[var(--min-screen-width)]:hidden'
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const buildProps = (options)=>{
|
|
92
|
+
const { ref, initialProps, matchTo, breakpoint, classNamePreset, mergeClassesFunction } = options;
|
|
93
|
+
const classNamePresetData = classNamePreset !== undefined ? CLASS_NAME_PRESETS[classNamePreset] : null;
|
|
94
|
+
if (matchTo === 'max') {
|
|
95
|
+
var _breakpoint_data_maxClassName;
|
|
96
|
+
return _object_spread_props(_object_spread({}, initialProps), {
|
|
97
|
+
ref,
|
|
98
|
+
style: _object_spread_props(_object_spread({}, initialProps === null || initialProps === void 0 ? void 0 : initialProps.style), {
|
|
99
|
+
['--max-screen-width']: getMaxScreenWidth(breakpoint.data)
|
|
100
|
+
}),
|
|
101
|
+
className: mergeClassesFunction(initialProps === null || initialProps === void 0 ? void 0 : initialProps.className, typeof breakpoint.data === 'object' ? (_breakpoint_data_maxClassName = breakpoint.data.maxClassName) !== null && _breakpoint_data_maxClassName !== void 0 ? _breakpoint_data_maxClassName : classNamePresetData === null || classNamePresetData === void 0 ? void 0 : classNamePresetData.maxClassName : classNamePresetData === null || classNamePresetData === void 0 ? void 0 : classNamePresetData.maxClassName)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
var _breakpoint_data_minClassName;
|
|
105
|
+
return _object_spread_props(_object_spread({}, initialProps), {
|
|
106
|
+
ref,
|
|
107
|
+
style: _object_spread_props(_object_spread({}, initialProps === null || initialProps === void 0 ? void 0 : initialProps.style), {
|
|
108
|
+
['--min-screen-width']: getMinScreenWidth(breakpoint.data)
|
|
109
|
+
}),
|
|
110
|
+
className: mergeClassesFunction(initialProps === null || initialProps === void 0 ? void 0 : initialProps.className, typeof breakpoint.data === 'object' ? (_breakpoint_data_minClassName = breakpoint.data.minClassName) !== null && _breakpoint_data_minClassName !== void 0 ? _breakpoint_data_minClassName : classNamePresetData === null || classNamePresetData === void 0 ? void 0 : classNamePresetData.minClassName : classNamePresetData === null || classNamePresetData === void 0 ? void 0 : classNamePresetData.minClassName)
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const BreakpointChildRenderFunction = (props, ref)=>{
|
|
114
|
+
const { as: Component = DEFAULT_COMPONENT, size, matchTo, child } = props, componentProps = _object_without_properties(props, [
|
|
115
|
+
"as",
|
|
116
|
+
"size",
|
|
117
|
+
"matchTo",
|
|
118
|
+
"child"
|
|
119
|
+
]);
|
|
120
|
+
const { breakpoints, classNamePreset, mergeClassesFunction } = useBreakpointsContext();
|
|
121
|
+
const breakpoint = breakpoints[size];
|
|
122
|
+
if (!breakpoint) {
|
|
123
|
+
throw new Error(`Breakpoint with screen size "${size}" is not defined`);
|
|
124
|
+
}
|
|
125
|
+
if (Component === Fragment && /*#__PURE__*/ isValidElement(child)) {
|
|
126
|
+
return /*#__PURE__*/ cloneElement(child, buildProps({
|
|
127
|
+
initialProps: child.props,
|
|
128
|
+
matchTo,
|
|
129
|
+
breakpoint,
|
|
130
|
+
classNamePreset,
|
|
131
|
+
mergeClassesFunction
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
if (Component !== Fragment) {
|
|
135
|
+
return /*#__PURE__*/ _jsx(Component, _object_spread_props(_object_spread({}, buildProps({
|
|
136
|
+
initialProps: componentProps,
|
|
137
|
+
ref,
|
|
138
|
+
matchTo,
|
|
139
|
+
breakpoint,
|
|
140
|
+
classNamePreset,
|
|
141
|
+
mergeClassesFunction
|
|
142
|
+
})), {
|
|
143
|
+
children: child
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
return /*#__PURE__*/ _jsx("div", _object_spread_props(_object_spread({}, buildProps({
|
|
147
|
+
matchTo,
|
|
148
|
+
breakpoint,
|
|
149
|
+
classNamePreset,
|
|
150
|
+
mergeClassesFunction
|
|
151
|
+
})), {
|
|
152
|
+
children: child
|
|
153
|
+
}));
|
|
154
|
+
};
|
|
155
|
+
export const BreakpointChild = /*#__PURE__*/ forwardRef(BreakpointChildRenderFunction);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../components/Breakpoint/Breakpoint';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Breakpoint";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Context, type FC, type ReactNode } from 'react';
|
|
2
|
+
import { type BreakpointData } from '../../types/breakpoints';
|
|
3
|
+
type BreakpointContext = {
|
|
4
|
+
isMatches: boolean | null;
|
|
5
|
+
};
|
|
6
|
+
export type ContextualizedBreakpointData = {
|
|
7
|
+
data: BreakpointData;
|
|
8
|
+
context: Context<BreakpointContext>;
|
|
9
|
+
};
|
|
10
|
+
export declare const DEFAULT_BREAKPOINT_CONTEXT: BreakpointContext;
|
|
11
|
+
type BreakpointProviderProps = {
|
|
12
|
+
breakpoint: ContextualizedBreakpointData;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
};
|
|
15
|
+
export declare const BreakpointProvider: FC<BreakpointProviderProps>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { getMinScreenWidth } from "../../utils/screen-width";
|
|
4
|
+
export const DEFAULT_BREAKPOINT_CONTEXT = {
|
|
5
|
+
isMatches: null
|
|
6
|
+
};
|
|
7
|
+
export const BreakpointProvider = (props)=>{
|
|
8
|
+
const { breakpoint, children } = props;
|
|
9
|
+
const { Provider } = breakpoint.context;
|
|
10
|
+
const [isMatches, setIsMatches] = useState(DEFAULT_BREAKPOINT_CONTEXT.isMatches);
|
|
11
|
+
const minScreenWidth = getMinScreenWidth(breakpoint.data);
|
|
12
|
+
useEffect(()=>{
|
|
13
|
+
const media = window.matchMedia(`(min-width: ${minScreenWidth})`);
|
|
14
|
+
setIsMatches(media.matches);
|
|
15
|
+
const handleMediaChange = (event)=>{
|
|
16
|
+
setIsMatches(event.matches);
|
|
17
|
+
};
|
|
18
|
+
media.addEventListener('change', handleMediaChange);
|
|
19
|
+
return ()=>{
|
|
20
|
+
media.removeEventListener('change', handleMediaChange);
|
|
21
|
+
};
|
|
22
|
+
}, [
|
|
23
|
+
minScreenWidth
|
|
24
|
+
]);
|
|
25
|
+
const value = useMemo(()=>({
|
|
26
|
+
isMatches
|
|
27
|
+
}), [
|
|
28
|
+
isMatches
|
|
29
|
+
]);
|
|
30
|
+
return /*#__PURE__*/ _jsx(Provider, {
|
|
31
|
+
value: value,
|
|
32
|
+
children: children
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type FC, type ReactNode } from 'react';
|
|
2
|
+
import { type Breakpoints, type MatchTo, type ScreenSize } from '../../types/breakpoints';
|
|
3
|
+
import { type ClassNamePreset, type MergeClassesFunction } from '../../types/styles';
|
|
4
|
+
import { type ContextualizedBreakpointData } from '../../components/BreakpointsProvider/BreakpointProvider';
|
|
5
|
+
type ContextualizedBreakpoints = Record<ScreenSize, ContextualizedBreakpointData>;
|
|
6
|
+
type BreakpointsContext = {
|
|
7
|
+
breakpoints: ContextualizedBreakpoints;
|
|
8
|
+
classNamePreset?: ClassNamePreset;
|
|
9
|
+
mergeClassesFunction: MergeClassesFunction;
|
|
10
|
+
};
|
|
11
|
+
type BreakpointsProviderProps = {
|
|
12
|
+
breakpoints: Breakpoints;
|
|
13
|
+
classNamePreset?: ClassNamePreset;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
mergeClassesFunction?: MergeClassesFunction;
|
|
16
|
+
};
|
|
17
|
+
export declare const useBreakpointsContext: () => BreakpointsContext;
|
|
18
|
+
export declare const useMatchBreakpoint: (size: ScreenSize, matchTo?: MatchTo, defaultValue?: boolean) => boolean;
|
|
19
|
+
export declare const MatchBreakpointProvider: FC<BreakpointsProviderProps>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _object_spread(target) {
|
|
15
|
+
for(var i = 1; i < arguments.length; i++){
|
|
16
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
17
|
+
var ownKeys = Object.keys(source);
|
|
18
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
19
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
20
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
ownKeys.forEach(function(key) {
|
|
24
|
+
_define_property(target, key, source[key]);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
function ownKeys(object, enumerableOnly) {
|
|
30
|
+
var keys = Object.keys(object);
|
|
31
|
+
if (Object.getOwnPropertySymbols) {
|
|
32
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
33
|
+
if (enumerableOnly) {
|
|
34
|
+
symbols = symbols.filter(function(sym) {
|
|
35
|
+
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
keys.push.apply(keys, symbols);
|
|
39
|
+
}
|
|
40
|
+
return keys;
|
|
41
|
+
}
|
|
42
|
+
function _object_spread_props(target, source) {
|
|
43
|
+
source = source != null ? source : {};
|
|
44
|
+
if (Object.getOwnPropertyDescriptors) {
|
|
45
|
+
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
46
|
+
} else {
|
|
47
|
+
ownKeys(Object(source)).forEach(function(key) {
|
|
48
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return target;
|
|
52
|
+
}
|
|
53
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
54
|
+
import { createContext, useContext, useMemo } from "react";
|
|
55
|
+
import { BreakpointProvider, DEFAULT_BREAKPOINT_CONTEXT } from "./BreakpointProvider";
|
|
56
|
+
const DEFAULT_BREAKPOINTS_CONTEXT = {
|
|
57
|
+
breakpoints: {},
|
|
58
|
+
mergeClassesFunction: (...classes)=>classes.filter(Boolean).join(' ')
|
|
59
|
+
};
|
|
60
|
+
const BreakpointsContextInstance = /*#__PURE__*/ createContext(DEFAULT_BREAKPOINTS_CONTEXT);
|
|
61
|
+
export const useBreakpointsContext = ()=>useContext(BreakpointsContextInstance);
|
|
62
|
+
export const useMatchBreakpoint = (size, matchTo = 'min', defaultValue = false)=>{
|
|
63
|
+
const { breakpoints } = useBreakpointsContext();
|
|
64
|
+
const breakpoint = breakpoints[size];
|
|
65
|
+
if (!breakpoint) {
|
|
66
|
+
throw new Error(`Breakpoint with screen size "${size}" is not defined`);
|
|
67
|
+
}
|
|
68
|
+
const { isMatches } = useContext(breakpoint.context);
|
|
69
|
+
if (isMatches === null) return defaultValue;
|
|
70
|
+
return matchTo === 'min' ? isMatches : !isMatches;
|
|
71
|
+
};
|
|
72
|
+
export const MatchBreakpointProvider = (props)=>{
|
|
73
|
+
const { breakpoints, classNamePreset, children, mergeClassesFunction = DEFAULT_BREAKPOINTS_CONTEXT.mergeClassesFunction } = props;
|
|
74
|
+
const value = useMemo(()=>({
|
|
75
|
+
breakpoints: Object.keys(breakpoints).reduce((acc, key)=>_object_spread_props(_object_spread({}, acc), {
|
|
76
|
+
[key]: {
|
|
77
|
+
data: breakpoints[key],
|
|
78
|
+
context: /*#__PURE__*/ createContext(_object_spread({}, DEFAULT_BREAKPOINT_CONTEXT))
|
|
79
|
+
}
|
|
80
|
+
}), {}),
|
|
81
|
+
classNamePreset,
|
|
82
|
+
mergeClassesFunction
|
|
83
|
+
}), [
|
|
84
|
+
breakpoints,
|
|
85
|
+
classNamePreset,
|
|
86
|
+
mergeClassesFunction
|
|
87
|
+
]);
|
|
88
|
+
return /*#__PURE__*/ _jsx(BreakpointsContextInstance.Provider, {
|
|
89
|
+
value: value,
|
|
90
|
+
children: Object.keys(value.breakpoints).reduce((acc, size)=>/*#__PURE__*/ _jsx(BreakpointProvider, {
|
|
91
|
+
breakpoint: value.breakpoints[size],
|
|
92
|
+
children: acc
|
|
93
|
+
}), children)
|
|
94
|
+
});
|
|
95
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../components/BreakpointsProvider/BreakpointsProvider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./BreakpointsProvider";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const getMaxScreenWidth = (breakpointData)=>{
|
|
2
|
+
if (typeof breakpointData === 'string') return breakpointData;
|
|
3
|
+
if (typeof breakpointData === 'number') return `${breakpointData}px`;
|
|
4
|
+
if (typeof breakpointData.max === 'string') return breakpointData.max;
|
|
5
|
+
return `${breakpointData.max}px`;
|
|
6
|
+
};
|
|
7
|
+
export const getMinScreenWidth = (breakpointData)=>{
|
|
8
|
+
if (typeof breakpointData === 'string') return breakpointData;
|
|
9
|
+
if (typeof breakpointData === 'number') return `${breakpointData}px`;
|
|
10
|
+
if (typeof breakpointData.min === 'string') return breakpointData.min;
|
|
11
|
+
return `${breakpointData.min}px`;
|
|
12
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "match-breakpoint",
|
|
3
|
+
"version": "1.0.0-canary.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Oleg Putseiko <oleg.putseiko@gmail.com> (https://github.com/oleg-putseiko)",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"homepage": "https://github.com/oleg-putseiko/match-breakpoint",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/oleg-putseiko/match-breakpoint"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/oleg-putseiko/match-breakpoint/issues"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/src/index.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/src/components/**/*",
|
|
19
|
+
"dist/src/utils/**/*",
|
|
20
|
+
"dist/src/index.js"
|
|
21
|
+
],
|
|
22
|
+
"exports": "./dist/src/index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "swc ./src/**/* -d dist && tsc --emitDeclarationOnly && tsc-alias",
|
|
25
|
+
"format": "prettier -c .",
|
|
26
|
+
"format:fix": "prettier -w .",
|
|
27
|
+
"lint": "eslint ./src/**/*.{js,ts,tsx,json}",
|
|
28
|
+
"lint:fix": "eslint ./src/**/*.{js,ts,tsx,json} --fix && yarn format:fix",
|
|
29
|
+
"lint:strict": "eslint ./src/**/*.{js,ts,tsx,json} --max-warnings=0 && yarn format",
|
|
30
|
+
"prepare": "husky install | chmod +x ./.husky/*",
|
|
31
|
+
"test": "jest",
|
|
32
|
+
"typecheck": "tsc --noEmit --incremental false"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "^18.2.0",
|
|
36
|
+
"react-dom": "^18.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@commitlint/cli": "^18.2.0",
|
|
40
|
+
"@commitlint/config-conventional": "^18.1.0",
|
|
41
|
+
"@swc/cli": "^0.5.1",
|
|
42
|
+
"@swc/core": "^1.9.3",
|
|
43
|
+
"@types/react": "^18.0.0",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
|
45
|
+
"@typescript-eslint/parser": "^6.9.1",
|
|
46
|
+
"eslint": "^8.53.0",
|
|
47
|
+
"eslint-config-prettier": "^9.0.0",
|
|
48
|
+
"eslint-config-woofmeow": "^1.0.1",
|
|
49
|
+
"eslint-plugin-react": "^7.37.2",
|
|
50
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
51
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
52
|
+
"eslint-plugin-unused-imports": "^3.0.0",
|
|
53
|
+
"husky": "^8.0.3",
|
|
54
|
+
"lint-staged": "^15.0.2",
|
|
55
|
+
"prettier": "^3.0.3",
|
|
56
|
+
"tsc-alias": "^1.8.10",
|
|
57
|
+
"typescript": "~5.2.2"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": "18.18.0",
|
|
61
|
+
"yarn": "3.6.4"
|
|
62
|
+
},
|
|
63
|
+
"volta": {
|
|
64
|
+
"node": "18.18.0",
|
|
65
|
+
"yarn": "3.6.4"
|
|
66
|
+
},
|
|
67
|
+
"packageManager": "yarn@3.6.4"
|
|
68
|
+
}
|