@stackshift-ui/portfolio 6.0.3 → 6.0.4

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/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@stackshift-ui/portfolio",
3
3
  "description": "",
4
- "version": "6.0.3",
4
+ "version": "6.0.4",
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": {
@@ -28,20 +29,20 @@
28
29
  "typescript": "^5.6.2",
29
30
  "vite-tsconfig-paths": "^5.0.1",
30
31
  "vitest": "^2.1.1",
31
- "@stackshift-ui/eslint-config": "6.0.2",
32
- "@stackshift-ui/typescript-config": "6.0.2"
32
+ "@stackshift-ui/typescript-config": "6.0.2",
33
+ "@stackshift-ui/eslint-config": "6.0.2"
33
34
  },
34
35
  "dependencies": {
35
36
  "classnames": "^2.5.1",
36
37
  "@stackshift-ui/scripts": "6.0.2",
37
- "@stackshift-ui/system": "6.0.2",
38
- "@stackshift-ui/button": "6.0.2",
39
- "@stackshift-ui/text": "6.0.2",
40
- "@stackshift-ui/heading": "6.0.2",
41
- "@stackshift-ui/image": "6.0.2",
42
- "@stackshift-ui/container": "6.0.2",
43
- "@stackshift-ui/section": "6.0.2",
44
- "@stackshift-ui/flex": "6.0.2"
38
+ "@stackshift-ui/text": "6.0.3",
39
+ "@stackshift-ui/image": "6.0.3",
40
+ "@stackshift-ui/section": "6.0.3",
41
+ "@stackshift-ui/container": "6.0.3",
42
+ "@stackshift-ui/flex": "6.0.3",
43
+ "@stackshift-ui/heading": "6.0.3",
44
+ "@stackshift-ui/system": "6.0.3",
45
+ "@stackshift-ui/button": "6.0.3"
45
46
  },
