diff --git a/package-lock.json b/package-lock.json
index 17df74f..46ccfb4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,9 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^6.26.0"
+ "react-router-dom": "^6.26.0",
+ "react-slick": "^0.30.2",
+ "slick-carousel": "^1.8.1"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.6.1",
@@ -3647,6 +3649,11 @@
}
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -4028,6 +4035,11 @@
"node": ">= 0.8"
}
},
+ "node_modules/enquire.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
+ "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw=="
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -5999,6 +6011,14 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -6096,6 +6116,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -7322,6 +7347,22 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-slick": {
+ "version": "0.30.2",
+ "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz",
+ "integrity": "sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "enquire.js": "^2.1.6",
+ "json2mq": "^0.2.0",
+ "lodash.debounce": "^4.0.8",
+ "resize-observer-polyfill": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -7473,6 +7514,11 @@
"node": ">=0.10.5"
}
},
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
"node_modules/resolve": {
"version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
@@ -7828,6 +7874,14 @@
"node": ">=8"
}
},
+ "node_modules/slick-carousel": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz",
+ "integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==",
+ "peerDependencies": {
+ "jquery": ">=1.8.0"
+ }
+ },
"node_modules/snake-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
@@ -7893,6 +7947,11 @@
"url": "https://opencollective.com/storybook"
}
},
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
diff --git a/package.json b/package.json
index 07300ff..c849c22 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,9 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-router-dom": "^6.26.0"
+ "react-router-dom": "^6.26.0",
+ "react-slick": "^0.30.2",
+ "slick-carousel": "^1.8.1"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.6.1",
diff --git a/src/App.jsx b/src/App.jsx
index b3062f1..e563787 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,9 +1,15 @@
+import Container from "./containers/Container";
+import TopDoctorsSection from "./sections/TopDoctorsSection";
+
const App = () => {
return (
-
- Welcome to KlinicCon App
-
+
Welcome to KlinicCon App
+
+
+
+
+
);
};
diff --git a/src/assets/shape.svg b/src/assets/shape.svg
new file mode 100644
index 0000000..a099534
--- /dev/null
+++ b/src/assets/shape.svg
@@ -0,0 +1,422 @@
+
diff --git a/src/assets/temp/img_01.webp b/src/assets/temp/img_01.webp
new file mode 100644
index 0000000..f1cb530
Binary files /dev/null and b/src/assets/temp/img_01.webp differ
diff --git a/src/assets/temp/img_02.webp b/src/assets/temp/img_02.webp
new file mode 100644
index 0000000..233d227
Binary files /dev/null and b/src/assets/temp/img_02.webp differ
diff --git a/src/assets/temp/img_03.webp b/src/assets/temp/img_03.webp
new file mode 100644
index 0000000..0841512
Binary files /dev/null and b/src/assets/temp/img_03.webp differ
diff --git a/src/assets/temp/img_04.webp b/src/assets/temp/img_04.webp
new file mode 100644
index 0000000..040d20a
Binary files /dev/null and b/src/assets/temp/img_04.webp differ
diff --git a/src/assets/temp/img_05.webp b/src/assets/temp/img_05.webp
new file mode 100644
index 0000000..9379540
Binary files /dev/null and b/src/assets/temp/img_05.webp differ
diff --git a/src/components/Paragraph.jsx b/src/components/Paragraph.jsx
new file mode 100644
index 0000000..74bf774
--- /dev/null
+++ b/src/components/Paragraph.jsx
@@ -0,0 +1,16 @@
+import PropTypes from "prop-types";
+
+const Paragraph = ({ children, className }) => {
+ return {children}
;
+};
+
+Paragraph.propTypes = {
+ children: PropTypes.node.isRequired,
+ className: PropTypes.string,
+};
+
+Paragraph.defaultProps = {
+ className: "",
+};
+
+export default Paragraph;
diff --git a/src/components/StarRating.jsx b/src/components/StarRating.jsx
new file mode 100644
index 0000000..d601bf4
--- /dev/null
+++ b/src/components/StarRating.jsx
@@ -0,0 +1,22 @@
+import PropTypes from "prop-types";
+import "@fortawesome/fontawesome-free/css/all.css";
+
+const StarRating = ({ rating }) => {
+ const stars = Array(5)
+ .fill(0)
+ .map((_, i) =>
+ i < rating ? (
+
+ ) : (
+
+ )
+ );
+
+ return {stars}
;
+};
+
+StarRating.propTypes = {
+ rating: PropTypes.number.isRequired,
+};
+
+export default StarRating;
diff --git a/src/containers/Container.jsx b/src/containers/Container.jsx
new file mode 100644
index 0000000..e1b2e7d
--- /dev/null
+++ b/src/containers/Container.jsx
@@ -0,0 +1,20 @@
+import PropTypes from "prop-types";
+
+const Container = ({ children, className }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+Container.propTypes = {
+ children: PropTypes.node.isRequired,
+ className: PropTypes.string,
+};
+
+Container.defaultProps = {
+ className: "",
+};
+
+export default Container;
diff --git a/src/containers/DoctorSlider.jsx b/src/containers/DoctorSlider.jsx
new file mode 100644
index 0000000..e474a52
--- /dev/null
+++ b/src/containers/DoctorSlider.jsx
@@ -0,0 +1,72 @@
+import Slider from "react-slick";
+import "slick-carousel/slick/slick.css";
+import "slick-carousel/slick/slick-theme.css";
+import PropTypes from "prop-types";
+import StarRating from "../components/StarRating";
+
+const DoctorSlider = ({ teamMembers = [] }) => {
+ const settings = {
+ dots: true,
+ infinite: true,
+ autoplay: true,
+ speed: 500,
+ slidesToShow: 4,
+ slidesToScroll: 2,
+ responsive: [
+ {
+ breakpoint: 1024,
+ settings: {
+ slidesToShow: 3,
+ },
+ },
+ {
+ breakpoint: 768,
+ settings: {
+ slidesToShow: 2,
+ },
+ },
+ {
+ breakpoint: 640,
+ settings: {
+ slidesToShow: 1,
+ slidesToScroll: 1,
+ },
+ },
+ ],
+ };
+
+ return (
+
+ {teamMembers.map((member, index) => (
+
+
+
+
+
+
+
{member.name}
+
{member.role}
+
+
+
+ ))}
+
+ );
+};
+
+DoctorSlider.propTypes = {
+ teamMembers: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ role: PropTypes.string.isRequired,
+ imageUrl: PropTypes.string.isRequired,
+ rating: PropTypes.number.isRequired,
+ })
+ ),
+};
+
+export default DoctorSlider;
diff --git a/src/main.jsx b/src/main.jsx
index d13e088..2c5d34f 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -2,6 +2,7 @@ import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
+import "./styles/slick.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
diff --git a/src/sections/TopDoctorsSection.jsx b/src/sections/TopDoctorsSection.jsx
new file mode 100644
index 0000000..df4ec64
--- /dev/null
+++ b/src/sections/TopDoctorsSection.jsx
@@ -0,0 +1,52 @@
+import Paragraph from "../components/Paragraph";
+import DoctorSlider from "../containers/DoctorSlider";
+import HeaderText from "../components/HeaderText";
+import image1 from "../assets/temp/img_01.webp";
+import image2 from "../assets/temp/img_02.webp";
+import image3 from "../assets/temp/img_03.webp";
+import image4 from "../assets/temp/img_04.webp";
+import image5 from "../assets/temp/img_05.webp";
+
+const TopDoctorsSection = () => {
+ const teamMembers = [
+ {
+ name: "Jubayer Al Hasan",
+ role: "Marketing Expert",
+ imageUrl: image1,
+ rating: 4,
+ },
+ {
+ name: "Jannatul Ferdaus",
+ role: "Broker",
+ imageUrl: image2,
+ rating: 5,
+ },
+ {
+ name: "Chris Matial",
+ role: "Broker",
+ imageUrl: image3,
+ rating: 3,
+ },
+ {
+ name: "Mark Filo",
+ role: "CEO & Founder",
+ imageUrl: image4,
+ rating: 5,
+ },
+ {
+ name: "Professor Dr D. Filo",
+ role: "CEO & Founder",
+ imageUrl: image5,
+ rating: 4,
+ },
+ ];
+ return (
+ <>
+
+ Lorem is placeholder text commonly used graphic
+
+ >
+ );
+};
+
+export default TopDoctorsSection;
diff --git a/src/stories/Container.stories.jsx b/src/stories/Container.stories.jsx
new file mode 100644
index 0000000..bd40b46
--- /dev/null
+++ b/src/stories/Container.stories.jsx
@@ -0,0 +1,46 @@
+import Container from "../containers/Container";
+
+export default {
+ title: "Components/Container",
+ component: Container,
+ tags: ["autodocs"],
+ argTypes: {
+ className: { control: "text" },
+ children: { control: false },
+ },
+};
+
+// Default Container
+export const Default = (args) => Default Content;
+
+Default.args = {
+ className: "",
+};
+
+// Container with Custom Padding
+export const CustomPadding = (args) => Content with Custom Padding;
+
+CustomPadding.args = {
+ className: "p-8",
+};
+
+// Container with Background Color
+export const WithBackgroundColor = (args) => Content with Background;
+
+WithBackgroundColor.args = {
+ className: "bg-gray-200",
+};
+
+// Container with Centered Text
+export const CenteredText = (args) => Centered Text Content;
+
+CenteredText.args = {
+ className: "text-center",
+};
+
+// Container with Margin Adjustment
+export const WithMarginAdjustment = (args) => Content with Margin Adjustment;
+
+WithMarginAdjustment.args = {
+ className: "my-10",
+};
diff --git a/src/stories/DoctorSlider.stories.jsx b/src/stories/DoctorSlider.stories.jsx
new file mode 100644
index 0000000..00b9f4f
--- /dev/null
+++ b/src/stories/DoctorSlider.stories.jsx
@@ -0,0 +1,86 @@
+import "./assets/styles/slick.css";
+import image1 from "../assets/temp/img_01.webp";
+import image2 from "../assets/temp/img_02.webp";
+import image3 from "../assets/temp/img_03.webp";
+import image4 from "../assets/temp/img_04.webp";
+import image5 from "../assets/temp/img_05.webp";
+import DoctorSlider from "../containers/DoctorSlider";
+
+export default {
+ title: "Components/DoctorSlider",
+ component: DoctorSlider,
+ tags: ["autodocs"],
+ argTypes: {
+ member1Name: { control: "text", name: "Member 1 Name" },
+ member1Role: { control: "text", name: "Member 1 Role" },
+ member1Rating: { control: "number", name: "Member 1 Rating" },
+ member2Name: { control: "text", name: "Member 2 Name" },
+ member2Role: { control: "text", name: "Member 2 Role" },
+ member2Rating: { control: "number", name: "Member 2 Rating" },
+ member3Name: { control: "text", name: "Member 3 Name" },
+ member3Role: { control: "text", name: "Member 3 Role" },
+ member3Rating: { control: "number", name: "Member 3 Rating" },
+ member4Name: { control: "text", name: "Member 4 Name" },
+ member4Role: { control: "text", name: "Member 4 Role" },
+ member4Rating: { control: "number", name: "Member 4 Rating" },
+ member5Name: { control: "text", name: "Member 5 Name" },
+ member5Role: { control: "text", name: "Member 5 Role" },
+ member5Rating: { control: "number", name: "Member 5 Rating" },
+ },
+};
+
+const Template = (args) => {
+ const teamMembers = [
+ {
+ name: args.member1Name,
+ role: args.member1Role,
+ imageUrl: image1,
+ rating: args.member1Rating,
+ },
+ {
+ name: args.member2Name,
+ role: args.member2Role,
+ imageUrl: image2,
+ rating: args.member2Rating,
+ },
+ {
+ name: args.member3Name,
+ role: args.member3Role,
+ imageUrl: image3,
+ rating: args.member3Rating,
+ },
+ {
+ name: args.member4Name,
+ role: args.member4Role,
+ imageUrl: image4,
+ rating: args.member4Rating,
+ },
+ {
+ name: args.member5Name,
+ role: args.member5Role,
+ imageUrl: image5,
+ rating: args.member5Rating,
+ },
+ ];
+
+ return ;
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ member1Name: "Jubayer Al Hasan",
+ member1Role: "Marketing Expert",
+ member1Rating: 4,
+ member2Name: "Jannatul Ferdaus",
+ member2Role: "Broker",
+ member2Rating: 5,
+ member3Name: "Chris Matial",
+ member3Role: "Broker",
+ member3Rating: 3,
+ member4Name: "Mark Filo",
+ member4Role: "CEO & Founder",
+ member4Rating: 5,
+ member5Name: "Professor Dr D. Filo",
+ member5Role: "CEO & Founder",
+ member5Rating: 4,
+};
diff --git a/src/stories/StarRating.stories.jsx b/src/stories/StarRating.stories.jsx
new file mode 100644
index 0000000..bc88aab
--- /dev/null
+++ b/src/stories/StarRating.stories.jsx
@@ -0,0 +1,41 @@
+import StarRating from "../components/StarRating";
+import "../styles/slick.css";
+
+export default {
+ title: "Components/StarRating",
+ component: StarRating,
+ tags: ["autodocs"],
+ argTypes: {
+ rating: {
+ control: {
+ type: "number",
+ min: 0,
+ max: 5,
+ },
+ },
+ },
+};
+
+// Default Star Rating
+export const Default = (args) => ;
+Default.args = {
+ rating: 3, // default rating
+};
+
+// 1-Star Rating
+export const OneStar = (args) => ;
+OneStar.args = {
+ rating: 1,
+};
+
+// 3-Star Rating
+export const ThreeStars = (args) => ;
+ThreeStars.args = {
+ rating: 3,
+};
+
+// 5-Star Rating
+export const FiveStars = (args) => ;
+FiveStars.args = {
+ rating: 5,
+};
diff --git a/src/stories/assets/styles/slick.css b/src/stories/assets/styles/slick.css
new file mode 100644
index 0000000..396fd0e
--- /dev/null
+++ b/src/stories/assets/styles/slick.css
@@ -0,0 +1,21 @@
+.with-background::before {
+ content: "";
+ position: absolute;
+ left: -40px;
+ right: -40px;
+ top: -35px;
+ height: 45%;
+ z-index: -1;
+ background: url("../../../assets/shape.svg") no-repeat 50%;
+ background-size: cover;
+}
+.slick-dots li.slick-active button:before {
+ opacity: 1 !important;
+ color: #f97316 !important;
+ font-size: 14px !important;
+}
+.slick-dots li button:before {
+ opacity: 1 !important;
+ color: #000 !important;
+ font-size: 14px !important;
+}
diff --git a/src/styles/slick.css b/src/styles/slick.css
new file mode 100644
index 0000000..3a78598
--- /dev/null
+++ b/src/styles/slick.css
@@ -0,0 +1,43 @@
+.with-background::before {
+ content: "";
+ position: absolute;
+ left: -40px;
+ right: -40px;
+ top: -35px;
+ height: 45%;
+ z-index: -1;
+ background: url("../assets/shape.svg") no-repeat 50%;
+ background-size: cover;
+}
+.slick-dots li.slick-active button:before {
+ @apply opacity-100 text-orange-600 text-[14px];
+}
+.slick-dots li button:before {
+ @apply opacity-100 text-black text-[14px];
+}
+
+.doctor-section {
+ @apply relative;
+
+ /* Left Line (Before) */
+ &::before {
+ content: "";
+ position: absolute;
+ width: calc(50% - 65px);
+ height: 2px;
+ background-color: #000;
+ bottom: 0;
+ left: 0;
+ }
+
+ /* Right Line (After) */
+ &::after {
+ content: "";
+ position: absolute;
+ width: calc(50% - 65px);
+ height: 2px;
+ background-color: #000;
+ bottom: 0;
+ right: 0;
+ }
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index c7429c4..e1e1f22 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -2,11 +2,7 @@ const defaultColors = require("tailwindcss/colors");
/** @type {import('tailwindcss').Config} */
export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- "./.storybook/**/*.{js,jsx,ts,tsx}",
- ],
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", "./.storybook/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {
colors: {
@@ -21,5 +17,15 @@ export default {
},
},
},
- plugins: [],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ ".slick-dot-active": {
+ opacity: "1 !important",
+ color: "#f97316 !important",
+ fontSize: "14px !important",
+ },
+ });
+ },
+ ],
};