@stackshift-ui/portfolio 6.0.3 → 6.0.5-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.
@@ -0,0 +1,145 @@
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 { Section } from "@stackshift-ui/section";
7
+ import { Text } from "@stackshift-ui/text";
8
+ import React from "react";
9
+
10
+ import { PortfolioProps } from ".";
11
+ import { Content, LabeledRoute, PortfoliosWithCategories } from "./types";
12
+
13
+ export default function Portfolio_A({
14
+ caption,
15
+ title,
16
+ portfoliosWithCategory,
17
+ length = 8,
18
+ }: PortfolioProps): React.JSX.Element {
19
+ const portfolioLength = length; //set initial number of portfolios to display for this variant
20
+ const [activeTab, setActiveTab] = React.useState(portfoliosWithCategory?.[0]?.category); //set the first index category as initial value
21
+
22
+ //creates new array of items filtered by active tab
23
+ const portfoliosPerCategory = portfoliosWithCategory?.find(data => data?.category === activeTab);
24
+
25
+ return (
26
+ <Section className="py-20 bg-background">
27
+ <Container maxWidth={1280}>
28
+ <Container maxWidth={512} className="mb-8 text-center md:mb-16">
29
+ <CaptionAndTitleText caption={caption} title={title} />
30
+ <PortfolioCategories
31
+ categories={portfoliosWithCategory}
32
+ activeTab={activeTab}
33
+ onClickFn={setActiveTab}
34
+ />
35
+ </Container>
36
+ <PortfolioContent
37
+ portfolios={portfoliosPerCategory?.content}
38
+ portfolioLength={portfolioLength}
39
+ />
40
+ <PrimaryButton button={portfoliosPerCategory?.primaryButton} />
41
+ </Container>
42
+ </Section>
43
+ );
44
+ }
45
+
46
+ function CaptionAndTitleText({
47
+ caption,
48
+ title,
49
+ }: {
50
+ caption?: string | null;
51
+ title?: string | null;
52
+ }) {
53
+ if (!caption || !title) return null;
54
+
55
+ return (
56
+ <React.Fragment>
57
+ <Text weight="semibold">{caption}</Text>
58
+ <Heading className="mb-6" fontSize="2xl">
59
+ {title}
60
+ </Heading>
61
+ </React.Fragment>
62
+ );
63
+ }
64
+
65
+ function PrimaryButton({ button }: { button?: LabeledRoute | null }) {
66
+ if (!button?.label) return null;
67
+
68
+ return (
69
+ <div className="text-center">
70
+ <Button as="link" ariaLabel={button?.label} link={button}>
71
+ {button?.label}
72
+ </Button>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ function PortfolioCategories({
78
+ categories,
79
+ activeTab,
80
+ onClickFn,
81
+ }: {
82
+ categories?: PortfoliosWithCategories[] | null;
83
+ activeTab?: string | null;
84
+ onClickFn?: React.Dispatch<React.SetStateAction<string | null | undefined>>;
85
+ }) {
86
+ if (!categories || categories?.length === 0) return null;
87
+
88
+ return (
89
+ <Flex className="inline-flex py-1 text-sm bg-white rounded" wrap>
90
+ {categories?.map((content, index) => (
91
+ <Button
92
+ variant="tab"
93
+ as="button"
94
+ ariaLabel={content?.category ?? `Category button ${index + 1}`}
95
+ key={content?._key}
96
+ isActive={activeTab === content?.category}
97
+ onClick={() => onClickFn?.(content?.category)}>
98
+ {content?.category}
99
+ </Button>
100
+ ))}
101
+ </Flex>
102
+ );
103
+ }
104
+
105
+ function PortfolioContent({
106
+ portfolios,
107
+ portfolioLength,
108
+ }: {
109
+ portfolios?: Content[] | null;
110
+ portfolioLength?: number;
111
+ }) {
112
+ if (!portfolios || portfolios?.length === 0) return null;
113
+
114
+ return (
115
+ <Flex wrap className="mb-8">
116
+ {portfolios?.slice(0, portfolioLength)?.map((content, index: number) => (
117
+ <Flex className="w-full space-x-5 px-4 mb-8 sm:w-1/2 lg:w-1/4" key={content?._key}>
118
+ <div className="relative mx-auto h-[256px] w-[332px] overflow-hidden rounded-md">
119
+ {content?.mainImage?.image && (
120
+ <Image
121
+ className="object-cover w-full h-full"
122
+ src={content?.mainImage?.image}
123
+ alt={content?.mainImage?.alt ?? `portfolio-image-${index}`}
124
+ />
125
+ )}
126
+ <div className="absolute inset-0 z-10 flex items-center justify-center duration-300 bg-slate-900 rounded-md opacity-0 hover:opacity-75">
127
+ {content?.primaryButton?.label && (
128
+ <Button
129
+ as="link"
130
+ variant="outline"
131
+ ariaLabel={content?.primaryButton?.label}
132
+ link={content?.primaryButton}
133
+ className="bg-transparent border-secondary outline text-white hover:bg-secondary/20 hover:border-secondary/20 inline-block rounded-l-xl rounded-t-xl font-bold transition duration-200 px-3 py-4">
134
+ {content?.primaryButton?.label}
135
+ </Button>
136
+ )}
137
+ </div>
138
+ </div>
139
+ </Flex>
140
+ ))}
141
+ </Flex>
142
+ );
143
+ }
144
+
145
+ export { Portfolio_A };
@@ -0,0 +1,127 @@
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 { Section } from "@stackshift-ui/section";
7
+ import { Text } from "@stackshift-ui/text";
8
+ import React from "react";
9
+
10
+ import { PortfolioProps } from ".";
11
+ import { Content } from "./types";
12
+
13
+ export default function Portfolio_B({
14
+ caption,
15
+ title,
16
+ portfolios,
17
+ primaryButton,
18
+ length = 6,
19
+ }: PortfolioProps): React.JSX.Element {
20
+ const portfolioLength = length; //set initial number of portfolios to display for this variant
21
+
22
+ return (
23
+ <Section className="py-20 bg-background">
24
+ <Container maxWidth={1280}>
25
+ <Flex wrap align="center" justify="center" className="mb-16 md:justify-between">
26
+ <CaptionAndTitleText caption={caption} title={title} />
27
+ <div className="hidden mt-5 text-right md:mt-0 lg:mt-0 lg:block xl:mt-0">
28
+ {primaryButton?.label && (
29
+ <Button as="link" ariaLabel={primaryButton?.label} link={primaryButton}>
30
+ {primaryButton?.label}
31
+ </Button>
32
+ )}
33
+ </div>
34
+ </Flex>
35
+ <PortfolioContent portfolios={portfolios} portfolioLength={portfolioLength} />
36
+ <div className="block mt-5 text-center md:mt-0 lg:mt-0 lg:hidden xl:mt-0">
37
+ {primaryButton?.label && (
38
+ <Button as="link" ariaLabel={primaryButton?.label} link={primaryButton}>
39
+ {primaryButton?.label}
40
+ </Button>
41
+ )}
42
+ </div>
43
+ </Container>
44
+ </Section>
45
+ );
46
+ }
47
+
48
+ function CaptionAndTitleText({
49
+ caption,
50
+ title,
51
+ }: {
52
+ caption?: string | null;
53
+ title?: string | null;
54
+ }) {
55
+ if (!caption || !title) return null;
56
+
57
+ return (
58
+ <div className="text-center lg:text-left">
59
+ <Text weight="semibold">{caption}</Text>
60
+ <Heading className="mb-6" fontSize="2xl">
61
+ {title}
62
+ </Heading>
63
+ </div>
64
+ );
65
+ }
66
+
67
+ function ProjectItem({ content }: { content: Content }) {
68
+ return (
69
+ <div className="w-full px-4 mb-4 md:w-1/2 lg:w-1/3">
70
+ <div className="relative mx-auto overflow-hidden rounded-md md:mb-5">
71
+ {content?.mainImage?.image && (
72
+ <Image
73
+ className="object-cover w-full h-80"
74
+ src={content?.mainImage?.image}
75
+ width={480}
76
+ height={320}
77
+ alt={content?.mainImage?.alt ?? `portfolio-image`}
78
+ />
79
+ )}
80
+
81
+ <Flex
82
+ direction="col"
83
+ align="start"
84
+ className="absolute inset-0 z-10 items-start p-6 duration-300 bg-slate-900 rounded opacity-0 hover:opacity-75">
85
+ <Text className="text-secondary-foreground">{content?.dateAdded}</Text>
86
+ {content?.title && (
87
+ <Text weight="bold" className="mb-auto text-white md:text-xl lg:text-2xl">
88
+ {content?.title?.length > 80
89
+ ? `${content?.title?.substring(0, 80)}...`
90
+ : content?.title}
91
+ </Text>
92
+ )}
93
+ {content?.primaryButton?.label && (
94
+ <Button
95
+ as="link"
96
+ variant="outline"
97
+ ariaLabel={content?.primaryButton?.label}
98
+ link={content?.primaryButton}
99
+ className="bg-transparent border-secondary outline text-white hover:bg-secondary/20 hover:border-secondary/20 inline-block rounded-l-xl rounded-t-xl font-bold transition duration-200 px-3 py-4">
100
+ {content?.primaryButton?.label}
101
+ </Button>
102
+ )}
103
+ </Flex>
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ function PortfolioContent({
110
+ portfolios,
111
+ portfolioLength,
112
+ }: {
113
+ portfolios?: Content[] | null;
114
+ portfolioLength?: number;
115
+ }) {
116
+ if (!portfolios || portfolios?.length === 0) return null;
117
+
118
+ return (
119
+ <Flex wrap className="mb-4 ">
120
+ {portfolios
121
+ ?.slice(0, portfolioLength)
122
+ .map(content => <ProjectItem content={content} key={content._key} />)}
123
+ </Flex>
124
+ );
125
+ }
126
+
127
+ export { Portfolio_B };
@@ -0,0 +1,122 @@
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 { Section } from "@stackshift-ui/section";
7
+ import { Text } from "@stackshift-ui/text";
8
+
9
+ import { PortfolioProps } from ".";
10
+ import { Content } from "./types";
11
+
12
+ export default function Portfolio_C({
13
+ caption,
14
+ title,
15
+ portfolios,
16
+ primaryButton,
17
+ length = 6,
18
+ }: PortfolioProps) {
19
+ const portfolioLength = length; //set initial number of portfolios to display for this variant
20
+
21
+ return (
22
+ <Section className="py-20 bg-background">
23
+ <Container maxWidth={1280}>
24
+ <Flex wrap align="center" justify="center" className="mb-16 md:justify-between">
25
+ <CaptionAndTitleText caption={caption} title={title} />
26
+ <div className="hidden mt-5 text-right md:mt-0 lg:mt-0 lg:block xl:mt-0">
27
+ {primaryButton?.label && (
28
+ <Button as="link" ariaLabel={primaryButton?.label} link={primaryButton}>
29
+ {primaryButton?.label}
30
+ </Button>
31
+ )}
32
+ </div>
33
+ </Flex>
34
+ <PortfolioContent portfolios={portfolios} portfolioLength={portfolioLength} />
35
+ <div className="block mt-5 text-center md:mt-0 lg:mt-0 lg:hidden xl:mt-0">
36
+ {primaryButton?.label && (
37
+ <Button as="link" ariaLabel={primaryButton?.label} link={primaryButton}>
38
+ {primaryButton?.label}
39
+ </Button>
40
+ )}
41
+ </div>
42
+ </Container>
43
+ </Section>
44
+ );
45
+ }
46
+
47
+ function CaptionAndTitleText({
48
+ caption,
49
+ title,
50
+ }: {
51
+ caption?: string | null;
52
+ title?: string | null;
53
+ }) {
54
+ if (!caption || !title) return null;
55
+
56
+ return (
57
+ <div className="text-center lg:text-left">
58
+ <Text weight="semibold">{caption}</Text>
59
+ <Heading className="mb-6" fontSize="2xl">
60
+ {title}
61
+ </Heading>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ function ProjectItem({ content }: { content: Content }) {
67
+ return (
68
+ <div className="relative w-full px-4 mb-8 md:w-1/2 lg:w-1/3">
69
+ {content?.mainImage?.image && (
70
+ <div className="h-full overflow-hidden bg-white rounded-md" style={{ maxHeight: "600px" }}>
71
+ <Image
72
+ className="object-cover w-full h-80"
73
+ src={content?.mainImage?.image}
74
+ width={480}
75
+ height={320}
76
+ alt={content?.mainImage?.alt ?? `portfolio-image`}
77
+ />
78
+ <div className="p-6">
79
+ <Text muted>{content?.dateAdded}</Text>
80
+ {content?.title && (
81
+ <Text weight="bold" fontSize="xl" className="mb-4">
82
+ {content?.title?.length > 31
83
+ ? `${content?.title?.substring(0, 31)}...`
84
+ : content?.title}
85
+ </Text>
86
+ )}
87
+ {content?.primaryButton?.label && (
88
+ <Button
89
+ as="link"
90
+ variant="link"
91
+ ariaLabel={content?.primaryButton?.label}
92
+ link={content?.primaryButton}
93
+ className="font-bold no-underline text-primary transition-200 hover:text-primary/50">
94
+ {content?.primaryButton?.label}
95
+ </Button>
96
+ )}
97
+ </div>
98
+ </div>
99
+ )}
100
+ </div>
101
+ );
102
+ }
103
+
104
+ function PortfolioContent({
105
+ portfolios,
106
+ portfolioLength,
107
+ }: {
108
+ portfolios?: Content[] | null;
109
+ portfolioLength?: number;
110
+ }) {
111
+ if (!portfolios || portfolios?.length === 0) return null;
112
+
113
+ return (
114
+ <Flex wrap className="mb-4">
115
+ {portfolios
116
+ ?.slice(0, portfolioLength)
117
+ .map(content => <ProjectItem content={content} key={content._key} />)}
118
+ </Flex>
119
+ );
120
+ }
121
+
122
+ export { Portfolio_C };
@@ -0,0 +1,183 @@
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 { Section } from "@stackshift-ui/section";
7
+ import { Text } from "@stackshift-ui/text";
8
+ import React from "react";
9
+
10
+ import { PortfolioProps } from ".";
11
+ import { useMediaQuery } from "./helper";
12
+ import { Content, LabeledRoute, PortfoliosWithCategories } from "./types";
13
+
14
+ export default function Portfolio_D({
15
+ caption,
16
+ title,
17
+ portfoliosWithCategory,
18
+ length = 6,
19
+ }: PortfolioProps) {
20
+ const portfoliosPerPage = length;
21
+ const count = 0; // default number of portfolios per category
22
+ const [activeTab, setActiveTab] = React.useState(portfoliosWithCategory?.[0]?.category); // set the first index category as initial value
23
+
24
+ // group portfolios per category
25
+ const portfoliosPerCategory = portfoliosWithCategory?.find(data => data?.category === activeTab);
26
+
27
+ return (
28
+ <Section className="py-20 bg-background">
29
+ <Container maxWidth={1280}>
30
+ <Container maxWidth={512} className="mb-8 text-center md:mb-16">
31
+ <CaptionAndTitleText caption={caption} title={title} />
32
+ <PortfolioCategories
33
+ categories={portfoliosWithCategory}
34
+ activeTab={activeTab}
35
+ onClickFn={setActiveTab}
36
+ />
37
+ </Container>
38
+ <div className="mb-12 sm:flex">
39
+ <Flex wrap className="w-full mb-8 lg:mb-0 lg:w-1/2">
40
+ {portfoliosPerCategory?.content
41
+ ?.slice(count, count + 2)
42
+ ?.map(content => <ProjectItem size={"sm"} content={content} key={content._key} />)}
43
+ {portfoliosPerCategory?.content
44
+ ?.slice(count + 2, count + 3)
45
+ ?.map(content => <ProjectItem size={"lg"} content={content} key={content._key} />)}
46
+ </Flex>
47
+ <div className="w-full lg:w-1/2">
48
+ {portfoliosPerCategory?.content
49
+ ?.slice(count + 3, count + 4)
50
+ ?.map(content => <ProjectItem size={"lg"} content={content} key={content._key} />)}
51
+ <div className="flex flex-wrap">
52
+ {portfoliosPerCategory?.content
53
+ ?.slice(count + 4, portfoliosPerPage)
54
+ ?.map(content => <ProjectItem size={"sm"} content={content} key={content._key} />)}
55
+ </div>
56
+ </div>
57
+ </div>
58
+ <PrimaryButton button={portfoliosPerCategory?.primaryButton} />
59
+ </Container>
60
+ </Section>
61
+ );
62
+ }
63
+
64
+ function CaptionAndTitleText({
65
+ caption,
66
+ title,
67
+ }: {
68
+ caption?: string | null;
69
+ title?: string | null;
70
+ }) {
71
+ if (!caption || !title) return null;
72
+
73
+ return (
74
+ <React.Fragment>
75
+ <Text weight="semibold">{caption}</Text>
76
+ <Heading className="mb-6" fontSize="2xl">
77
+ {title}
78
+ </Heading>
79
+ </React.Fragment>
80
+ );
81
+ }
82
+
83
+ function PrimaryButton({ button }: { button?: LabeledRoute | null }) {
84
+ if (!button?.label) return null;
85
+
86
+ return (
87
+ <div className="text-center">
88
+ <Button as="link" ariaLabel={button?.label} link={button}>
89
+ {button?.label}
90
+ </Button>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ function PortfolioCategories({
96
+ categories,
97
+ activeTab,
98
+ onClickFn,
99
+ }: {
100
+ categories?: PortfoliosWithCategories[] | null;
101
+ activeTab?: string | null;
102
+ onClickFn?: React.Dispatch<React.SetStateAction<string | null | undefined>>;
103
+ }) {
104
+ if (!categories || categories?.length === 0) return null;
105
+
106
+ return (
107
+ <Flex className="inline-flex py-1 text-sm bg-white rounded" wrap>
108
+ {categories?.map((content, index) => (
109
+ <Button
110
+ variant="tab"
111
+ as="button"
112
+ ariaLabel={content?.category ?? `Category button ${index + 1}`}
113
+ key={content?._key}
114
+ isActive={activeTab === content?.category}
115
+ onClick={() => onClickFn?.(content?.category)}>
116
+ {content?.category}
117
+ </Button>
118
+ ))}
119
+ </Flex>
120
+ );
121
+ }
122
+
123
+ function ProjectItem({ size, content }: { size?: string | null; content?: Content }) {
124
+ const breakpoints = useMediaQuery("639");
125
+ const maxLength = breakpoints ? 60 : 90;
126
+
127
+ return (
128
+ <div className={`w-full px-4 mb-8 ${size === "lg" ? "w-full" : "lg:w-1/2"}`}>
129
+ {content?.mainImage?.image && (
130
+ <div className="relative overflow-hidden rounded-md">
131
+ <Image
132
+ className={`object-cover w-full ${size === "lg" ? "h-128" : "h-64"}`}
133
+ src={content?.mainImage?.image}
134
+ //width={352}
135
+ //height={280}
136
+ alt={content?.mainImage?.alt ?? `portfolio-image-${content?._key}`}
137
+ />
138
+ <div className="absolute inset-0 z-10 justify-center p-6 duration-300 bg-slate-900 rounded-md opacity-0 hover:opacity-80">
139
+ <div className="max-w-md my-auto text-xs">
140
+ {content?.subtitle && (
141
+ <Text className="text-sm text-primary" weight="bold">
142
+ {content?.subtitle?.length > 26
143
+ ? `${content?.subtitle?.substring(0, 26)}...`
144
+ : content?.subtitle}
145
+ </Text>
146
+ )}
147
+ {content?.title && (
148
+ <Heading
149
+ weight="bold"
150
+ className={`my-5 text-white ${size === "lg" ? "text-sm md:text-xl" : "text-sm"}`}>
151
+ {content?.title?.length > 38
152
+ ? `${content?.title?.substring(0, 38)}...`
153
+ : content?.title}
154
+ </Heading>
155
+ )}
156
+ <div className="max-w-xs my-5">
157
+ {content?.description && (
158
+ <Text fontSize="xs" muted className="mb-6 text-white">
159
+ {content?.description?.length > maxLength
160
+ ? `${content?.description?.substring(0, maxLength)}...`
161
+ : content?.description}
162
+ </Text>
163
+ )}
164
+ {content?.primaryButton?.label && (
165
+ <Button
166
+ as="link"
167
+ variant="outline"
168
+ ariaLabel={content?.primaryButton?.label}
169
+ link={content?.primaryButton}
170
+ className="bg-transparent border-secondary outline text-white hover:bg-secondary/20 hover:border-secondary/20 inline-block rounded-l-xl rounded-t-xl font-bold transition duration-200 px-3 py-4">
171
+ {content?.primaryButton?.label}
172
+ </Button>
173
+ )}
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ )}
179
+ </div>
180
+ );
181
+ }
182
+
183
+ export { Portfolio_D };