@stackshift-ui/blog 6.0.5 → 6.0.6

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/types.d.ts CHANGED
@@ -181,6 +181,7 @@ export interface Variants {
181
181
  signinLink?: LabeledRoute | null;
182
182
  tags?: string[] | null;
183
183
  posts?: BlogPost[] | null;
184
+ blogPosts?: BlogPost[] | null;
184
185
  blogsPerPage?: number | null;
185
186
  form?: Form | null;
186
187
  collections?: Collection | null;
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@stackshift-ui/blog",
3
3
  "description": "",
4
- "version": "6.0.5",
4
+ "version": "6.0.6",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",
10
10
  "files": [
11
- "dist/**"
11
+ "dist/**",
12
+ "src"
12
13
  ],
13
14
  "author": "WebriQ <info@webriq.com>",
14
15
  "devDependencies": {
@@ -34,19 +35,19 @@
34
35
  },
35
36
  "dependencies": {
36
37
  "classnames": "^2.5.1",
37
- "@stackshift-ui/badge": "6.0.2",
38
- "@stackshift-ui/button": "6.0.2",
39
- "@stackshift-ui/card": "6.0.2",
40
- "@stackshift-ui/container": "6.0.2",
41
- "@stackshift-ui/flex": "6.0.2",
42
- "@stackshift-ui/heading": "6.0.2",
43
- "@stackshift-ui/image": "6.0.2",
44
- "@stackshift-ui/input": "6.0.3",
45
- "@stackshift-ui/link": "6.0.2",
38
+ "@stackshift-ui/container": "6.0.3",
39
+ "@stackshift-ui/button": "6.0.3",
40
+ "@stackshift-ui/flex": "6.0.3",
41
+ "@stackshift-ui/link": "6.0.3",
42
+ "@stackshift-ui/heading": "6.0.3",
46
43
  "@stackshift-ui/scripts": "6.0.2",
47
- "@stackshift-ui/system": "6.0.2",
48
- "@stackshift-ui/section": "6.0.2",
49
- "@stackshift-ui/text": "6.0.2"
44
+ "@stackshift-ui/section": "6.0.3",
45
+ "@stackshift-ui/input": "6.0.4",
46
+ "@stackshift-ui/system": "6.0.3",
47
+ "@stackshift-ui/text": "6.0.3",
48
+ "@stackshift-ui/image": "6.0.3",
49
+ "@stackshift-ui/badge": "6.0.3",
50
+ "@stackshift-ui/card": "6.0.3"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "@stackshift-ui/system": ">=0.0.0",
@@ -0,0 +1,13 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import { afterEach, describe, test } from "vitest";
3
+ import { Blog } from "./blog";
4
+
5
+ describe.concurrent("blog", () => {
6
+ afterEach(cleanup);
7
+
8
+ test.skip("Dummy test - test if renders without errors", ({ expect }) => {
9
+ const clx = "my-class";
10
+ render(<Blog />);
11
+ expect(screen.getByTestId("{ kebabCase name }}").classList).toContain(clx);
12
+ });
13
+ });
package/src/blog.tsx ADDED
@@ -0,0 +1,32 @@
1
+ import { lazy } from "react";
2
+ import { BlogPost, LabeledRoute, SectionsProps } from "./types";
3
+
4
+ const Variants = {
5
+ variant_a: lazy(() => import("./blog_a")),
6
+ variant_b: lazy(() => import("./blog_b")),
7
+ variant_c: lazy(() => import("./blog_c")),
8
+ variant_d: lazy(() => import("./blog_d")),
9
+ };
10
+
11
+ export interface BlogProps {
12
+ subtitle?: string;
13
+ title?: string;
14
+ posts?: BlogPost[];
15
+ primaryButton?: LabeledRoute;
16
+ }
17
+ const displayName = "Blog";
18
+
19
+ export const Blog: React.FC<SectionsProps> = ({ data }) => {
20
+ const variant = data?.variant;
21
+ const Variant = variant && Variants[variant as keyof typeof Variants];
22
+
23
+ const props = {
24
+ subtitle: data?.variants?.subtitle ?? undefined,
25
+ title: data?.variants?.title ?? undefined,
26
+ posts: (data?.variants?.posts || data?.variants?.blogPosts) ?? undefined,
27
+ primaryButton: data?.variants?.primaryButton ?? undefined,
28
+ };
29
+ return Variant ? <Variant {...props} /> : null;
30
+ };
31
+
32
+ Blog.displayName = displayName;
package/src/blog_a.tsx ADDED
@@ -0,0 +1,121 @@
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
+
11
+ import { BlogProps } from ".";
12
+ import { BlogPost, Category, LabeledRoute } from "./types";
13
+
14
+ export default function Blog_A({ subtitle, title, posts, primaryButton }: BlogProps) {
15
+ return (
16
+ <Section className="py-20 bg-background">
17
+ <Container maxWidth={1280}>
18
+ <SubtitleAndTitleText subtitle={subtitle} title={title} />
19
+ <BlogPosts posts={posts} />
20
+ <PrimaryButton primaryButton={primaryButton} />
21
+ </Container>
22
+ </Section>
23
+ );
24
+ }
25
+
26
+ function SubtitleAndTitleText({ subtitle, title }: { subtitle?: string; title?: string }) {
27
+ return (
28
+ <div className="mb-16 text-center">
29
+ {subtitle ? (
30
+ <Text weight="bold" className="text-secondary">
31
+ {subtitle}
32
+ </Text>
33
+ ) : null}
34
+ {title ? <Heading fontSize="3xl">{title}</Heading> : null}
35
+ </div>
36
+ );
37
+ }
38
+
39
+ function BlogPosts({ posts }: { posts?: BlogPost[] }) {
40
+ if (!posts) return null;
41
+
42
+ let blogsPerPage = 6,
43
+ count = 0;
44
+
45
+ return (
46
+ <Flex gap={4} className="flex-col lg:flex-row">
47
+ <div className="w-full space-y-5 lg:w-1/2">
48
+ {posts?.slice(count, count + 1)?.map((post, key) => <BlogItem post={post} key={key} />)}
49
+ <Flex gap={4} className="flex-col lg:flex-row">
50
+ {posts
51
+ ?.slice(count + 1, count + 3)
52
+ ?.map((post, key) => <BlogItem post={post} key={key} />)}
53
+ </Flex>
54
+ </div>
55
+
56
+ <div className="w-full space-y-5 lg:w-1/2">
57
+ <Flex gap={4} className="flex-col lg:flex-row">
58
+ {posts
59
+ ?.slice(count + 3, count + 5)
60
+ ?.map((post, key) => <BlogItem post={post} key={key} />)}
61
+ </Flex>
62
+ {posts
63
+ ?.slice(count + 5, blogsPerPage)
64
+ ?.map((post, key) => <BlogItem post={post} key={key} />)}
65
+ </div>
66
+ </Flex>
67
+ );
68
+ }
69
+
70
+ function BlogItem({ post }: { post?: BlogPost }) {
71
+ return (
72
+ <div className="relative w-full h-64 rounded">
73
+ {post?.mainImage ? (
74
+ <Image
75
+ className="relative object-cover w-full h-full overflow-hidden rounded-global"
76
+ src={`${post?.mainImage}`}
77
+ alt={`blog-variantA-image`}
78
+ sizes="(min-width: 1540px) 740px, (min-width: 1280px) 612px, (min-width: 1040px) 484px, (min-width: 780px) 736px, (min-width: 680px) 608px, calc(94.44vw - 15px)"
79
+ />
80
+ ) : null}
81
+ <div className="absolute inset-0 bg-gray-900 rounded-global opacity-75" />
82
+ <div className="absolute inset-0 flex flex-col items-start p-6">
83
+ {post?.categories ? (
84
+ <div className="absolute flex left-5 top-5">
85
+ {post?.categories?.map((category: Category, index: number) => (
86
+ <span
87
+ className="px-3 py-1 mb-auto mr-3 text-sm font-bold uppercase bg-white rounded-full text-primary"
88
+ key={index}>
89
+ {category?.title}
90
+ </span>
91
+ ))}
92
+ </div>
93
+ ) : null}
94
+ <span className="mt-auto text-sm text-gray-500">
95
+ {post?.publishedAt ? format(new Date(post?.publishedAt), "dd MMM, yyyy") : ""}
96
+ </span>
97
+ {post?.title ? (
98
+ <Link
99
+ className="text-lg font-bold text-white transform hover:scale-110 hover:text-secondary motion-reduce:transform-none"
100
+ href={`/${post?.link}`}>
101
+ {post?.title?.length > 40 ? post?.title.substring(0, 40) + "..." : post?.title}
102
+ </Link>
103
+ ) : null}
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ function PrimaryButton({ primaryButton }: { primaryButton?: LabeledRoute }) {
110
+ if (!primaryButton?.label) return null;
111
+
112
+ return (
113
+ <div className="mt-10 text-center">
114
+ <Button as="link" link={primaryButton} ariaLabel={primaryButton?.label}>
115
+ {primaryButton?.label}
116
+ </Button>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ export { Blog_A };
package/src/blog_b.tsx ADDED
@@ -0,0 +1,158 @@
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" />
62
+ </div>
63
+ ))}
64
+ </Flex>
65
+ </Flex>
66
+ );
67
+ }
68
+
69
+ function BlogItem({ post, size }: { post: BlogPost; size?: string }) {
70
+ const breakpoints = useMediaQuery("1024");
71
+
72
+ return (
73
+ <div className="overflow-hidden rounded-global shadow">
74
+ {post?.mainImage ? (
75
+ <ImageContainer post={post} size={size} breakpoints={breakpoints} />
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
+ }: {
113
+ post: BlogPost;
114
+ size?: string;
115
+ breakpoints: boolean;
116
+ }) {
117
+ return (
118
+ <>
119
+ {breakpoints ? (
120
+ <Image
121
+ className="object-cover w-full overflow-hidden"
122
+ src={`${post.mainImage}`}
123
+ sizes="100vw"
124
+ style={{ width: "100%", height: "auto", objectFit: "cover" }}
125
+ width={271}
126
+ height={248}
127
+ alt={`blog-variantB-image-${post.title}`}
128
+ />
129
+ ) : (
130
+ <div className={`${size === "lg" ? "h-[44.5rem]" : "h-[12.5rem]"}`}>
131
+ <Image
132
+ className="object-cover w-full overflow-hidden rounded-t-global"
133
+ src={`${post.mainImage}`}
134
+ sizes="100vw"
135
+ style={{ width: "100%", height: "100%", objectFit: "cover" }}
136
+ width={271}
137
+ height={248}
138
+ alt={`blog-variantB-image-${post.title}`}
139
+ />
140
+ </div>
141
+ )}
142
+ </>
143
+ );
144
+ }
145
+
146
+ function PrimaryButton({ primaryButton }: { primaryButton?: LabeledRoute }) {
147
+ if (!primaryButton?.label) return null;
148
+
149
+ return (
150
+ <div className="text-center">
151
+ <Button as="link" link={primaryButton} ariaLabel={primaryButton?.label}>
152
+ {primaryButton?.label}
153
+ </Button>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ 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-global 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 }: { 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={`blog-variantC-image-`}
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 };