@kaizen/components 0.0.0-canary-useContainerQuery-20251123222018 → 0.0.0-canary-useContainerQuery-20251125222037
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/dist/cjs/src/utils/useContainerQueries.cjs +23 -177
- package/dist/esm/src/utils/useContainerQueries.mjs +24 -172
- package/dist/types/utils/useContainerQueries.d.ts +19 -47
- package/package.json +1 -1
- package/src/utils/useContainerQueries.spec.tsx +6 -16
- package/src/utils/useContainerQueries.tsx +36 -161
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var useDebounce = require('use-debounce');
|
|
6
|
-
function _interopDefault(e) {
|
|
7
|
-
return e && e.__esModule ? e : {
|
|
8
|
-
default: e
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
12
6
|
var DEFAULT_DEBOUNCE_MS = 500;
|
|
13
7
|
/**
|
|
14
8
|
* Tailwind CSS default container query breakpoints in pixels
|
|
@@ -50,21 +44,17 @@ var parseBreakpointValue = function (value) {
|
|
|
50
44
|
*
|
|
51
45
|
* @returns An object containing:
|
|
52
46
|
* - containerRef: A ref to attach to your container element
|
|
53
|
-
* - queries: Boolean flags for each breakpoint (
|
|
54
|
-
* - components: React components for conditional rendering (XsOnly, SmOrLarger, etc.)
|
|
47
|
+
* - queries: Boolean flags for each breakpoint (isXsOrLarger, isSmOrLarger, isMdOrLarger, etc.) and custom queries
|
|
55
48
|
*
|
|
56
49
|
* @example
|
|
57
50
|
* ```tsx
|
|
58
51
|
* const MyComponent = () => {
|
|
59
|
-
* const { containerRef, queries
|
|
60
|
-
* const { MdOrLarger } = components
|
|
52
|
+
* const { containerRef, queries } = useContainerQueries()
|
|
61
53
|
*
|
|
62
54
|
* return (
|
|
63
55
|
* <div ref={containerRef}>
|
|
64
|
-
* {queries.
|
|
65
|
-
* <
|
|
66
|
-
* <p>Medium or larger container</p>
|
|
67
|
-
* </MdOrLarger>
|
|
56
|
+
* {queries.isSmOrLarger && <p>Small container</p>}
|
|
57
|
+
* {queries.isMdOrLarger && <p>Medium or larger container</p>}
|
|
68
58
|
* </div>
|
|
69
59
|
* )
|
|
70
60
|
* }
|
|
@@ -72,16 +62,15 @@ var parseBreakpointValue = function (value) {
|
|
|
72
62
|
*
|
|
73
63
|
* @example With custom queries
|
|
74
64
|
* ```tsx
|
|
75
|
-
* const { containerRef, queries
|
|
65
|
+
* const { containerRef, queries } = useContainerQueries({
|
|
76
66
|
* compact: '400px',
|
|
77
67
|
* spacious: '800px',
|
|
78
68
|
* })
|
|
79
|
-
* const { Compact, Spacious } = components
|
|
80
69
|
*
|
|
81
70
|
* return (
|
|
82
71
|
* <div ref={containerRef}>
|
|
83
|
-
* <
|
|
84
|
-
* <
|
|
72
|
+
* {queries.compact && <p>Compact view</p>}
|
|
73
|
+
* {queries.spacious && <p>Spacious view</p>}
|
|
85
74
|
* </div>
|
|
86
75
|
* )
|
|
87
76
|
* ```
|
|
@@ -90,76 +79,7 @@ var useContainerQueries = function (propQueries) {
|
|
|
90
79
|
if (propQueries === void 0) {
|
|
91
80
|
propQueries = {};
|
|
92
81
|
}
|
|
93
|
-
|
|
94
|
-
if (typeof window === 'undefined') {
|
|
95
|
-
return {
|
|
96
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
97
|
-
containerRef: function () {},
|
|
98
|
-
queries: {
|
|
99
|
-
isXs: false,
|
|
100
|
-
isSm: false,
|
|
101
|
-
isMd: false,
|
|
102
|
-
isLg: false,
|
|
103
|
-
isXl: false,
|
|
104
|
-
is2xl: false,
|
|
105
|
-
is3xl: false,
|
|
106
|
-
is4xl: false,
|
|
107
|
-
is5xl: false,
|
|
108
|
-
is6xl: false,
|
|
109
|
-
is7xl: true // Default to largest for SSR
|
|
110
|
-
},
|
|
111
|
-
components: {
|
|
112
|
-
'XsOnly': function () {
|
|
113
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
114
|
-
},
|
|
115
|
-
'SmOnly': function () {
|
|
116
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
117
|
-
},
|
|
118
|
-
'MdOnly': function () {
|
|
119
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
120
|
-
},
|
|
121
|
-
'LgOnly': function () {
|
|
122
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
123
|
-
},
|
|
124
|
-
'XlOnly': function () {
|
|
125
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
126
|
-
},
|
|
127
|
-
'2xlOnly': function () {
|
|
128
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
129
|
-
},
|
|
130
|
-
'3xlOnly': function () {
|
|
131
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
132
|
-
},
|
|
133
|
-
'4xlOnly': function () {
|
|
134
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
135
|
-
},
|
|
136
|
-
'5xlOnly': function () {
|
|
137
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
138
|
-
},
|
|
139
|
-
'6xlOnly': function () {
|
|
140
|
-
return React__default.default.createElement(React__default.default.Fragment, null);
|
|
141
|
-
},
|
|
142
|
-
'7xlOnly': function (props) {
|
|
143
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
144
|
-
},
|
|
145
|
-
'XsOrLarger': function (props) {
|
|
146
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
147
|
-
},
|
|
148
|
-
'SmOrLarger': function (props) {
|
|
149
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
150
|
-
},
|
|
151
|
-
'MdOrLarger': function (props) {
|
|
152
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
153
|
-
},
|
|
154
|
-
'LgOrLarger': function (props) {
|
|
155
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
156
|
-
},
|
|
157
|
-
'XlOrLarger': function (props) {
|
|
158
|
-
return React__default.default.createElement(React__default.default.Fragment, null, props.children);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
82
|
+
var isClient = typeof window !== 'undefined';
|
|
163
83
|
// Parse custom queries
|
|
164
84
|
var customQueriesPx = React.useMemo(function () {
|
|
165
85
|
return Object.entries(propQueries).reduce(function (acc, _a) {
|
|
@@ -181,6 +101,8 @@ var useContainerQueries = function (propQueries) {
|
|
|
181
101
|
}, DEFAULT_DEBOUNCE_MS);
|
|
182
102
|
// Callback ref for the container element
|
|
183
103
|
var containerRef = React.useCallback(function (node) {
|
|
104
|
+
// Skip if SSR
|
|
105
|
+
if (!isClient) return;
|
|
184
106
|
// Cleanup previous observer
|
|
185
107
|
if (resizeObserverRef.current) {
|
|
186
108
|
resizeObserverRef.current.disconnect();
|
|
@@ -202,7 +124,7 @@ var useContainerQueries = function (propQueries) {
|
|
|
202
124
|
var width = node.getBoundingClientRect().width;
|
|
203
125
|
setContainerWidth(width);
|
|
204
126
|
}
|
|
205
|
-
}, [debouncedSetContainerWidth]);
|
|
127
|
+
}, [debouncedSetContainerWidth, isClient]);
|
|
206
128
|
// Cleanup on unmount
|
|
207
129
|
React.useEffect(function () {
|
|
208
130
|
return function () {
|
|
@@ -214,17 +136,17 @@ var useContainerQueries = function (propQueries) {
|
|
|
214
136
|
// Calculate breakpoint matches based on container width
|
|
215
137
|
var breakpointMatches = React.useMemo(function () {
|
|
216
138
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
139
|
+
isXsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xs,
|
|
140
|
+
isSmOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.sm,
|
|
141
|
+
isMdOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.md,
|
|
142
|
+
isLgOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.lg,
|
|
143
|
+
isXlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xl,
|
|
144
|
+
is2xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['2xl'],
|
|
145
|
+
is3xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['3xl'],
|
|
146
|
+
is4xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['4xl'],
|
|
147
|
+
is5xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['5xl'],
|
|
148
|
+
is6xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['6xl'],
|
|
149
|
+
is7xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['7xl']
|
|
228
150
|
};
|
|
229
151
|
}, [containerWidth]);
|
|
230
152
|
// Calculate custom query matches
|
|
@@ -236,85 +158,9 @@ var useContainerQueries = function (propQueries) {
|
|
|
236
158
|
return acc;
|
|
237
159
|
}, {});
|
|
238
160
|
}, [containerWidth, customQueriesPx]);
|
|
239
|
-
// Helper function to check if container is at exact breakpoint (not larger)
|
|
240
|
-
var isExactBreakpoint = React.useCallback(function (breakpoint) {
|
|
241
|
-
var sortedBreakpoints = Object.entries(DEFAULT_BREAKPOINTS).sort(function (_a, _b) {
|
|
242
|
-
var a = _a[1];
|
|
243
|
-
var b = _b[1];
|
|
244
|
-
return a - b;
|
|
245
|
-
});
|
|
246
|
-
var currentIndex = sortedBreakpoints.findIndex(function (_a) {
|
|
247
|
-
var key = _a[0];
|
|
248
|
-
return key === breakpoint;
|
|
249
|
-
});
|
|
250
|
-
var nextBreakpoint = sortedBreakpoints[currentIndex + 1];
|
|
251
|
-
var minWidth = DEFAULT_BREAKPOINTS[breakpoint];
|
|
252
|
-
var maxWidth = nextBreakpoint ? nextBreakpoint[1] : Infinity;
|
|
253
|
-
return containerWidth >= minWidth && containerWidth < maxWidth;
|
|
254
|
-
}, [containerWidth]);
|
|
255
|
-
// Create helper components for Tailwind breakpoints
|
|
256
|
-
var components = React.useMemo(function () {
|
|
257
|
-
return tslib.__assign({
|
|
258
|
-
'XsOnly': function (props) {
|
|
259
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('xs') && props.children);
|
|
260
|
-
},
|
|
261
|
-
'SmOnly': function (props) {
|
|
262
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('sm') && props.children);
|
|
263
|
-
},
|
|
264
|
-
'MdOnly': function (props) {
|
|
265
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('md') && props.children);
|
|
266
|
-
},
|
|
267
|
-
'LgOnly': function (props) {
|
|
268
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('lg') && props.children);
|
|
269
|
-
},
|
|
270
|
-
'XlOnly': function (props) {
|
|
271
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('xl') && props.children);
|
|
272
|
-
},
|
|
273
|
-
'2xlOnly': function (props) {
|
|
274
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('2xl') && props.children);
|
|
275
|
-
},
|
|
276
|
-
'3xlOnly': function (props) {
|
|
277
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('3xl') && props.children);
|
|
278
|
-
},
|
|
279
|
-
'4xlOnly': function (props) {
|
|
280
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('4xl') && props.children);
|
|
281
|
-
},
|
|
282
|
-
'5xlOnly': function (props) {
|
|
283
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('5xl') && props.children);
|
|
284
|
-
},
|
|
285
|
-
'6xlOnly': function (props) {
|
|
286
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('6xl') && props.children);
|
|
287
|
-
},
|
|
288
|
-
'7xlOnly': function (props) {
|
|
289
|
-
return React__default.default.createElement(React__default.default.Fragment, null, isExactBreakpoint('7xl') && props.children);
|
|
290
|
-
},
|
|
291
|
-
'XsOrLarger': function (props) {
|
|
292
|
-
return React__default.default.createElement(React__default.default.Fragment, null, breakpointMatches.isXs && props.children);
|
|
293
|
-
},
|
|
294
|
-
'SmOrLarger': function (props) {
|
|
295
|
-
return React__default.default.createElement(React__default.default.Fragment, null, breakpointMatches.isSm && props.children);
|
|
296
|
-
},
|
|
297
|
-
'MdOrLarger': function (props) {
|
|
298
|
-
return React__default.default.createElement(React__default.default.Fragment, null, breakpointMatches.isMd && props.children);
|
|
299
|
-
},
|
|
300
|
-
'LgOrLarger': function (props) {
|
|
301
|
-
return React__default.default.createElement(React__default.default.Fragment, null, breakpointMatches.isLg && props.children);
|
|
302
|
-
},
|
|
303
|
-
'XlOrLarger': function (props) {
|
|
304
|
-
return React__default.default.createElement(React__default.default.Fragment, null, breakpointMatches.isXl && props.children);
|
|
305
|
-
}
|
|
306
|
-
}, Object.keys(customQueriesPx).reduce(function (acc, key) {
|
|
307
|
-
var componentName = key.charAt(0).toUpperCase() + key.slice(1);
|
|
308
|
-
acc[componentName] = function (props) {
|
|
309
|
-
return React__default.default.createElement(React__default.default.Fragment, null, customMatches[key] && props.children);
|
|
310
|
-
};
|
|
311
|
-
return acc;
|
|
312
|
-
}, {}));
|
|
313
|
-
}, [breakpointMatches, customMatches, isExactBreakpoint, customQueriesPx]);
|
|
314
161
|
return {
|
|
315
162
|
containerRef: containerRef,
|
|
316
|
-
queries: tslib.__assign(tslib.__assign({}, breakpointMatches), customMatches)
|
|
317
|
-
components: components
|
|
163
|
+
queries: tslib.__assign(tslib.__assign({}, breakpointMatches), customMatches)
|
|
318
164
|
};
|
|
319
165
|
};
|
|
320
166
|
exports.useContainerQueries = useContainerQueries;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __assign } from 'tslib';
|
|
2
|
-
import
|
|
2
|
+
import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
|
|
3
3
|
import { useDebouncedCallback } from 'use-debounce';
|
|
4
4
|
var DEFAULT_DEBOUNCE_MS = 500;
|
|
5
5
|
/**
|
|
@@ -42,21 +42,17 @@ var parseBreakpointValue = function (value) {
|
|
|
42
42
|
*
|
|
43
43
|
* @returns An object containing:
|
|
44
44
|
* - containerRef: A ref to attach to your container element
|
|
45
|
-
* - queries: Boolean flags for each breakpoint (
|
|
46
|
-
* - components: React components for conditional rendering (XsOnly, SmOrLarger, etc.)
|
|
45
|
+
* - queries: Boolean flags for each breakpoint (isXsOrLarger, isSmOrLarger, isMdOrLarger, etc.) and custom queries
|
|
47
46
|
*
|
|
48
47
|
* @example
|
|
49
48
|
* ```tsx
|
|
50
49
|
* const MyComponent = () => {
|
|
51
|
-
* const { containerRef, queries
|
|
52
|
-
* const { MdOrLarger } = components
|
|
50
|
+
* const { containerRef, queries } = useContainerQueries()
|
|
53
51
|
*
|
|
54
52
|
* return (
|
|
55
53
|
* <div ref={containerRef}>
|
|
56
|
-
* {queries.
|
|
57
|
-
* <
|
|
58
|
-
* <p>Medium or larger container</p>
|
|
59
|
-
* </MdOrLarger>
|
|
54
|
+
* {queries.isSmOrLarger && <p>Small container</p>}
|
|
55
|
+
* {queries.isMdOrLarger && <p>Medium or larger container</p>}
|
|
60
56
|
* </div>
|
|
61
57
|
* )
|
|
62
58
|
* }
|
|
@@ -64,16 +60,15 @@ var parseBreakpointValue = function (value) {
|
|
|
64
60
|
*
|
|
65
61
|
* @example With custom queries
|
|
66
62
|
* ```tsx
|
|
67
|
-
* const { containerRef, queries
|
|
63
|
+
* const { containerRef, queries } = useContainerQueries({
|
|
68
64
|
* compact: '400px',
|
|
69
65
|
* spacious: '800px',
|
|
70
66
|
* })
|
|
71
|
-
* const { Compact, Spacious } = components
|
|
72
67
|
*
|
|
73
68
|
* return (
|
|
74
69
|
* <div ref={containerRef}>
|
|
75
|
-
* <
|
|
76
|
-
* <
|
|
70
|
+
* {queries.compact && <p>Compact view</p>}
|
|
71
|
+
* {queries.spacious && <p>Spacious view</p>}
|
|
77
72
|
* </div>
|
|
78
73
|
* )
|
|
79
74
|
* ```
|
|
@@ -82,76 +77,7 @@ var useContainerQueries = function (propQueries) {
|
|
|
82
77
|
if (propQueries === void 0) {
|
|
83
78
|
propQueries = {};
|
|
84
79
|
}
|
|
85
|
-
|
|
86
|
-
if (typeof window === 'undefined') {
|
|
87
|
-
return {
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
89
|
-
containerRef: function () {},
|
|
90
|
-
queries: {
|
|
91
|
-
isXs: false,
|
|
92
|
-
isSm: false,
|
|
93
|
-
isMd: false,
|
|
94
|
-
isLg: false,
|
|
95
|
-
isXl: false,
|
|
96
|
-
is2xl: false,
|
|
97
|
-
is3xl: false,
|
|
98
|
-
is4xl: false,
|
|
99
|
-
is5xl: false,
|
|
100
|
-
is6xl: false,
|
|
101
|
-
is7xl: true // Default to largest for SSR
|
|
102
|
-
},
|
|
103
|
-
components: {
|
|
104
|
-
'XsOnly': function () {
|
|
105
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
106
|
-
},
|
|
107
|
-
'SmOnly': function () {
|
|
108
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
109
|
-
},
|
|
110
|
-
'MdOnly': function () {
|
|
111
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
112
|
-
},
|
|
113
|
-
'LgOnly': function () {
|
|
114
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
115
|
-
},
|
|
116
|
-
'XlOnly': function () {
|
|
117
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
118
|
-
},
|
|
119
|
-
'2xlOnly': function () {
|
|
120
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
121
|
-
},
|
|
122
|
-
'3xlOnly': function () {
|
|
123
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
124
|
-
},
|
|
125
|
-
'4xlOnly': function () {
|
|
126
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
127
|
-
},
|
|
128
|
-
'5xlOnly': function () {
|
|
129
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
130
|
-
},
|
|
131
|
-
'6xlOnly': function () {
|
|
132
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null);
|
|
133
|
-
},
|
|
134
|
-
'7xlOnly': function (props) {
|
|
135
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
136
|
-
},
|
|
137
|
-
'XsOrLarger': function (props) {
|
|
138
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
139
|
-
},
|
|
140
|
-
'SmOrLarger': function (props) {
|
|
141
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
142
|
-
},
|
|
143
|
-
'MdOrLarger': function (props) {
|
|
144
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
145
|
-
},
|
|
146
|
-
'LgOrLarger': function (props) {
|
|
147
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
148
|
-
},
|
|
149
|
-
'XlOrLarger': function (props) {
|
|
150
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
80
|
+
var isClient = typeof window !== 'undefined';
|
|
155
81
|
// Parse custom queries
|
|
156
82
|
var customQueriesPx = useMemo(function () {
|
|
157
83
|
return Object.entries(propQueries).reduce(function (acc, _a) {
|
|
@@ -173,6 +99,8 @@ var useContainerQueries = function (propQueries) {
|
|
|
173
99
|
}, DEFAULT_DEBOUNCE_MS);
|
|
174
100
|
// Callback ref for the container element
|
|
175
101
|
var containerRef = useCallback(function (node) {
|
|
102
|
+
// Skip if SSR
|
|
103
|
+
if (!isClient) return;
|
|
176
104
|
// Cleanup previous observer
|
|
177
105
|
if (resizeObserverRef.current) {
|
|
178
106
|
resizeObserverRef.current.disconnect();
|
|
@@ -194,7 +122,7 @@ var useContainerQueries = function (propQueries) {
|
|
|
194
122
|
var width = node.getBoundingClientRect().width;
|
|
195
123
|
setContainerWidth(width);
|
|
196
124
|
}
|
|
197
|
-
}, [debouncedSetContainerWidth]);
|
|
125
|
+
}, [debouncedSetContainerWidth, isClient]);
|
|
198
126
|
// Cleanup on unmount
|
|
199
127
|
useEffect(function () {
|
|
200
128
|
return function () {
|
|
@@ -206,17 +134,17 @@ var useContainerQueries = function (propQueries) {
|
|
|
206
134
|
// Calculate breakpoint matches based on container width
|
|
207
135
|
var breakpointMatches = useMemo(function () {
|
|
208
136
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
137
|
+
isXsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xs,
|
|
138
|
+
isSmOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.sm,
|
|
139
|
+
isMdOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.md,
|
|
140
|
+
isLgOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.lg,
|
|
141
|
+
isXlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xl,
|
|
142
|
+
is2xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['2xl'],
|
|
143
|
+
is3xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['3xl'],
|
|
144
|
+
is4xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['4xl'],
|
|
145
|
+
is5xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['5xl'],
|
|
146
|
+
is6xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['6xl'],
|
|
147
|
+
is7xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['7xl']
|
|
220
148
|
};
|
|
221
149
|
}, [containerWidth]);
|
|
222
150
|
// Calculate custom query matches
|
|
@@ -228,85 +156,9 @@ var useContainerQueries = function (propQueries) {
|
|
|
228
156
|
return acc;
|
|
229
157
|
}, {});
|
|
230
158
|
}, [containerWidth, customQueriesPx]);
|
|
231
|
-
// Helper function to check if container is at exact breakpoint (not larger)
|
|
232
|
-
var isExactBreakpoint = useCallback(function (breakpoint) {
|
|
233
|
-
var sortedBreakpoints = Object.entries(DEFAULT_BREAKPOINTS).sort(function (_a, _b) {
|
|
234
|
-
var a = _a[1];
|
|
235
|
-
var b = _b[1];
|
|
236
|
-
return a - b;
|
|
237
|
-
});
|
|
238
|
-
var currentIndex = sortedBreakpoints.findIndex(function (_a) {
|
|
239
|
-
var key = _a[0];
|
|
240
|
-
return key === breakpoint;
|
|
241
|
-
});
|
|
242
|
-
var nextBreakpoint = sortedBreakpoints[currentIndex + 1];
|
|
243
|
-
var minWidth = DEFAULT_BREAKPOINTS[breakpoint];
|
|
244
|
-
var maxWidth = nextBreakpoint ? nextBreakpoint[1] : Infinity;
|
|
245
|
-
return containerWidth >= minWidth && containerWidth < maxWidth;
|
|
246
|
-
}, [containerWidth]);
|
|
247
|
-
// Create helper components for Tailwind breakpoints
|
|
248
|
-
var components = useMemo(function () {
|
|
249
|
-
return __assign({
|
|
250
|
-
'XsOnly': function (props) {
|
|
251
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('xs') && props.children);
|
|
252
|
-
},
|
|
253
|
-
'SmOnly': function (props) {
|
|
254
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('sm') && props.children);
|
|
255
|
-
},
|
|
256
|
-
'MdOnly': function (props) {
|
|
257
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('md') && props.children);
|
|
258
|
-
},
|
|
259
|
-
'LgOnly': function (props) {
|
|
260
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('lg') && props.children);
|
|
261
|
-
},
|
|
262
|
-
'XlOnly': function (props) {
|
|
263
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('xl') && props.children);
|
|
264
|
-
},
|
|
265
|
-
'2xlOnly': function (props) {
|
|
266
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('2xl') && props.children);
|
|
267
|
-
},
|
|
268
|
-
'3xlOnly': function (props) {
|
|
269
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('3xl') && props.children);
|
|
270
|
-
},
|
|
271
|
-
'4xlOnly': function (props) {
|
|
272
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('4xl') && props.children);
|
|
273
|
-
},
|
|
274
|
-
'5xlOnly': function (props) {
|
|
275
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('5xl') && props.children);
|
|
276
|
-
},
|
|
277
|
-
'6xlOnly': function (props) {
|
|
278
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('6xl') && props.children);
|
|
279
|
-
},
|
|
280
|
-
'7xlOnly': function (props) {
|
|
281
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, isExactBreakpoint('7xl') && props.children);
|
|
282
|
-
},
|
|
283
|
-
'XsOrLarger': function (props) {
|
|
284
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, breakpointMatches.isXs && props.children);
|
|
285
|
-
},
|
|
286
|
-
'SmOrLarger': function (props) {
|
|
287
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, breakpointMatches.isSm && props.children);
|
|
288
|
-
},
|
|
289
|
-
'MdOrLarger': function (props) {
|
|
290
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, breakpointMatches.isMd && props.children);
|
|
291
|
-
},
|
|
292
|
-
'LgOrLarger': function (props) {
|
|
293
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, breakpointMatches.isLg && props.children);
|
|
294
|
-
},
|
|
295
|
-
'XlOrLarger': function (props) {
|
|
296
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, breakpointMatches.isXl && props.children);
|
|
297
|
-
}
|
|
298
|
-
}, Object.keys(customQueriesPx).reduce(function (acc, key) {
|
|
299
|
-
var componentName = key.charAt(0).toUpperCase() + key.slice(1);
|
|
300
|
-
acc[componentName] = function (props) {
|
|
301
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, customMatches[key] && props.children);
|
|
302
|
-
};
|
|
303
|
-
return acc;
|
|
304
|
-
}, {}));
|
|
305
|
-
}, [breakpointMatches, customMatches, isExactBreakpoint, customQueriesPx]);
|
|
306
159
|
return {
|
|
307
160
|
containerRef: containerRef,
|
|
308
|
-
queries: __assign(__assign({}, breakpointMatches), customMatches)
|
|
309
|
-
components: components
|
|
161
|
+
queries: __assign(__assign({}, breakpointMatches), customMatches)
|
|
310
162
|
};
|
|
311
163
|
};
|
|
312
164
|
export { useContainerQueries };
|
|
@@ -1,41 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type React from 'react';
|
|
2
2
|
type Props = Record<string, string>;
|
|
3
|
-
type GenericChildrenType = {
|
|
4
|
-
children?: ReactNode;
|
|
5
|
-
};
|
|
6
3
|
type ContainerQueries = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
isXsOrLarger: boolean;
|
|
5
|
+
isSmOrLarger: boolean;
|
|
6
|
+
isMdOrLarger: boolean;
|
|
7
|
+
isLgOrLarger: boolean;
|
|
8
|
+
isXlOrLarger: boolean;
|
|
9
|
+
is2xlOrLarger: boolean;
|
|
10
|
+
is3xlOrLarger: boolean;
|
|
11
|
+
is4xlOrLarger: boolean;
|
|
12
|
+
is5xlOrLarger: boolean;
|
|
13
|
+
is6xlOrLarger: boolean;
|
|
14
|
+
is7xlOrLarger: boolean;
|
|
18
15
|
[key: string]: boolean;
|
|
19
16
|
};
|
|
20
|
-
type ContainerComponents = {
|
|
21
|
-
'XsOnly': (props: GenericChildrenType) => JSX.Element;
|
|
22
|
-
'SmOnly': (props: GenericChildrenType) => JSX.Element;
|
|
23
|
-
'MdOnly': (props: GenericChildrenType) => JSX.Element;
|
|
24
|
-
'LgOnly': (props: GenericChildrenType) => JSX.Element;
|
|
25
|
-
'XlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
26
|
-
'2xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
27
|
-
'3xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
28
|
-
'4xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
29
|
-
'5xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
30
|
-
'6xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
31
|
-
'7xlOnly': (props: GenericChildrenType) => JSX.Element;
|
|
32
|
-
'XsOrLarger': (props: GenericChildrenType) => JSX.Element;
|
|
33
|
-
'SmOrLarger': (props: GenericChildrenType) => JSX.Element;
|
|
34
|
-
'MdOrLarger': (props: GenericChildrenType) => JSX.Element;
|
|
35
|
-
'LgOrLarger': (props: GenericChildrenType) => JSX.Element;
|
|
36
|
-
'XlOrLarger': (props: GenericChildrenType) => JSX.Element;
|
|
37
|
-
[key: string]: (props: GenericChildrenType) => JSX.Element;
|
|
38
|
-
};
|
|
39
17
|
/**
|
|
40
18
|
* A React hook for responding to container size changes using Tailwind CSS container query breakpoints.
|
|
41
19
|
*
|
|
@@ -47,21 +25,17 @@ type ContainerComponents = {
|
|
|
47
25
|
*
|
|
48
26
|
* @returns An object containing:
|
|
49
27
|
* - containerRef: A ref to attach to your container element
|
|
50
|
-
* - queries: Boolean flags for each breakpoint (
|
|
51
|
-
* - components: React components for conditional rendering (XsOnly, SmOrLarger, etc.)
|
|
28
|
+
* - queries: Boolean flags for each breakpoint (isXsOrLarger, isSmOrLarger, isMdOrLarger, etc.) and custom queries
|
|
52
29
|
*
|
|
53
30
|
* @example
|
|
54
31
|
* ```tsx
|
|
55
32
|
* const MyComponent = () => {
|
|
56
|
-
* const { containerRef, queries
|
|
57
|
-
* const { MdOrLarger } = components
|
|
33
|
+
* const { containerRef, queries } = useContainerQueries()
|
|
58
34
|
*
|
|
59
35
|
* return (
|
|
60
36
|
* <div ref={containerRef}>
|
|
61
|
-
* {queries.
|
|
62
|
-
* <
|
|
63
|
-
* <p>Medium or larger container</p>
|
|
64
|
-
* </MdOrLarger>
|
|
37
|
+
* {queries.isSmOrLarger && <p>Small container</p>}
|
|
38
|
+
* {queries.isMdOrLarger && <p>Medium or larger container</p>}
|
|
65
39
|
* </div>
|
|
66
40
|
* )
|
|
67
41
|
* }
|
|
@@ -69,16 +43,15 @@ type ContainerComponents = {
|
|
|
69
43
|
*
|
|
70
44
|
* @example With custom queries
|
|
71
45
|
* ```tsx
|
|
72
|
-
* const { containerRef, queries
|
|
46
|
+
* const { containerRef, queries } = useContainerQueries({
|
|
73
47
|
* compact: '400px',
|
|
74
48
|
* spacious: '800px',
|
|
75
49
|
* })
|
|
76
|
-
* const { Compact, Spacious } = components
|
|
77
50
|
*
|
|
78
51
|
* return (
|
|
79
52
|
* <div ref={containerRef}>
|
|
80
|
-
* <
|
|
81
|
-
* <
|
|
53
|
+
* {queries.compact && <p>Compact view</p>}
|
|
54
|
+
* {queries.spacious && <p>Spacious view</p>}
|
|
82
55
|
* </div>
|
|
83
56
|
* )
|
|
84
57
|
* ```
|
|
@@ -86,6 +59,5 @@ type ContainerComponents = {
|
|
|
86
59
|
export declare const useContainerQueries: (propQueries?: Props) => {
|
|
87
60
|
containerRef: React.RefCallback<HTMLElement>;
|
|
88
61
|
queries: ContainerQueries;
|
|
89
|
-
components: ContainerComponents;
|
|
90
62
|
};
|
|
91
63
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "0.0.0-canary-useContainerQuery-
|
|
3
|
+
"version": "0.0.0-canary-useContainerQuery-20251125222037",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -4,22 +4,16 @@ import { vi } from 'vitest'
|
|
|
4
4
|
import { useContainerQueries } from './useContainerQueries'
|
|
5
5
|
|
|
6
6
|
const ExampleComponent = (): JSX.Element => {
|
|
7
|
-
const { containerRef, queries
|
|
7
|
+
const { containerRef, queries } = useContainerQueries({
|
|
8
8
|
compact: '400px',
|
|
9
9
|
wide: '800px',
|
|
10
10
|
})
|
|
11
|
-
const { MdOrLarger, Compact } = components
|
|
12
11
|
|
|
13
12
|
return (
|
|
14
13
|
<div ref={containerRef} data-testid="container">
|
|
15
|
-
{queries.
|
|
16
|
-
<
|
|
17
|
-
<button type="button">Medium or larger component</button>
|
|
18
|
-
</MdOrLarger>
|
|
14
|
+
{queries.isSmOrLarger && <button type="button">Small query boolean</button>}
|
|
15
|
+
{queries.isMdOrLarger && <button type="button">Medium or larger query</button>}
|
|
19
16
|
{queries.compact && <button type="button">Compact query boolean</button>}
|
|
20
|
-
<Compact>
|
|
21
|
-
<button type="button">Compact component</button>
|
|
22
|
-
</Compact>
|
|
23
17
|
</div>
|
|
24
18
|
)
|
|
25
19
|
}
|
|
@@ -129,7 +123,7 @@ describe('useContainerQueries()', () => {
|
|
|
129
123
|
// Initially at 300px, should not show sm (384px) or md (448px) content
|
|
130
124
|
expect(screen.queryByRole('button', { name: /Small query boolean/i })).not.toBeInTheDocument()
|
|
131
125
|
expect(
|
|
132
|
-
screen.queryByRole('button', { name: /Medium or larger
|
|
126
|
+
screen.queryByRole('button', { name: /Medium or larger query/i }),
|
|
133
127
|
).not.toBeInTheDocument()
|
|
134
128
|
|
|
135
129
|
// Trigger resize to 400px (sm breakpoint is 384px)
|
|
@@ -141,7 +135,7 @@ describe('useContainerQueries()', () => {
|
|
|
141
135
|
|
|
142
136
|
expect(screen.queryByRole('button', { name: /Small query boolean/i })).toBeInTheDocument()
|
|
143
137
|
expect(
|
|
144
|
-
screen.queryByRole('button', { name: /Medium or larger
|
|
138
|
+
screen.queryByRole('button', { name: /Medium or larger query/i }),
|
|
145
139
|
).not.toBeInTheDocument()
|
|
146
140
|
|
|
147
141
|
// Trigger resize to 500px (md breakpoint is 448px)
|
|
@@ -152,9 +146,7 @@ describe('useContainerQueries()', () => {
|
|
|
152
146
|
})
|
|
153
147
|
|
|
154
148
|
expect(screen.queryByRole('button', { name: /Small query boolean/i })).toBeInTheDocument()
|
|
155
|
-
expect(
|
|
156
|
-
screen.queryByRole('button', { name: /Medium or larger component/i }),
|
|
157
|
-
).toBeInTheDocument()
|
|
149
|
+
expect(screen.queryByRole('button', { name: /Medium or larger query/i })).toBeInTheDocument()
|
|
158
150
|
})
|
|
159
151
|
|
|
160
152
|
it('shows and hides content based on custom queries', async () => {
|
|
@@ -178,7 +170,6 @@ describe('useContainerQueries()', () => {
|
|
|
178
170
|
|
|
179
171
|
// Initially at 300px, custom 'compact' query (400px) should not match
|
|
180
172
|
expect(screen.queryByRole('button', { name: /Compact query boolean/i })).not.toBeInTheDocument()
|
|
181
|
-
expect(screen.queryByRole('button', { name: /Compact component/i })).not.toBeInTheDocument()
|
|
182
173
|
|
|
183
174
|
// Trigger resize to 450px (compact is 400px)
|
|
184
175
|
await act(async () => {
|
|
@@ -188,7 +179,6 @@ describe('useContainerQueries()', () => {
|
|
|
188
179
|
})
|
|
189
180
|
|
|
190
181
|
expect(screen.queryByRole('button', { name: /Compact query boolean/i })).toBeInTheDocument()
|
|
191
|
-
expect(screen.queryByRole('button', { name: /Compact component/i })).toBeInTheDocument()
|
|
192
182
|
})
|
|
193
183
|
|
|
194
184
|
it('returns SSR-safe defaults when window is undefined', () => {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import { useDebouncedCallback } from 'use-debounce'
|
|
4
4
|
|
|
5
5
|
type Props = Record<string, string>
|
|
6
|
-
type GenericChildrenType = { children?: ReactNode }
|
|
7
6
|
|
|
8
7
|
const DEFAULT_DEBOUNCE_MS = 500
|
|
9
8
|
|
|
@@ -38,45 +37,21 @@ const parseBreakpointValue = (value: string): number => {
|
|
|
38
37
|
return parseFloat(value)
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
type HelperComponentProps = {
|
|
42
|
-
children?: ReactNode
|
|
43
|
-
}
|
|
44
|
-
|
|
45
40
|
type ContainerQueries = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
isXsOrLarger: boolean
|
|
42
|
+
isSmOrLarger: boolean
|
|
43
|
+
isMdOrLarger: boolean
|
|
44
|
+
isLgOrLarger: boolean
|
|
45
|
+
isXlOrLarger: boolean
|
|
46
|
+
is2xlOrLarger: boolean
|
|
47
|
+
is3xlOrLarger: boolean
|
|
48
|
+
is4xlOrLarger: boolean
|
|
49
|
+
is5xlOrLarger: boolean
|
|
50
|
+
is6xlOrLarger: boolean
|
|
51
|
+
is7xlOrLarger: boolean
|
|
57
52
|
[key: string]: boolean
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
type ContainerComponents = {
|
|
61
|
-
'XsOnly': (props: GenericChildrenType) => JSX.Element
|
|
62
|
-
'SmOnly': (props: GenericChildrenType) => JSX.Element
|
|
63
|
-
'MdOnly': (props: GenericChildrenType) => JSX.Element
|
|
64
|
-
'LgOnly': (props: GenericChildrenType) => JSX.Element
|
|
65
|
-
'XlOnly': (props: GenericChildrenType) => JSX.Element
|
|
66
|
-
'2xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
67
|
-
'3xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
68
|
-
'4xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
69
|
-
'5xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
70
|
-
'6xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
71
|
-
'7xlOnly': (props: GenericChildrenType) => JSX.Element
|
|
72
|
-
'XsOrLarger': (props: GenericChildrenType) => JSX.Element
|
|
73
|
-
'SmOrLarger': (props: GenericChildrenType) => JSX.Element
|
|
74
|
-
'MdOrLarger': (props: GenericChildrenType) => JSX.Element
|
|
75
|
-
'LgOrLarger': (props: GenericChildrenType) => JSX.Element
|
|
76
|
-
'XlOrLarger': (props: GenericChildrenType) => JSX.Element
|
|
77
|
-
[key: string]: (props: GenericChildrenType) => JSX.Element
|
|
78
|
-
}
|
|
79
|
-
|
|
80
55
|
/**
|
|
81
56
|
* A React hook for responding to container size changes using Tailwind CSS container query breakpoints.
|
|
82
57
|
*
|
|
@@ -88,21 +63,17 @@ type ContainerComponents = {
|
|
|
88
63
|
*
|
|
89
64
|
* @returns An object containing:
|
|
90
65
|
* - containerRef: A ref to attach to your container element
|
|
91
|
-
* - queries: Boolean flags for each breakpoint (
|
|
92
|
-
* - components: React components for conditional rendering (XsOnly, SmOrLarger, etc.)
|
|
66
|
+
* - queries: Boolean flags for each breakpoint (isXsOrLarger, isSmOrLarger, isMdOrLarger, etc.) and custom queries
|
|
93
67
|
*
|
|
94
68
|
* @example
|
|
95
69
|
* ```tsx
|
|
96
70
|
* const MyComponent = () => {
|
|
97
|
-
* const { containerRef, queries
|
|
98
|
-
* const { MdOrLarger } = components
|
|
71
|
+
* const { containerRef, queries } = useContainerQueries()
|
|
99
72
|
*
|
|
100
73
|
* return (
|
|
101
74
|
* <div ref={containerRef}>
|
|
102
|
-
* {queries.
|
|
103
|
-
* <
|
|
104
|
-
* <p>Medium or larger container</p>
|
|
105
|
-
* </MdOrLarger>
|
|
75
|
+
* {queries.isSmOrLarger && <p>Small container</p>}
|
|
76
|
+
* {queries.isMdOrLarger && <p>Medium or larger container</p>}
|
|
106
77
|
* </div>
|
|
107
78
|
* )
|
|
108
79
|
* }
|
|
@@ -110,16 +81,15 @@ type ContainerComponents = {
|
|
|
110
81
|
*
|
|
111
82
|
* @example With custom queries
|
|
112
83
|
* ```tsx
|
|
113
|
-
* const { containerRef, queries
|
|
84
|
+
* const { containerRef, queries } = useContainerQueries({
|
|
114
85
|
* compact: '400px',
|
|
115
86
|
* spacious: '800px',
|
|
116
87
|
* })
|
|
117
|
-
* const { Compact, Spacious } = components
|
|
118
88
|
*
|
|
119
89
|
* return (
|
|
120
90
|
* <div ref={containerRef}>
|
|
121
|
-
* <
|
|
122
|
-
* <
|
|
91
|
+
* {queries.compact && <p>Compact view</p>}
|
|
92
|
+
* {queries.spacious && <p>Spacious view</p>}
|
|
123
93
|
* </div>
|
|
124
94
|
* )
|
|
125
95
|
* ```
|
|
@@ -129,46 +99,8 @@ export const useContainerQueries = (
|
|
|
129
99
|
): {
|
|
130
100
|
containerRef: React.RefCallback<HTMLElement>
|
|
131
101
|
queries: ContainerQueries
|
|
132
|
-
components: ContainerComponents
|
|
133
102
|
} => {
|
|
134
|
-
|
|
135
|
-
if (typeof window === 'undefined') {
|
|
136
|
-
return {
|
|
137
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
138
|
-
containerRef: () => {},
|
|
139
|
-
queries: {
|
|
140
|
-
isXs: false,
|
|
141
|
-
isSm: false,
|
|
142
|
-
isMd: false,
|
|
143
|
-
isLg: false,
|
|
144
|
-
isXl: false,
|
|
145
|
-
is2xl: false,
|
|
146
|
-
is3xl: false,
|
|
147
|
-
is4xl: false,
|
|
148
|
-
is5xl: false,
|
|
149
|
-
is6xl: false,
|
|
150
|
-
is7xl: true, // Default to largest for SSR
|
|
151
|
-
},
|
|
152
|
-
components: {
|
|
153
|
-
'XsOnly': () => <></>,
|
|
154
|
-
'SmOnly': () => <></>,
|
|
155
|
-
'MdOnly': () => <></>,
|
|
156
|
-
'LgOnly': () => <></>,
|
|
157
|
-
'XlOnly': () => <></>,
|
|
158
|
-
'2xlOnly': () => <></>,
|
|
159
|
-
'3xlOnly': () => <></>,
|
|
160
|
-
'4xlOnly': () => <></>,
|
|
161
|
-
'5xlOnly': () => <></>,
|
|
162
|
-
'6xlOnly': () => <></>,
|
|
163
|
-
'7xlOnly': (props: HelperComponentProps) => <>{props.children}</>,
|
|
164
|
-
'XsOrLarger': (props: HelperComponentProps) => <>{props.children}</>,
|
|
165
|
-
'SmOrLarger': (props: HelperComponentProps) => <>{props.children}</>,
|
|
166
|
-
'MdOrLarger': (props: HelperComponentProps) => <>{props.children}</>,
|
|
167
|
-
'LgOrLarger': (props: HelperComponentProps) => <>{props.children}</>,
|
|
168
|
-
'XlOrLarger': (props: HelperComponentProps) => <>{props.children}</>,
|
|
169
|
-
},
|
|
170
|
-
}
|
|
171
|
-
}
|
|
103
|
+
const isClient = typeof window !== 'undefined'
|
|
172
104
|
|
|
173
105
|
// Parse custom queries
|
|
174
106
|
const customQueriesPx = useMemo(
|
|
@@ -197,6 +129,9 @@ export const useContainerQueries = (
|
|
|
197
129
|
// Callback ref for the container element
|
|
198
130
|
const containerRef = useCallback(
|
|
199
131
|
(node: HTMLElement | null) => {
|
|
132
|
+
// Skip if SSR
|
|
133
|
+
if (!isClient) return
|
|
134
|
+
|
|
200
135
|
// Cleanup previous observer
|
|
201
136
|
if (resizeObserverRef.current) {
|
|
202
137
|
resizeObserverRef.current.disconnect()
|
|
@@ -220,7 +155,7 @@ export const useContainerQueries = (
|
|
|
220
155
|
setContainerWidth(width)
|
|
221
156
|
}
|
|
222
157
|
},
|
|
223
|
-
[debouncedSetContainerWidth],
|
|
158
|
+
[debouncedSetContainerWidth, isClient],
|
|
224
159
|
)
|
|
225
160
|
|
|
226
161
|
// Cleanup on unmount
|
|
@@ -236,17 +171,17 @@ export const useContainerQueries = (
|
|
|
236
171
|
// Calculate breakpoint matches based on container width
|
|
237
172
|
const breakpointMatches = useMemo(
|
|
238
173
|
() => ({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
174
|
+
isXsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xs,
|
|
175
|
+
isSmOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.sm,
|
|
176
|
+
isMdOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.md,
|
|
177
|
+
isLgOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.lg,
|
|
178
|
+
isXlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xl,
|
|
179
|
+
is2xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['2xl'],
|
|
180
|
+
is3xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['3xl'],
|
|
181
|
+
is4xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['4xl'],
|
|
182
|
+
is5xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['5xl'],
|
|
183
|
+
is6xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['6xl'],
|
|
184
|
+
is7xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['7xl'],
|
|
250
185
|
}),
|
|
251
186
|
[containerWidth],
|
|
252
187
|
)
|
|
@@ -264,68 +199,8 @@ export const useContainerQueries = (
|
|
|
264
199
|
[containerWidth, customQueriesPx],
|
|
265
200
|
)
|
|
266
201
|
|
|
267
|
-
// Helper function to check if container is at exact breakpoint (not larger)
|
|
268
|
-
const isExactBreakpoint = useCallback(
|
|
269
|
-
(breakpoint: keyof typeof DEFAULT_BREAKPOINTS): boolean => {
|
|
270
|
-
const sortedBreakpoints = Object.entries(DEFAULT_BREAKPOINTS).sort(([, a], [, b]) => a - b)
|
|
271
|
-
const currentIndex = sortedBreakpoints.findIndex(([key]) => key === breakpoint)
|
|
272
|
-
const nextBreakpoint = sortedBreakpoints[currentIndex + 1]
|
|
273
|
-
|
|
274
|
-
const minWidth = DEFAULT_BREAKPOINTS[breakpoint]
|
|
275
|
-
const maxWidth = nextBreakpoint ? nextBreakpoint[1] : Infinity
|
|
276
|
-
|
|
277
|
-
return containerWidth >= minWidth && containerWidth < maxWidth
|
|
278
|
-
},
|
|
279
|
-
[containerWidth],
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
// Create helper components for Tailwind breakpoints
|
|
283
|
-
const components = useMemo(
|
|
284
|
-
() => ({
|
|
285
|
-
'XsOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('xs') && props.children}</>,
|
|
286
|
-
'SmOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('sm') && props.children}</>,
|
|
287
|
-
'MdOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('md') && props.children}</>,
|
|
288
|
-
'LgOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('lg') && props.children}</>,
|
|
289
|
-
'XlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('xl') && props.children}</>,
|
|
290
|
-
'2xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('2xl') && props.children}</>,
|
|
291
|
-
'3xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('3xl') && props.children}</>,
|
|
292
|
-
'4xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('4xl') && props.children}</>,
|
|
293
|
-
'5xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('5xl') && props.children}</>,
|
|
294
|
-
'6xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('6xl') && props.children}</>,
|
|
295
|
-
'7xlOnly': (props: HelperComponentProps) => <>{isExactBreakpoint('7xl') && props.children}</>,
|
|
296
|
-
'XsOrLarger': (props: HelperComponentProps) => (
|
|
297
|
-
<>{breakpointMatches.isXs && props.children}</>
|
|
298
|
-
),
|
|
299
|
-
'SmOrLarger': (props: HelperComponentProps) => (
|
|
300
|
-
<>{breakpointMatches.isSm && props.children}</>
|
|
301
|
-
),
|
|
302
|
-
'MdOrLarger': (props: HelperComponentProps) => (
|
|
303
|
-
<>{breakpointMatches.isMd && props.children}</>
|
|
304
|
-
),
|
|
305
|
-
'LgOrLarger': (props: HelperComponentProps) => (
|
|
306
|
-
<>{breakpointMatches.isLg && props.children}</>
|
|
307
|
-
),
|
|
308
|
-
'XlOrLarger': (props: HelperComponentProps) => (
|
|
309
|
-
<>{breakpointMatches.isXl && props.children}</>
|
|
310
|
-
),
|
|
311
|
-
// Custom query components
|
|
312
|
-
...Object.keys(customQueriesPx).reduce(
|
|
313
|
-
(acc, key) => {
|
|
314
|
-
const componentName = key.charAt(0).toUpperCase() + key.slice(1)
|
|
315
|
-
acc[componentName] = (props: HelperComponentProps): JSX.Element => (
|
|
316
|
-
<>{customMatches[key] && props.children}</>
|
|
317
|
-
)
|
|
318
|
-
return acc
|
|
319
|
-
},
|
|
320
|
-
{} as Record<string, (props: GenericChildrenType) => JSX.Element>,
|
|
321
|
-
),
|
|
322
|
-
}),
|
|
323
|
-
[breakpointMatches, customMatches, isExactBreakpoint, customQueriesPx],
|
|
324
|
-
)
|
|
325
|
-
|
|
326
202
|
return {
|
|
327
203
|
containerRef,
|
|
328
204
|
queries: { ...breakpointMatches, ...customMatches },
|
|
329
|
-
components,
|
|
330
205
|
}
|
|
331
206
|
}
|