@stackshift-ui/blog 6.0.5 → 6.0.7-beta.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/src/blog_b.tsx ADDED
@@ -0,0 +1,160 @@
1
+ import { Button } from "@stackshift-ui/button";
2
+ import { Container } from "@stackshift-ui/container";
3
+ import { Flex } from "@stackshift-ui/flex";
4
+ import { Heading } from "@stackshift-ui/heading";
5
+ import { Image } from "@stackshift-ui/image";
6
+ import { Link } from "@stackshift-ui/link";
7
+ import { Section } from "@stackshift-ui/section";
8
+ import { Text } from "@stackshift-ui/text";
9
+ import { format } from "date-fns";
10
+ import { BlogProps } from ".";
11
+ import { useMediaQuery } from "./hooks/useMediaQuery";
12
+ import { BlogPost, LabeledRoute } from "./types";
13
+
14
+ export default function Blog_B({ subtitle, title, posts, primaryButton }: BlogProps) {
15
+ const blogsPerPage = 5;
16
+ const count = 0;
17
+
18
+ return (
19
+ <Section className="py-20 bg-background">
20
+ <Container maxWidth={1280}>
21
+ <SubtitleAndTitle subtitle={subtitle} title={title} />
22
+ <BlogPosts posts={posts} count={count} blogsPerPage={blogsPerPage} />
23
+ <PrimaryButton primaryButton={primaryButton} />
24
+ </Container>
25
+ </Section>
26
+ );
27
+ }
28
+
29
+ function SubtitleAndTitle({ subtitle, title }: { subtitle?: string; title?: string }) {
30
+ return (
31
+ <div className="w-full mb-16 text-center">
32
+ {subtitle ? (
33
+ <Text weight="bold" className="text-secondary">
34
+ {subtitle}
35
+ </Text>
36
+ ) : null}
37
+ {title ? <Heading fontSize="3xl">{title}</Heading> : null}
38
+ </div>
39
+ );
40
+ }
41
+
42
+ function BlogPosts({
43
+ posts,
44
+ count,
45
+ blogsPerPage,
46
+ }: {
47
+ posts?: BlogPost[];
48
+ count: number;
49
+ blogsPerPage: number;
50
+ }) {
51
+ return (
52
+ <Flex wrap justify="center" className="mb-16" gap={4}>
53
+ <div className="w-full lg:w-[45%]">
54
+ {posts
55
+ ?.slice(count, count + 1)
56
+ .map((post, key) => <BlogItem size="lg" post={post} key={key} />)}
57
+ </div>
58
+ <Flex wrap className="w-full lg:w-[45%]" gap={4}>
59
+ {posts?.slice(count + 1, blogsPerPage).map((post, key) => (
60
+ <div className="w-full lg:basis-[45%]" key={key}>
61
+ <BlogItem post={post} size="sm" key={key} />
62
+ </div>
63
+ ))}
64
+ </Flex>
65
+ </Flex>
66
+ );
67
+ }
68
+
69
+ function BlogItem({ post, size, key }: { post: BlogPost; size?: string; key: number }) {
70
+ const breakpoints = useMediaQuery("1024");
71
+
72
+ return (
73
+ <div className="overflow-hidden rounded-md shadow">
74
+ {post?.mainImage ? (
75
+ <ImageContainer post={post} size={size} breakpoints={breakpoints} key={key} />
76
+ ) : null}
77
+ <div className="p-6 bg-white flex flex-col justify-between" style={{ height: "295px" }}>
78
+ <div>
79
+ {post?.publishedAt ? (
80
+ <Text muted className="text-sm">
81
+ {format(new Date(post.publishedAt), " dd MMM, yyyy")}
82
+ </Text>
83
+ ) : null}
84
+ {post?.title ? (
85
+ <Heading type="h4" className="my-2">
86
+ {post.title.length > 25 ? `${post.title.substring(0, 25)}...` : post.title}
87
+ </Heading>
88
+ ) : null}
89
+ {post?.excerpt ? (
90
+ <Text muted className="mb-6 text-justify">
91
+ {post.excerpt.length > 41 ? `${post.excerpt.substring(0, 41)}...` : post.excerpt}
92
+ </Text>
93
+ ) : null}
94
+ </div>
95
+ {post?.link ? (
96
+ <Link
97
+ aria-label="View Blog Post"
98
+ className="font-bold text-primary hover:text-secondary"
99
+ href={`/${post?.link}`}>
100
+ View Blog Post
101
+ </Link>
102
+ ) : null}
103
+ </div>
104
+ </div>
105
+ );
106
+ }
107
+
108
+ function ImageContainer({
109
+ post,
110
+ size,
111
+ breakpoints,
112
+ key,
113
+ }: {
114
+ post: BlogPost;
115
+ size?: string;
116
+ breakpoints: boolean;
117
+ key: number;
118
+ }) {
119
+ return (
120
+ <>
121
+ {breakpoints ? (
122
+ <Image
123
+ className="object-cover w-full overflow-hidden"
124
+ src={`${post.mainImage}`}
125
+ sizes="100vw"
126
+ style={{ width: "100%", height: "auto", objectFit: "cover" }}
127
+ width={271}
128
+ height={248}
129
+ alt={post?.mainImage ?? `blog-variantB-image-${key}`}
130
+ />
131
+ ) : (
132
+ <div className={`${size === "lg" ? "h-[44.5rem]" : "h-[12.5rem]"}`}>
133
+ <Image
134
+ className="object-cover w-full overflow-hidden rounded-t-md"
135
+ src={`${post.mainImage}`}
136
+ sizes="100vw"
137
+ style={{ width: "100%", height: "100%", objectFit: "cover" }}
138
+ width={271}
139
+ height={248}
140
+ alt={`blog-variantB-image-${post.title}`}
141
+ />
142
+ </div>
143
+ )}
144
+ </>
145
+ );
146
+ }
147
+
148
+ function PrimaryButton({ primaryButton }: { primaryButton?: LabeledRoute }) {
149
+ if (!primaryButton?.label) return null;
150
+
151
+ return (
152
+ <div className="text-center">
153
+ <Button as="link" link={primaryButton} ariaLabel={primaryButton?.label}>
154
+ {primaryButton?.label}
155
+ </Button>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ export { Blog_B };
package/src/blog_c.tsx ADDED
@@ -0,0 +1,142 @@
1
+ import { Badge } from "@stackshift-ui/badge";
2
+ import { Button } from "@stackshift-ui/button";
3
+ import { Container } from "@stackshift-ui/container";
4
+ import { Flex } from "@stackshift-ui/flex";
5
+ import { Heading } from "@stackshift-ui/heading";
6
+ import { Image } from "@stackshift-ui/image";
7
+ import { Link } from "@stackshift-ui/link";
8
+ import { Section } from "@stackshift-ui/section";
9
+ import { Text } from "@stackshift-ui/text";
10
+ import { format } from "date-fns";
11
+ import React from "react";
12
+ import { BlogProps } from ".";
13
+ import { useMediaQuery } from "./hooks/useMediaQuery";
14
+ import { BlogPost, LabeledRoute } from "./types";
15
+
16
+ export default function Blog_C({ subtitle, title, posts, primaryButton }: BlogProps) {
17
+ let blogsPerPage = 3;
18
+
19
+ return (
20
+ <Section className="py-20 bg-background">
21
+ <Container maxWidth={1280}>
22
+ <Flex align="center" justify="between" className="flex-col mb-16 md:flex-row" gap={4}>
23
+ <SubtitleAndTitleText subtitle={subtitle} title={title} />
24
+ <PrimaryButton primaryButton={primaryButton} />
25
+ </Flex>
26
+ <BlogPosts posts={posts} blogsPerPage={blogsPerPage} />
27
+ <PrimaryButton primaryButton={primaryButton} />
28
+ </Container>
29
+ </Section>
30
+ );
31
+ }
32
+
33
+ function SubtitleAndTitleText({ subtitle, title }: { subtitle?: string; title?: string }) {
34
+ return (
35
+ <div className="text-center md:text-left">
36
+ {subtitle ? (
37
+ <Text weight="bold" className="text-primary">
38
+ {subtitle}
39
+ </Text>
40
+ ) : null}
41
+ {title ? <Heading fontSize="3xl">{title}</Heading> : null}
42
+ </div>
43
+ );
44
+ }
45
+
46
+ function BlogPosts({ posts, blogsPerPage }: { posts?: BlogPost[]; blogsPerPage?: number }) {
47
+ if (!posts) return null;
48
+ return (
49
+ <div>
50
+ {posts?.slice(0, blogsPerPage)?.map((post, key) => (
51
+ <div className="flex flex-wrap mb-8 overflow-hidden rounded-md shadow" key={key}>
52
+ <BlogItem
53
+ post={post}
54
+ className={`${key % 2 === 0 ? "flex-row" : "flex-row-reverse"}`}
55
+ key={key}
56
+ />
57
+ </div>
58
+ ))}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ function BlogItem({ post, className, key }: { key: number; post: BlogPost; className?: string }) {
64
+ const breakpoints = useMediaQuery("1100");
65
+ const maxExcerptLength = breakpoints ? 70 : 200;
66
+
67
+ return (
68
+ <Flex wrap className={`bg-white overflow-hidden rounded-lg shadow w-full ${className}`}>
69
+ {post?.mainImage && (
70
+ <Image
71
+ className="object-cover w-full h-auto rounded-l lg:w-1/2"
72
+ src={`${post?.mainImage}`}
73
+ sizes="100vw"
74
+ width={554}
75
+ height={416}
76
+ alt={post?.alt ?? `blog-variantC-image-${key}`}
77
+ />
78
+ )}
79
+ <div className="w-full px-6 py-6 rounded-r lg:w-1/2 lg:pt-10">
80
+ <Flex gap={2}>
81
+ {post?.categories &&
82
+ post?.categories?.map((category, index) => (
83
+ <Badge className=" bg-secondary-foreground text-primary" key={index}>
84
+ {category?.title}
85
+ </Badge>
86
+ ))}
87
+ </Flex>
88
+
89
+ {post?.publishedAt && (
90
+ <Text muted className="m-1">
91
+ {format(new Date(post?.publishedAt), " dd MMM, yyyy")}
92
+ </Text>
93
+ )}
94
+ {post?.title && (
95
+ <Heading className="my-4" type="h3">
96
+ {post?.title?.length > 40 ? post?.title?.substring(0, 40) + "..." : post?.title}
97
+ </Heading>
98
+ )}
99
+ {post?.authors && (
100
+ <div className="flex mb-10 flex-wrap">
101
+ <span className="italic text-primary">By&nbsp;</span>
102
+ {post?.authors?.map((author, index, { length }) => (
103
+ <>
104
+ <Text className="italic text-primary">{author?.name}</Text>
105
+ {index + 1 !== length ? <span>&nbsp;,&nbsp;</span> : null}
106
+ </>
107
+ ))}
108
+ </div>
109
+ )}
110
+ {post?.excerpt && (
111
+ <Text muted className="mb-6 leading-loose text-justify">
112
+ {post?.excerpt?.length > maxExcerptLength
113
+ ? post?.excerpt?.substring(0, maxExcerptLength) + "..."
114
+ : post?.excerpt}
115
+ </Text>
116
+ )}
117
+ {post?.link && (
118
+ <Link
119
+ aria-label="View Blog Post"
120
+ className="font-bold text-primary hover:text-primary-foreground"
121
+ href={`/${post?.link}`}>
122
+ View Blog Post
123
+ </Link>
124
+ )}
125
+ </div>
126
+ </Flex>
127
+ );
128
+ }
129
+
130
+ function PrimaryButton({ primaryButton }: { primaryButton?: LabeledRoute }) {
131
+ if (!primaryButton?.label) return null;
132
+
133
+ return (
134
+ <React.Fragment>
135
+ <Button as="link" link={primaryButton} ariaLabel={primaryButton?.label}>
136
+ {primaryButton?.label}
137
+ </Button>
138
+ </React.Fragment>
139
+ );
140
+ }
141
+
142
+ export { Blog_C };
package/src/blog_d.tsx ADDED
@@ -0,0 +1,350 @@
1
+ import { Button } from "@stackshift-ui/button";
2
+ import { Card } from "@stackshift-ui/card";
3
+ import { Container } from "@stackshift-ui/container";
4
+ import { Flex } from "@stackshift-ui/flex";
5
+ import { Heading } from "@stackshift-ui/heading";
6
+ import { Image } from "@stackshift-ui/image";
7
+ import { Input } from "@stackshift-ui/input";
8
+ import { Link } from "@stackshift-ui/link";
9
+ import { Section } from "@stackshift-ui/section";
10
+ import { Text } from "@stackshift-ui/text";
11
+ import { format } from "date-fns";
12
+ import React from "react";
13
+ import { BlogProps } from ".";
14
+ import { Author, BlogPost, SanityBody } from "./types";
15
+
16
+ interface BlogPostProps extends SanityBody {
17
+ category?: string;
18
+ title?: string;
19
+ slug?: {
20
+ _type: "slug";
21
+ current: string;
22
+ };
23
+ excerpt?: string;
24
+ publishedAt?: string;
25
+ mainImage?: string;
26
+ authors?: Author[];
27
+ }
28
+
29
+ export default function Blog_D({ subtitle, title, posts }: BlogProps) {
30
+ const [activeTab, setActiveTab] = React.useState<string>("All");
31
+ const [currentPage, setCurrentPage] = React.useState<number>(1);
32
+ const [searchQuery, setSearchQuery] = React.useState<string>("");
33
+ let blogsPerPage = 6;
34
+
35
+ React.useEffect(() => {
36
+ setCurrentPage(1);
37
+ }, [activeTab]);
38
+
39
+ const transformedPosts: BlogPostProps[] = (posts ?? []).flatMap(post =>
40
+ (post?.categories ?? []).map(
41
+ category =>
42
+ ({
43
+ category: category?.title,
44
+ title: post?.title,
45
+ slug: post?.slug,
46
+ excerpt: post?.excerpt,
47
+ publishedAt: post?.publishedAt,
48
+ mainImage: post?.mainImage,
49
+ authors: post?.authors,
50
+ }) as BlogPostProps,
51
+ ),
52
+ );
53
+
54
+ // get all categories
55
+ const categories: string[] = transformedPosts?.reduce((newArr: any[], items: BlogPostProps) => {
56
+ const titles = items?.category;
57
+
58
+ if (newArr.indexOf(titles) === -1) {
59
+ newArr.push(titles);
60
+ }
61
+ return newArr;
62
+ }, []);
63
+
64
+ // filtered posts per category
65
+ const filteredPosts =
66
+ activeTab === "All"
67
+ ? posts?.filter(post => post?.title?.toLowerCase().includes(searchQuery.toLowerCase()))
68
+ : transformedPosts.filter(
69
+ item =>
70
+ item?.category === activeTab &&
71
+ item?.title?.toLowerCase().includes(searchQuery.toLowerCase()),
72
+ );
73
+
74
+ //Pagination
75
+ const indexOfLastPost = currentPage * blogsPerPage;
76
+ const indexOfFirstPost = indexOfLastPost - blogsPerPage;
77
+ const currentPosts = filteredPosts?.slice(indexOfFirstPost, indexOfLastPost);
78
+
79
+ //Change page
80
+ const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
81
+
82
+ const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
83
+ setSearchQuery(e.target.value);
84
+ setActiveTab("All");
85
+ setCurrentPage(1);
86
+ };
87
+
88
+ return (
89
+ <Section className="py-20 bg-background">
90
+ <Container maxWidth={1280}>
91
+ <SubtitleAndTitleText subtitle={subtitle} title={title} />
92
+ <SearchInput handleSearchChange={handleSearchChange} />
93
+ <Flex wrap>
94
+ <CategoryTab categories={categories} activeTab={activeTab} setActiveTab={setActiveTab} />
95
+ {filteredPosts?.length === 0 ? (
96
+ <NoPostsMessage />
97
+ ) : (
98
+ <PostItems
99
+ currentPosts={currentPosts}
100
+ activeTab={activeTab}
101
+ blogsPerPage={blogsPerPage}
102
+ />
103
+ )}
104
+ </Flex>
105
+ <Pagination
106
+ blogsPerPage={blogsPerPage}
107
+ totalBlogs={filteredPosts?.length as number}
108
+ paginate={paginate}
109
+ currentPage={currentPage}
110
+ />
111
+ </Container>
112
+ </Section>
113
+ );
114
+ }
115
+
116
+ function NoPostsMessage({ message = "No post available." }) {
117
+ return <div className="w-full px-3 lg:w-3/4 font-medium text-lg">{message}</div>;
118
+ }
119
+
120
+ function SubtitleAndTitleText({ subtitle, title }: { subtitle?: string; title?: string }) {
121
+ return (
122
+ <div className="w-full mb-16">
123
+ {subtitle ? (
124
+ <Text weight={"bold"} className="text-secondary">
125
+ {subtitle}
126
+ </Text>
127
+ ) : null}
128
+ {title ? <Heading fontSize="3xl">{title}</Heading> : null}
129
+ </div>
130
+ );
131
+ }
132
+
133
+ function SearchInput({
134
+ handleSearchChange,
135
+ }: {
136
+ handleSearchChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
137
+ }) {
138
+ return (
139
+ <div className="relative mb-5 w-full lg:w-1/4">
140
+ <Input
141
+ type="text"
142
+ aria-label="Search, find any question you want to ask..."
143
+ className="w-full bg-white border rounded-global font-heading focus:border-gray-500 focus:outline-none"
144
+ placeholder="Search posts..."
145
+ onChange={handleSearchChange}
146
+ />
147
+ <Button
148
+ as="button"
149
+ variant="unstyled"
150
+ ariaLabel="Search button"
151
+ className="absolute right-0 top-0 h-full px-3 bg-white border-r rounded-global text-primary flex items-center">
152
+ <svg
153
+ className="w-6 h-6"
154
+ fill="none"
155
+ stroke="currentColor"
156
+ viewBox="0 0 24 24"
157
+ xmlns="http://www.w3.org/2000/svg">
158
+ <path
159
+ strokeLinecap="round"
160
+ strokeLinejoin="round"
161
+ strokeWidth={2}
162
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
163
+ />
164
+ </svg>
165
+ </Button>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ function CategoryTab({
171
+ categories,
172
+ activeTab,
173
+ setActiveTab,
174
+ }: {
175
+ categories?: string[];
176
+ activeTab: string;
177
+ setActiveTab: (category: string) => void;
178
+ }) {
179
+ return (
180
+ <Card className="w-full px-3 mb-8 bg-white lg:mb-0 lg:w-1/4" borderRadius="md">
181
+ {categories && (
182
+ <React.Fragment>
183
+ <Heading
184
+ type="h3"
185
+ muted
186
+ weight={"bold"}
187
+ className="mb-4 text-base uppercase lg:text-base">
188
+ Topics
189
+ </Heading>
190
+ <ul>
191
+ {categories?.length > 1 && (
192
+ <CategoryItem activeTab={activeTab} setActiveTab={setActiveTab} category={"All"} />
193
+ )}
194
+ {categories?.map((category, index) => (
195
+ <CategoryItem
196
+ key={index}
197
+ activeTab={activeTab}
198
+ setActiveTab={setActiveTab}
199
+ category={category}
200
+ />
201
+ ))}
202
+ </ul>
203
+ </React.Fragment>
204
+ )}
205
+ </Card>
206
+ );
207
+ }
208
+
209
+ function CategoryItem({
210
+ key,
211
+ activeTab,
212
+ setActiveTab,
213
+ category,
214
+ }: {
215
+ key?: number;
216
+ activeTab: string;
217
+ setActiveTab: (category: string) => void;
218
+ category: string;
219
+ }) {
220
+ return (
221
+ <li key={key}>
222
+ <Button
223
+ as="button"
224
+ variant="unstyled"
225
+ ariaLabel="Show all blog posts"
226
+ className={`mb-4 block ${
227
+ !category ? "hidden" : "block"
228
+ } px-3 py-2 hover:bg-secondary-foreground focus:outline-none w-full text-left rounded ${
229
+ activeTab === category
230
+ ? "font-bold text-primary focus:outline-none bg-secondary-foreground"
231
+ : null
232
+ }`}
233
+ onClick={() => setActiveTab(category)}>
234
+ {category}
235
+ </Button>
236
+ </li>
237
+ );
238
+ }
239
+
240
+ function PostItems({
241
+ currentPosts,
242
+ activeTab,
243
+ blogsPerPage,
244
+ }: {
245
+ currentPosts?: BlogPost[];
246
+ activeTab: string;
247
+ blogsPerPage: number;
248
+ }) {
249
+ if (!currentPosts) return null;
250
+
251
+ return (
252
+ <div className="w-full px-3 lg:w-3/4">
253
+ {activeTab === "All"
254
+ ? currentPosts?.map((post, index) => <PostItem post={post} key={index} />)
255
+ : currentPosts
256
+ ?.slice(0, blogsPerPage)
257
+ ?.map((post, index) => <PostItem post={post} key={index} />)}
258
+ </div>
259
+ );
260
+ }
261
+
262
+ function PostItem({ post }: { post?: BlogPost }) {
263
+ if (!post) return null;
264
+
265
+ return (
266
+ <Flex wrap className="mb-8 lg:mb-6 bg-white shadow rounded-lg">
267
+ <div className="w-full h-full mb-4 lg:mb-0 lg:w-1/4">
268
+ <Image
269
+ className="object-cover w-full h-full overflow-hidden rounded"
270
+ src={`${post?.mainImage}`}
271
+ sizes="100vw"
272
+ width={188}
273
+ height={129}
274
+ alt={post?.alt ?? `blog-variantD-image-${post?.title}`}
275
+ />
276
+ </div>
277
+ <div className="w-full px-3 py-2 lg:w-3/4">
278
+ {post?.title && (
279
+ <Link
280
+ aria-label={post?.title}
281
+ className="mb-1 text-2xl font-bold hover:text-secondary font-heading"
282
+ href={`/${post?.link ?? "page-not-added"}`}>
283
+ {post?.title.length > 25 ? post?.title?.substring(0, 25) + "..." : post?.title}
284
+ </Link>
285
+ )}
286
+ <Flex wrap align="center" gap={1} className="mb-2 text-sm">
287
+ {post?.authors
288
+ ? post?.authors?.map((author, index, { length }) => (
289
+ <Flex key={index}>
290
+ <Text className="text-primary">{author?.name}</Text>
291
+ {index + 1 !== length ? <span>&nbsp;,&nbsp;</span> : null}
292
+ </Flex>
293
+ ))
294
+ : null}
295
+ {post?.publishedAt && post?.authors ? (
296
+ <span className="mx-2 text-gray-500">•</span>
297
+ ) : null}
298
+ {post?.publishedAt ? (
299
+ <Text muted>{format(new Date(post?.publishedAt), " dd MMM, yyyy")}</Text>
300
+ ) : null}
301
+ </Flex>
302
+ {post?.excerpt ? (
303
+ <Text muted>
304
+ {post?.excerpt.length > 60 ? post?.excerpt.substring(0, 60) + "..." : post?.excerpt}
305
+ </Text>
306
+ ) : null}
307
+ </div>
308
+ </Flex>
309
+ );
310
+ }
311
+
312
+ interface PaginationProps {
313
+ blogsPerPage: number;
314
+ totalBlogs: number;
315
+ paginate: (pageNumber: number) => void;
316
+ currentPage: number;
317
+ }
318
+
319
+ function Pagination({ blogsPerPage, totalBlogs, paginate, currentPage }: PaginationProps) {
320
+ if (!blogsPerPage) return null;
321
+ const pageNumber = [];
322
+
323
+ for (let i = 1; i <= Math.ceil(totalBlogs / blogsPerPage); i++) {
324
+ pageNumber.push(i);
325
+ }
326
+
327
+ return (
328
+ <nav className="mt-4" aria-label="Pagination">
329
+ <ul className="flex space-x-2 justify-end mr-5">
330
+ {pageNumber.map(number => (
331
+ <Button
332
+ variant="unstyled"
333
+ as="button"
334
+ ariaLabel={`Page ${number}`}
335
+ key={number}
336
+ className={`${
337
+ currentPage === number
338
+ ? "bg-secondary-foreground text-gray-500"
339
+ : "bg-white hover:bg-secondary-foreground hover:text-gray-500"
340
+ } text-primary font-medium py-2 px-4 border border-primary rounded-global focus:outline-none`}
341
+ onClick={() => paginate(number)}>
342
+ {number}
343
+ </Button>
344
+ ))}
345
+ </ul>
346
+ </nav>
347
+ );
348
+ }
349
+
350
+ export { Blog_D };
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+
3
+ export const useMediaQuery = (width: string) => {
4
+ const [targetReached, setTargetReached] = React.useState(false);
5
+
6
+ const updateTarget = React.useCallback((e: any) => {
7
+ if (e.matches) {
8
+ setTargetReached(true);
9
+ } else {
10
+ setTargetReached(false);
11
+ }
12
+ }, []);
13
+
14
+ React.useEffect(() => {
15
+ const media = window.matchMedia(`(max-width: ${width}px)`);
16
+ media.addEventListener("change", updateTarget);
17
+
18
+ // Check on mount (callback is not called until a change occurs)
19
+ if (media.matches) {
20
+ setTargetReached(true);
21
+ }
22
+
23
+ return () => media.removeEventListener("change", updateTarget);
24
+ }, []);
25
+
26
+ return targetReached;
27
+ };
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ // component exports
4
+ export * from "./blog";
5
+ export * from "./blog_a";
6
+ export * from "./blog_b";
7
+ export * from "./blog_c";
8
+ export * from "./blog_d";