46
47
  "peerDependencies": {
47
48
  "@types/react": "16.8 - 19",
@@ -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 "./portfolio";
5
+ export * from "./portfolio_a";
6
+ export * from "./portfolio_b";
7
+ export * from "./portfolio_c";
8
+ export * from "./portfolio_d";
@@ -0,0 +1,13 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import { afterEach, describe, test } from "vitest";
3
+ import { Portfolio } from "./portfolio";
4
+
5
+ describe.concurrent("portfolio", () => {
6
+ afterEach(cleanup);
7
+
8
+ test.skip("Dummy test - test if renders without errors", ({ expect }) => {
9
+ const clx = "my-class";
10
+ render(<Portfolio />);
11
+ expect(screen.getByTestId("{ kebabCase name }}").classList).toContain(clx);
12
+ });
13
+ });
@@ -0,0 +1,39 @@
1
+ import React, { lazy } from "react";
2
+
3
+ import { LabeledRoute, Portfolios, PortfoliosWithCategories, SectionsProps } from "./types";
4
+
5
+ const Variants = {
6
+ variant_a: lazy(() => import("./portfolio_a")),
7
+ variant_b: lazy(() => import("./portfolio_b")),
8
+ variant_c: lazy(() => import("./portfolio_c")),
9
+ variant_d: lazy(() => import("./portfolio_d")),
10
+ };
11
+
12
+ export interface PortfolioProps {
13
+ caption?: string;
14
+ title?: string;
15
+ portfoliosWithCategory?: PortfoliosWithCategories[];
16
+ portfolios?: Portfolios[];
17
+ primaryButton?: LabeledRoute;
18
+ length?: number;
19
+ }
20
+
21
+ const displayName = "Portfolio";
22
+
23
+ export const Portfolio: React.FC<SectionsProps> = ({ data }) => {
24
+ const variant = data?.variant;
25
+ const Variant = variant && Variants?.[variant as keyof typeof Variants];
26
+
27
+ const props = {
28
+ caption: data?.variants?.subtitle ?? undefined,
29
+ title: data?.variants?.title ?? undefined,
30
+ portfoliosWithCategory: data?.variants?.portfoliosWithCategories ?? undefined,
31
+ portfolios: data?.variants?.portfolios ?? undefined,
32
+ primaryButton: data?.variants?.primaryButton ?? undefined,
33
+ length: data?.variants?.length,
34
+ };
35
+
36
+ return Variant ? <Variant {...props} /> : null;
37
+ };
38
+
39
+ Portfolio.displayName = displayName;
@@ -0,0 +1,146 @@
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
+ primaryButton,
18
+ length = 8,
19
+ }: PortfolioProps): React.JSX.Element {
20
+ const portfolioLength = length; //set initial number of portfolios to display for this variant
21
+ const [activeTab, setActiveTab] = React.useState(portfoliosWithCategory?.[0]?.category); //set the first index category as initial value
22
+
23
+ //creates new array of items filtered by active tab
24
+ const portfoliosPerCategory = portfoliosWithCategory?.find(data => data?.category === activeTab);
25
+
26
+ return (
27
+ <Section className="py-20 bg-background">
28
+ <Container maxWidth={1280}>
29
+ <Container maxWidth={512} className="mb-8 text-center md:mb-16">
30
+ <CaptionAndTitleText caption={caption} title={title} />
31
+ <PortfolioCategories
32
+ categories={portfoliosWithCategory}
33
+ activeTab={activeTab}
34
+ onClickFn={setActiveTab}
35
+ />
36
+ </Container>
37
+ <PortfolioContent
38
+ portfolios={portfoliosPerCategory?.content}
39
+ portfolioLength={portfolioLength}
40
+ />
41
+ <PrimaryButton button={primaryButton} />
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
+ <React.Fragment>
58
+ <Text weight="semibold">{caption}</Text>
59
+ <Heading className="mb-6" fontSize="2xl">
60
+ {title}
61
+ </Heading>
62
+ </React.Fragment>
63
+ );
64
+ }
65
+
66
+ function PrimaryButton({ button }: { button?: LabeledRoute | null }) {
67
+ if (!button?.label) return null;
68
+
69
+ return (
70
+ <div className="text-center">
71
+ <Button as="link" ariaLabel={button?.label} link={button}>
72
+ {button?.label}
73
+ </Button>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ function PortfolioCategories({
79
+ categories,
80
+ activeTab,
81
+ onClickFn,
82
+ }: {
83
+ categories?: PortfoliosWithCategories[] | null;
84
+ activeTab?: string | null;
85
+ onClickFn?: React.Dispatch<React.SetStateAction<string | null | undefined>>;
86
+ }) {
87
+ if (!categories || categories?.length === 0) return null;
88
+
89
+ return (
90
+ <Flex className="inline-flex py-1 text-sm bg-white rounded" wrap>
91
+ {categories?.map((content, index) => (
92
+ <Button
93
+ variant="tab"
94
+ as="button"
95
+ ariaLabel={content?.category ?? `Category button ${index + 1}`}
96
+ key={content?._key}
97
+ isActive={activeTab === content?.category}
98
+ onClick={() => onClickFn?.(content?.category)}>
99
+ {content?.category}
100
+ </Button>
101
+ ))}
102
+ </Flex>
103
+ );
104
+ }
105
+
106
+ function PortfolioContent({
107
+ portfolios,
108
+ portfolioLength,
109
+ }: {
110
+ portfolios?: Content[] | null;
111
+ portfolioLength?: number;
112
+ }) {
113
+ if (!portfolios || portfolios?.length === 0) return null;
114
+
115
+ return (
116
+ <Flex wrap className="mb-8">
117
+ {portfolios?.slice(0, portfolioLength)?.map((content, index: number) => (
118
+ <Flex className="w-full space-x-5 px-4 mb-8 sm:w-1/2 lg:w-1/4" key={content?._key}>
119
+ <div className="relative mx-auto h-[256px] w-[332px] overflow-hidden rounded-global">
120
+ {content?.mainImage?.image && (
121
+ <Image
122
+ className="object-cover w-full h-full"
123
+ src={content?.mainImage?.image}
124
+ alt={content?.mainImage?.alt ?? `portfolio-image-${index}`}
125
+ />
126
+ )}
127
+ <div className="absolute inset-0 z-10 flex items-center justify-center duration-300 bg-slate-900 rounded-lg opacity-0 hover:opacity-75">
128
+ {content?.primaryButton?.label && (
129
+ <Button
130
+ as="link"
131
+ variant="outline"
132
+ ariaLabel={content?.primaryButton?.label}
133
+ link={content?.primaryButton}
134
+ 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">
135
+ {content?.primaryButton?.label}
136
+ </Button>
137
+ )}
138
+ </div>
139
+ </div>
140
+ </Flex>
141
+ ))}
142
+ </Flex>
143
+ );
144
+ }
145
+
146
+ 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-global 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,124 @@
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
71
+ className="h-full overflow-hidden bg-white rounded-global"
72
+ style={{ maxHeight: "600px" }}>
73
+ <Image
74
+ className="object-cover w-full h-80"
75
+ src={content?.mainImage?.image}
76
+ width={480}
77
+ height={320}
78
+ alt={content?.mainImage?.alt ?? `portfolio-image`}
79
+ />
80
+ <div className="p-6">
81
+ <Text muted>{content?.dateAdded}</Text>
82
+ {content?.title && (
83
+ <Text weight="bold" fontSize="xl" className="mb-4">
84
+ {content?.title?.length > 31
85
+ ? `${content?.title?.substring(0, 31)}...`
86
+ : content?.title}
87
+ </Text>
88
+ )}
89
+ {content?.primaryButton?.label && (
90
+ <Button
91
+ as="link"
92
+ variant="link"
93
+ ariaLabel={content?.primaryButton?.label}
94
+ link={content?.primaryButton}
95
+ className="font-bold no-underline text-primary transition-200 hover:text-primary/50">
96
+ {content?.primaryButton?.label}
97
+ </Button>
98
+ )}
99
+ </div>
100
+ </div>
101
+ )}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function PortfolioContent({
107
+ portfolios,
108
+ portfolioLength,
109
+ }: {
110
+ portfolios?: Content[] | null;
111
+ portfolioLength?: number;
112
+ }) {
113
+ if (!portfolios || portfolios?.length === 0) return null;
114
+
115
+ return (
116
+ <Flex wrap className="mb-4">
117
+ {portfolios
118
+ ?.slice(0, portfolioLength)
119
+ .map(content => <ProjectItem content={content} key={content._key} />)}
120
+ </Flex>
121
+ );
122
+ }
123
+
124
+ export { Portfolio_C };
@@ -0,0 +1,184 @@
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
+ primaryButton,
19
+ length = 6,
20
+ }: PortfolioProps) {
21
+ const portfoliosPerPage = length;
22
+ const count = 0; // default number of portfolios per category
23
+ const [activeTab, setActiveTab] = React.useState(portfoliosWithCategory?.[0]?.category); // set the first index category as initial value
24
+
25
+ // group portfolios per category
26
+ const portfoliosPerCategory = portfoliosWithCategory?.find(data => data?.category === activeTab);
27
+
28
+ return (
29
+ <Section className="py-20 bg-background">
30
+ <Container maxWidth={1280}>
31
+ <Container maxWidth={512} className="mb-8 text-center md:mb-16">
32
+ <CaptionAndTitleText caption={caption} title={title} />
33
+ <PortfolioCategories
34
+ categories={portfoliosWithCategory}
35
+ activeTab={activeTab}
36
+ onClickFn={setActiveTab}
37
+ />
38
+ </Container>
39
+ <div className="mb-12 sm:flex">
40
+ <Flex wrap className="w-full mb-8 lg:mb-0 lg:w-1/2">
41
+ {portfoliosPerCategory?.content
42
+ ?.slice(count, count + 2)
43
+ ?.map(content => <ProjectItem size={"sm"} content={content} key={content._key} />)}
44
+ {portfoliosPerCategory?.content
45
+ ?.slice(count + 2, count + 3)
46
+ ?.map(content => <ProjectItem size={"lg"} content={content} key={content._key} />)}
47
+ </Flex>
48
+ <div className="w-full lg:w-1/2">
49
+ {portfoliosPerCategory?.content
50
+ ?.slice(count + 3, count + 4)
51
+ ?.map(content => <ProjectItem size={"lg"} content={content} key={content._key} />)}
52
+ <div className="flex flex-wrap">
53
+ {portfoliosPerCategory?.content
54
+ ?.slice(count + 4, portfoliosPerPage)
55
+ ?.map(content => <ProjectItem size={"sm"} content={content} key={content._key} />)}
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <PrimaryButton button={primaryButton} />
60
+ </Container>
61
+ </Section>
62
+ );
63
+ }
64
+
65
+ function CaptionAndTitleText({
66
+ caption,
67
+ title,
68
+ }: {
69
+ caption?: string | null;
70
+ title?: string | null;
71
+ }) {
72
+ if (!caption || !title) return null;
73
+
74
+ return (
75
+ <React.Fragment>
76
+ <Text weight="semibold">{caption}</Text>
77
+ <Heading className="mb-6" fontSize="2xl">
78
+ {title}
79
+ </Heading>
80
+ </React.Fragment>
81
+ );
82
+ }
83
+
84
+ function PrimaryButton({ button }: { button?: LabeledRoute | null }) {
85
+ if (!button?.label) return null;
86
+
87
+ return (
88
+ <div className="text-center">
89
+ <Button as="link" ariaLabel={button?.label} link={button} className="text-white">
90
+ {button?.label}
91
+ </Button>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function PortfolioCategories({
97
+ categories,
98
+ activeTab,
99
+ onClickFn,
100
+ }: {
101
+ categories?: PortfoliosWithCategories[] | null;
102
+ activeTab?: string | null;
103
+ onClickFn?: React.Dispatch<React.SetStateAction<string | null | undefined>>;
104
+ }) {
105
+ if (!categories || categories?.length === 0) return null;
106
+
107
+ return (
108
+ <Flex className="inline-flex py-1 text-sm bg-white rounded" wrap>
109
+ {categories?.map((content, index) => (
110
+ <Button
111
+ variant="tab"
112
+ as="button"
113
+ ariaLabel={content?.category ?? `Category button ${index + 1}`}
114
+ key={content?._key}
115
+ isActive={activeTab === content?.category}
116
+ onClick={() => onClickFn?.(content?.category)}>
117
+ {content?.category}
118
+ </Button>
119
+ ))}
120
+ </Flex>
121
+ );
122
+ }
123
+
124
+ function ProjectItem({ size, content }: { size?: string | null; content?: Content }) {
125
+ const breakpoints = useMediaQuery("639");
126
+ const maxLength = breakpoints ? 60 : 90;
127
+
128
+ return (
129
+ <div className={`w-full px-4 mb-8 ${size === "lg" ? "w-full" : "lg:w-1/2"}`}>
130
+ {content?.mainImage?.image && (
131
+ <div className="relative overflow-hidden rounded-global">
132
+ <Image
133
+ className={`object-cover w-full ${size === "lg" ? "h-128" : "h-64"}`}
134
+ src={content?.mainImage?.image}
135
+ //width={352}
136
+ //height={280}
137
+ alt={`portfolio-image-${content?._key}`}
138
+ />
139
+ <div className="absolute inset-0 z-10 justify-center p-6 duration-300 bg-slate-900 rounded-lg opacity-0 hover:opacity-80">
140
+ <div className="max-w-md my-auto text-xs">
141
+ {content?.subtitle && (
142
+ <Text className="text-sm text-primary" weight="bold">
143
+ {content?.subtitle?.length > 26
144
+ ? `${content?.subtitle?.substring(0, 26)}...`
145
+ : content?.subtitle}
146
+ </Text>
147
+ )}
148
+ {content?.title && (
149
+ <Heading
150
+ weight="bold"
151
+ className={`my-5 text-white ${size === "lg" ? "text-sm md:text-xl" : "text-sm"}`}>
152
+ {content?.title?.length > 38
153
+ ? `${content?.title?.substring(0, 38)}...`
154
+ : content?.title}
155
+ </Heading>
156
+ )}
157
+ <div className="max-w-xs my-5">
158
+ {content?.description && (
159
+ <Text fontSize="xs" muted className="mb-6 text-white">
160
+ {content?.description?.length > maxLength
161
+ ? `${content?.description?.substring(0, maxLength)}...`
162
+ : content?.description}
163
+ </Text>
164
+ )}
165
+ {content?.primaryButton?.label && (
166
+ <Button
167
+ as="link"
168
+ variant="outline"
169
+ ariaLabel={content?.primaryButton?.label}
170
+ link={content?.primaryButton}
171
+ 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">
172
+ {content?.primaryButton?.label}
173
+ </Button>
174
+ )}
175
+ </div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+ )}
180
+ </div>
181
+ );
182
+ }
183
+
184
+ export { Portfolio_D };
package/src/types.ts ADDED
@@ -0,0 +1,412 @@
1
+ export type StyleVariants<T extends string> = Record<T, string>;
2
+
3
+ export type Socials = "facebook" | "instagram" | "youtube" | "linkedin" | "twitter";
4
+ export interface MainImage {
5
+ image: string;
6
+ alt?: string;
7
+ }
8
+
9
+ export interface LabeledRoute extends ConditionalLink {
10
+ ariaLabel?: string;
11
+ label?: string;
12
+ linkTarget?: string;
13
+ linkType?: string;
14
+ _type?: string;
15
+ linkInternal?: any;
16
+ }
17
+ export interface ConditionalLink {
18
+ type?: string;
19
+ internalLink?: string | null;
20
+ externalLink?: string | null;
21
+ }
22
+
23
+ export interface StatItems {
24
+ label?: string;
25
+ mainImage?: MainImage;
26
+ value?: string;
27
+ _key?: string;
28
+ _type?: string;
29
+ }
30
+
31
+ export interface Logo extends ConditionalLink {
32
+ alt?: string;
33
+ linkTarget?: string;
34
+ image?: string;
35
+ }
36
+
37
+ export interface Images {
38
+ image?: string;
39
+ _key?: string;
40
+ _type?: string;
41
+ alt?: string;
42
+ }
43
+
44
+ export interface ContactDetails {
45
+ addressInfo?: string;
46
+ contactInfo?: string;
47
+ emailInfo?: string;
48
+ _key?: string;
49
+ }
50
+
51
+ export interface SocialLink {
52
+ socialMedia?: string | null;
53
+ socialMediaLink?: string | null;
54
+ _key?: string | null;
55
+ _type?: string | null;
56
+ socialMediaIcon?: {
57
+ alt?: string;
58
+ image?: string;
59
+ } | null;
60
+ socialMediaPlatform?: string | null;
61
+ }
62
+
63
+ export interface LabeledRouteWithKey extends LabeledRoute {
64
+ _key?: string;
65
+ }
66
+
67
+ export interface ArrayOfImageTitleAndText {
68
+ mainImage?: {
69
+ alt?: string;
70
+ image?: string;
71
+ };
72
+ plainText?: string;
73
+ title?: string;
74
+ _key?: string;
75
+ _type?: string;
76
+ }
77
+
78
+ export interface FeaturedItem {
79
+ description?: string;
80
+ mainImage?: MainImage;
81
+ title?: string;
82
+ subtitle?: string;
83
+ _key?: string;
84
+ _type?: string;
85
+ }
86
+
87
+ export interface ArrayOfTitleAndText {
88
+ _key?: string;
89
+ plainText?: string;
90
+ title?: string;
91
+ }
92
+
93
+ export interface BlogPost extends SanityBody {
94
+ authors?: Author[] | null;
95
+ body?: any;
96
+ categories?: Category[] | null;
97
+ excerpt?: string | null;
98
+ link?: string | null;
99
+ mainImage?: string | null;
100
+ publishedAt?: string;
101
+ seo?: Seo | null;
102
+ slug?: SanitySlug | null;
103
+ title?: string;
104
+ }
105
+
106
+ export interface Seo {
107
+ _type?: string;
108
+ seoTitle?: string;
109
+ seoDescription?: string;
110
+ seoImage?: string;
111
+ seoKeywords?: string;
112
+ seoSynonyms?: string;
113
+ }
114
+
115
+ export interface SanitySlug {
116
+ current?: string;
117
+ _type?: "slug";
118
+ }
119
+
120
+ export interface SanityBody {
121
+ _createdAt?: string;
122
+ _id?: string;
123
+ _rev?: string;
124
+ _type?: string;
125
+ _updatedAt?: string;
126
+ }
127
+
128
+ export interface Author extends SanityBody {
129
+ link?: string | null;
130
+ bio?: string | null;
131
+ name?: string | null;
132
+ slug?: SanitySlug | null;
133
+ image?: string | null;
134
+ profile?: {
135
+ alt: string;
136
+ image: string;
137
+ } | null;
138
+ }
139
+
140
+ export interface Category extends SanityBody {
141
+ title?: string;
142
+ }
143
+
144
+ export interface Form {
145
+ id?: string | null;
146
+ buttonLabel?: string | null;
147
+ name?: string | null;
148
+ subtitle?: string | null;
149
+ fields?: FormFields[] | null;
150
+ thankYouPage?: ThankYouPage | null;
151
+ }
152
+
153
+ export interface FormFields {
154
+ name?: string;
155
+ placeholder?: string;
156
+ pricingType?: string;
157
+ type?: FormTypes;
158
+ _key?: string;
159
+ _type?: string;
160
+ isRequired?: boolean;
161
+ label?: string;
162
+ items?: string[];
163
+ }
164
+
165
+ export type FormTypes =
166
+ | "inputText"
167
+ | "inputEmail"
168
+ | "inputPassword"
169
+ | "inputNumber"
170
+ | "textarea"
171
+ | "inputFile"
172
+ | "inputRadio"
173
+ | "inputCheckbox"
174
+ | "inputSelect";
175
+
176
+ export interface ThankYouPage {
177
+ externalLink?: string | null;
178
+ internalLink?: string | null;
179
+ linkInternal?: any;
180
+ linkTarget?: string;
181
+ linkType?: string;
182
+ type?: string;
183
+ }
184
+
185
+ //Used on different sections
186
+ export interface SectionsProps {
187
+ template?: Template;
188
+ data?: Sections;
189
+ variant?: string | null | undefined;
190
+ schema?: Variants;
191
+ }
192
+
193
+ export interface Sections extends SanityBody {
194
+ label?: string;
195
+ variant?: string;
196
+ variants?: Variants;
197
+ _key?: string;
198
+ }
199
+
200
+ //*EDIT THIS SECTION WHEN CREATING/UPDATING SCHEMAS ON STUDIO */
201
+ export interface Variants {
202
+ template?: Template;
203
+ multipleMenus?: any;
204
+ arrayOfTitleAndText?: ArrayOfTitleAndText[] | null;
205
+ logo?: Logo | null;
206
+ primaryButton?: LabeledRoute | null;
207
+ secondaryButton?: LabeledRoute | null;
208
+ routes?: LabeledRouteWithKey[] | null;
209
+ menu?: LabeledRouteWithKey[] | null;
210
+ plans?: Plans[] | null;
211
+ formLinks?: LabeledRouteWithKey[] | null;
212
+ portfolios?: Portfolios[] | null;
213
+ portfoliosWithCategories?: PortfoliosWithCategories[] | null;
214
+ length?: number;
215
+ signInLink?: LabeledRoute | null;
216
+ signinLink?: LabeledRoute | null;
217
+ tags?: string[] | null;
218
+ posts?: BlogPost[] | null;
219
+ blogsPerPage?: number | null;
220
+ form?: Form | null;
221
+ collections?: Collection | null;
222
+ products?: CollectionProduct | null;
223
+ allProducts?: Collection[];
224
+ subtitle?: string | null;
225
+ caption?: string | null;
226
+ title?: string | null;
227
+ plainText?: string | null;
228
+ contactDescription?: string | null;
229
+ officeInformation?: string | null;
230
+ contactEmail?: string | null;
231
+ contactNumber?: string | null;
232
+ socialLinks?: SocialLink[] | null;
233
+ block?: any;
234
+ heading?: string | null;
235
+ acceptButtonLabel?: string | null;
236
+ declineButtonLabel?: string | null;
237
+ faqsWithCategories?: FaqsWithCategory[] | null;
238
+ faqs?: AskedQuestion[] | null;
239
+ arrayOfImageTitleAndText?: ArrayOfImageTitleAndText[] | null;
240
+ description?: string | null;
241
+ featuredItems?: FeaturedItem[] | null;
242
+ images?: Images[] | null;
243
+ contactDetails?: ContactDetails[] | null;
244
+ copyright?: string | null;
245
+ mainImage?: MainImage | null;
246
+ youtubeLink?: string | null;
247
+ banner?: any;
248
+ stats?: StatItems[] | null;
249
+ teams?: Team[] | null;
250
+ testimonials?: Testimonial[] | null;
251
+ selectStripeAccount?: string;
252
+ annualBilling?: string;
253
+ monthlyBilling?: string;
254
+ productDetails?: ProductDetail[];
255
+ btnLabel?: string;
256
+ selectAccount?: string;
257
+ hashtags?: string[];
258
+ numberOfPosts?: number;
259
+ text?: string;
260
+ button?: LabeledRoute;
261
+ features?: string[];
262
+ config?: {
263
+ enableAnalytics: boolean;
264
+ cookiePolicy?: {
265
+ siteName: string;
266
+ cookiePolicyPage: Reference;
267
+ };
268
+ consentModalPosition?: string;
269
+ };
270
+ contactLink?: LabeledRoute;
271
+ }
272
+
273
+ export interface Template {
274
+ bg?: string;
275
+ color?: string;
276
+ }
277
+
278
+ export type Plans = {
279
+ _key?: string | null;
280
+ _type?: "planItems" | null;
281
+ checkoutButtonName?: string | null;
282
+ description?: string | null;
283
+ monthlyPrice?: string | null;
284
+ planType?: string | null;
285
+ yearlyPrice?: string | null;
286
+ planIncludes?: string[] | null;
287
+ primaryButton?: LabeledRoute | null;
288
+ } & Record<string, string>;
289
+
290
+ export interface Portfolios {
291
+ dateAdded?: string | null;
292
+ mainImage?: {
293
+ image?: string | null;
294
+ alt?: string | null;
295
+ } | null;
296
+ primaryButton?: LabeledRoute | null;
297
+ title?: string | null;
298
+ _key?: string | null;
299
+ _type?: string | null;
300
+ }
301
+
302
+ export interface PortfoliosWithCategories {
303
+ category?: string | null;
304
+ content?: Content[] | null;
305
+ primaryButton?: LabeledRoute | null;
306
+ _key?: string | null;
307
+ _type?: string | null;
308
+ }
309
+
310
+ export interface Content extends Portfolios {
311
+ description?: string | null;
312
+ subtitle?: string | null;
313
+ }
314
+
315
+ export interface Collection extends SanityBody {
316
+ collectionInfoVariant?: {
317
+ variant?: string;
318
+ } | null;
319
+ name?: string | null;
320
+ products?: CollectionProduct[] | null;
321
+ sections?: any; //todo
322
+ seo?: Seo | null;
323
+ slug?: SanitySlug | null;
324
+ }
325
+
326
+ export interface CollectionProduct extends SanityBody {
327
+ compareToPrice?: number | null;
328
+ description?: string | null;
329
+ ecwidProductId?: number | null;
330
+ name?: string | null;
331
+ price?: number | null;
332
+ productInfo?: ProductInfo | null;
333
+ productInfoVariant?: {
334
+ variant?: string;
335
+ } | null;
336
+ sections?: any; //todo
337
+ seo?: Seo | null;
338
+ slug?: SanitySlug | null;
339
+ }
340
+
341
+ //TODO, RECHECK PRODUCT INFO DATA FROM SANITY
342
+ interface ProductInfo {
343
+ btnLabel?: string | null;
344
+ images?: ProductInfoImage[] | null;
345
+ productDetails?: ProductDetail[] | null;
346
+ socialLinks?: SocialLink[] | null;
347
+ subtitle?: string | null;
348
+ }
349
+
350
+ //TODO, RECHECK PRODUCT INFO DATA FROM SANITY
351
+ export interface ProductDetail {
352
+ blockContent?: any;
353
+ contentType?: string;
354
+ tabName?: string;
355
+ _key?: string;
356
+ [key: string]: any;
357
+ }
358
+ interface ProductInfoImage {
359
+ alt?: string | null;
360
+ _key: string;
361
+ _type: string;
362
+ image?: string | null;
363
+ }
364
+
365
+ export interface FaqsWithCategory {
366
+ askedQuestions?: AskedQuestion[] | null;
367
+ category?: string | null;
368
+ _key?: string;
369
+ _type?: string;
370
+ }
371
+
372
+ export interface AskedQuestion {
373
+ answer?: string | null;
374
+ question?: string | null;
375
+ hidden?: boolean;
376
+ _key?: string;
377
+ _type?: string;
378
+ }
379
+
380
+ export interface Team {
381
+ jobTitle?: string;
382
+ mainImage?: MainImage;
383
+ name?: string;
384
+ plainText?: string;
385
+ _key?: string;
386
+ _type?: string;
387
+ }
388
+
389
+ export interface Testimonial {
390
+ jobTitle?: string;
391
+ mainImage?: MainImage;
392
+ name?: string;
393
+ rating?: string;
394
+ testimony?: string;
395
+ _key?: string;
396
+ _type?: string;
397
+ }
398
+
399
+ export declare interface Reference {
400
+ _type: string;
401
+ _ref: string;
402
+ _key?: string;
403
+ _weak?: boolean;
404
+ _strengthenOnPublish?: {
405
+ type: string;
406
+ weak?: boolean;
407
+ template?: {
408
+ id: string;
409
+ params: Record<string, string | number | boolean>;
410
+ };
411
+ };
412
+ }
@@ -1 +0,0 @@
1
- import{Button as v}from"@stackshift-ui/button";import{Container as w}from"@stackshift-ui/container";import{Flex as p}from"@stackshift-ui/flex";import{Heading as h}from"@stackshift-ui/heading";import{Image as k}from"@stackshift-ui/image";import{Section as B}from"@stackshift-ui/section";import{Text as P}from"@stackshift-ui/text";import N from"react";import{jsx as r,jsxs as g}from"react/jsx-runtime";function L({caption:l,title:e,portfoliosWithCategory:i,primaryButton:a,length:s=8}){var x;let m=s,[b,f]=N.useState((x=i==null?void 0:i[0])==null?void 0:x.category),u=i==null?void 0:i.find(d=>(d==null?void 0:d.category)===b);return r(B,{className:"py-20 bg-background",children:g(w,{maxWidth:1280,children:[g(w,{maxWidth:512,className:"mb-8 text-center md:mb-16",children:[r(T,{caption:l,title:e}),r(S,{categories:i,activeTab:b,onClickFn:f})]}),r(A,{portfolios:u==null?void 0:u.content,portfolioLength:m}),r(R,{button:a})]})})}function T({caption:l,title:e}){return!l||!e?null:g(N.Fragment,{children:[r(P,{weight:"semibold",children:l}),r(h,{className:"mb-6",fontSize:"2xl",children:e})]})}function R({button:l}){return l!=null&&l.label?r("div",{className:"text-center",children:r(v,{as:"link",ariaLabel:l==null?void 0:l.label,link:l,children:l==null?void 0:l.label})}):null}function S({categories:l,activeTab:e,onClickFn:i}){return!l||(l==null?void 0:l.length)===0?null:r(p,{className:"inline-flex py-1 text-sm bg-white rounded-global",wrap:!0,children:l==null?void 0:l.map((a,s)=>{var m;return r(v,{variant:"tab",as:"button",ariaLabel:(m=a==null?void 0:a.category)!=null?m:`Category button ${s+1}`,isActive:e===(a==null?void 0:a.category),onClick:()=>i==null?void 0:i(a==null?void 0:a.category),children:a==null?void 0:a.category},a==null?void 0:a._key)})})}function A({portfolios:l,portfolioLength:e}){var i;return!l||(l==null?void 0:l.length)===0?null:r(p,{wrap:!0,className:"mb-8",children:(i=l==null?void 0:l.slice(0,e))==null?void 0:i.map((a,s)=>{var m,b,f,u,x,d,y;return r(p,{className:"w-full space-x-5 px-4 mb-8 sm:w-1/2 lg:w-1/4",children:g("div",{className:"relative mx-auto h-[256px] w-[332px] overflow-hidden rounded-global",children:[((m=a==null?void 0:a.mainImage)==null?void 0:m.image)&&r(k,{className:"object-cover w-full h-full",src:(b=a==null?void 0:a.mainImage)==null?void 0:b.image,alt:(u=(f=a==null?void 0:a.mainImage)==null?void 0:f.alt)!=null?u:`portfolio-image-${s}`}),r("div",{className:"absolute inset-0 z-10 flex items-center justify-center duration-300 bg-slate-900 rounded-lg opacity-0 hover:opacity-75",children:((x=a==null?void 0:a.primaryButton)==null?void 0:x.label)&&r(v,{as:"link",variant:"outline",ariaLabel:(d=a==null?void 0:a.primaryButton)==null?void 0:d.label,link:a==null?void 0:a.primaryButton,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",children:(y=a==null?void 0:a.primaryButton)==null?void 0:y.label})})]})},a==null?void 0:a._key)})})}export{L as a};
@@ -1 +0,0 @@
1
- import{lazy as i}from"react";import{jsx as b}from"react/jsx-runtime";var r={variant_a:i(()=>import("./portfolio_a.mjs")),variant_b:i(()=>import("./portfolio_b.mjs")),variant_c:i(()=>import("./portfolio_c.mjs")),variant_d:i(()=>import("./portfolio_d.mjs"))},h="Portfolio",C=({data:o})=>{var e,s,l,p,f,c,m,u,v,g,y;let n=o==null?void 0:o.variant,t=n&&(r==null?void 0:r[n]),P={caption:(s=(e=o==null?void 0:o.variants)==null?void 0:e.subtitle)!=null?s:void 0,title:(p=(l=o==null?void 0:o.variants)==null?void 0:l.title)!=null?p:void 0,portfoliosWithCategory:(c=(f=o==null?void 0:o.variants)==null?void 0:f.portfoliosWithCategories)!=null?c:void 0,portfolios:(u=(m=o==null?void 0:o.variants)==null?void 0:m.portfolios)!=null?u:void 0,primaryButton:(g=(v=o==null?void 0:o.variants)==null?void 0:v.primaryButton)!=null?g:void 0,length:(y=o==null?void 0:o.variants)==null?void 0:y.length};return t?b(t,{...P}):null};C.displayName=h;export{C as a};