@sanvika/ui 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -29
- package/src/components/buttons/Button.jsx +97 -0
- package/src/components/buttons/Button.stories.jsx +110 -0
- package/src/components/buttons/FavoriteHeartButton.jsx +41 -0
- package/src/components/buttons/FavoriteHeartButton.stories.jsx +48 -0
- package/src/components/buttons/ScrollButton.jsx +36 -0
- package/src/components/buttons/ScrollButton.stories.jsx +21 -0
- package/src/components/buttons/ThreeDotButton.jsx +36 -0
- package/src/components/buttons/ThreeDotButton.stories.jsx +14 -0
- package/src/components/common/Container.jsx +38 -0
- package/src/components/common/Section.jsx +60 -0
- package/src/components/common/Section.stories.jsx +64 -0
- package/src/components/icons/BellIcon.jsx +10 -0
- package/src/components/icons/ChevronDown.jsx +20 -0
- package/src/components/icons/EmailIcon.jsx +9 -0
- package/src/components/icons/FaAngleDown.jsx +9 -0
- package/src/components/icons/FaAngleRight.jsx +9 -0
- package/src/components/icons/FaArrowDown.jsx +9 -0
- package/src/components/icons/FaArrowLeft.jsx +9 -0
- package/src/components/icons/FaArrowUp.jsx +9 -0
- package/src/components/icons/FaBell.jsx +9 -0
- package/src/components/icons/FaBullhorn.jsx +10 -0
- package/src/components/icons/FaCalendarAlt.jsx +9 -0
- package/src/components/icons/FaCheck.jsx +9 -0
- package/src/components/icons/FaCheckCircle.jsx +9 -0
- package/src/components/icons/FaCheckDouble.jsx +9 -0
- package/src/components/icons/FaChevronDown.jsx +9 -0
- package/src/components/icons/FaChevronRight.jsx +9 -0
- package/src/components/icons/FaCircle.jsx +9 -0
- package/src/components/icons/FaComments.jsx +9 -0
- package/src/components/icons/FaEye.jsx +9 -0
- package/src/components/icons/FaEyeSlash.jsx +9 -0
- package/src/components/icons/FaHome.jsx +9 -0
- package/src/components/icons/FaKeyboard.jsx +9 -0
- package/src/components/icons/FaLocationArrow.jsx +9 -0
- package/src/components/icons/FaMapMarkerAlt.jsx +9 -0
- package/src/components/icons/FaMoon.jsx +9 -0
- package/src/components/icons/FaPaperPlane.jsx +9 -0
- package/src/components/icons/FaPaperclip.jsx +9 -0
- package/src/components/icons/FaSignInAlt.jsx +9 -0
- package/src/components/icons/FaSmile.jsx +9 -0
- package/src/components/icons/FaStar.jsx +9 -0
- package/src/components/icons/FaTag.jsx +9 -0
- package/src/components/icons/FaThumbsDown.jsx +9 -0
- package/src/components/icons/FaThumbsUp.jsx +9 -0
- package/src/components/icons/FaTrash.jsx +9 -0
- package/src/components/icons/FaUser.jsx +9 -0
- package/src/components/icons/FaUserCircle.jsx +9 -0
- package/src/components/icons/FacebookIcon.jsx +9 -0
- package/src/components/icons/HalfMoonIcon.jsx +18 -0
- package/src/components/icons/HeartIcon.jsx +9 -0
- package/src/components/icons/InstagramIcon.jsx +9 -0
- package/src/components/icons/LinkedInIcon.jsx +9 -0
- package/src/components/icons/MapMarkerIcon.jsx +9 -0
- package/src/components/icons/MdWbSunny.jsx +9 -0
- package/src/components/icons/ReFreshIcon.jsx +49 -0
- package/src/components/icons/SearchIcon.jsx +20 -0
- package/src/components/icons/StarIcon.jsx +9 -0
- package/src/components/icons/TelegramIcon.jsx +9 -0
- package/src/components/icons/TwitterIcon.jsx +9 -0
- package/src/components/icons/UserIcon.jsx +9 -0
- package/src/components/icons/WhatsappIcon.jsx +9 -0
- package/src/components/icons/YoutubeIcon.jsx +9 -0
- package/src/components/icons/index.js +60 -0
- package/src/components/layout/Footer.jsx +53 -0
- package/src/components/layout/Footer.stories.jsx +28 -0
- package/src/components/layout/Navbar.jsx +50 -0
- package/src/components/layout/Navbar.stories.jsx +42 -0
- package/src/components/layout/SubNavbar.jsx +36 -0
- package/src/components/modals/Modal.jsx +74 -0
- package/src/components/modals/Modal.stories.jsx +93 -0
- package/src/components/progressBar/ProgressBar.jsx +113 -0
- package/src/components/progressBar/ProgressBar.stories.jsx +67 -0
- package/src/context/ThemeContext.jsx +91 -0
- package/src/index.js +33 -0
- package/src/layouts/Layout.jsx +24 -0
- package/src/server/index.js +159 -0
- package/README.md +0 -36
- package/dist/EmailIcon-DumDw7u2.js +0 -781
- package/dist/EmailIcon-ssF1iAVu.js +0 -782
- package/dist/icons/index.js +0 -4
- package/dist/index.js +0 -555
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanvika/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
|
-
"description": "Sanvika Production — shared UI component library for 50+ projects
|
|
6
|
+
"description": "Sanvika Production — shared UI component library for 50+ projects (pure JS, CSS Modules, props-driven) + cloud-driven Navbar/Theme presets via ui.sanvikaproduction.com",
|
|
7
7
|
"author": "Shyam Bharteey <shyam@sanvikaproduction.com>",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
@@ -18,46 +18,32 @@
|
|
|
18
18
|
"sanvika",
|
|
19
19
|
"classified-ads"
|
|
20
20
|
],
|
|
21
|
-
"main": "./
|
|
22
|
-
"module": "./
|
|
21
|
+
"main": "./src/index.js",
|
|
22
|
+
"module": "./src/index.js",
|
|
23
23
|
"exports": {
|
|
24
|
-
".": "./
|
|
25
|
-
"./icons": "./
|
|
26
|
-
"./styles": "./src/styles/index.css"
|
|
24
|
+
".": "./src/index.js",
|
|
25
|
+
"./icons": "./src/components/icons/index.js",
|
|
26
|
+
"./styles": "./src/styles/index.css",
|
|
27
|
+
"./server": "./src/server/index.js"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"dist",
|
|
30
|
-
"src
|
|
31
|
+
"src"
|
|
31
32
|
],
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"react": ">=18.0.0",
|
|
34
35
|
"react-dom": ">=18.0.0"
|
|
35
36
|
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "rollup -c rollup.config.js",
|
|
38
|
-
"prepublishOnly": "npm run build",
|
|
39
|
-
"storybook": "storybook dev -p 6006",
|
|
40
|
-
"build-storybook": "storybook build"
|
|
41
|
-
},
|
|
42
|
-
"pnpm": {
|
|
43
|
-
"onlyBuiltDependencies": [
|
|
44
|
-
"core-js-pure",
|
|
45
|
-
"esbuild"
|
|
46
|
-
]
|
|
47
|
-
},
|
|
48
37
|
"devDependencies": {
|
|
49
38
|
"@babel/core": "^7.29.0",
|
|
50
39
|
"@babel/preset-react": "^7.28.5",
|
|
51
40
|
"@rollup/plugin-babel": "^7.0.0",
|
|
52
41
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
53
|
-
"@storybook/nextjs": "^10.3.2",
|
|
54
|
-
"eslint": "^9",
|
|
55
|
-
"eslint-config-next": "16.2.2",
|
|
56
|
-
"next": "16.2.2",
|
|
57
|
-
"react": "19.2.4",
|
|
58
|
-
"react-dom": "19.2.4",
|
|
59
42
|
"rollup": "^4.60.1",
|
|
60
|
-
"rollup-plugin-postcss": "^4.0.2"
|
|
61
|
-
|
|
43
|
+
"rollup-plugin-postcss": "^4.0.2"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rollup -c rollup.config.js",
|
|
47
|
+
"build:lib": "rollup -c rollup.config.js"
|
|
62
48
|
}
|
|
63
|
-
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/components/buttons/Button.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import styles from "../../styles/components/buttons/Button.module.css";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Button — versatile button/link component.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} props
|
|
9
|
+
* @param {React.ReactNode} [props.children]
|
|
10
|
+
* @param {string} [props.text] - button label (fallback if no children)
|
|
11
|
+
* @param {React.ReactNode} [props.icon] - icon rendered before label
|
|
12
|
+
* @param {React.ReactNode} [props.badge] - notification badge
|
|
13
|
+
* @param {"primary"|"secondary"|"danger"|"success"} [props.intent="primary"]
|
|
14
|
+
* @param {"solid"|"outline"|"ghost"} [props.appearance="solid"]
|
|
15
|
+
* @param {"sm"|"md"|"lg"} [props.size="md"]
|
|
16
|
+
* @param {boolean} [props.fullWidth=false]
|
|
17
|
+
* @param {string} [props.href] - renders as <a> when set
|
|
18
|
+
* @param {"button"|"submit"|"reset"} [props.type="button"]
|
|
19
|
+
* @param {boolean} [props.disabled=false]
|
|
20
|
+
*/
|
|
21
|
+
const Button = ({
|
|
22
|
+
children,
|
|
23
|
+
text,
|
|
24
|
+
icon,
|
|
25
|
+
badge,
|
|
26
|
+
badgeClassName,
|
|
27
|
+
intent = "primary",
|
|
28
|
+
appearance = "solid",
|
|
29
|
+
size = "md",
|
|
30
|
+
fullWidth = false,
|
|
31
|
+
href,
|
|
32
|
+
type = "button",
|
|
33
|
+
disabled = false,
|
|
34
|
+
className,
|
|
35
|
+
style,
|
|
36
|
+
onClick,
|
|
37
|
+
...props
|
|
38
|
+
}) => {
|
|
39
|
+
const label = children ?? text;
|
|
40
|
+
|
|
41
|
+
const intentClass = styles[intent] ?? styles.primary;
|
|
42
|
+
|
|
43
|
+
const appearanceClass =
|
|
44
|
+
appearance === "outline"
|
|
45
|
+
? styles.outline
|
|
46
|
+
: appearance === "ghost"
|
|
47
|
+
? styles.ghost
|
|
48
|
+
: "";
|
|
49
|
+
|
|
50
|
+
const sizeClass = size === "sm" ? styles.sm : size === "lg" ? styles.lg : "";
|
|
51
|
+
|
|
52
|
+
const cls = [
|
|
53
|
+
styles.button,
|
|
54
|
+
intentClass,
|
|
55
|
+
appearanceClass,
|
|
56
|
+
sizeClass,
|
|
57
|
+
fullWidth ? styles.full : "",
|
|
58
|
+
className ?? "",
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(" ");
|
|
62
|
+
|
|
63
|
+
const content = (
|
|
64
|
+
<>
|
|
65
|
+
{icon && (
|
|
66
|
+
<span className={styles.icon} aria-hidden="true">
|
|
67
|
+
{icon}
|
|
68
|
+
</span>
|
|
69
|
+
)}
|
|
70
|
+
{label && <span className={styles.text}>{label}</span>}
|
|
71
|
+
{badge != null && <span className={`${styles.badge} ${badgeClassName ?? ""}`.trim()}>{badge}</span>}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (href) {
|
|
76
|
+
return (
|
|
77
|
+
<a href={href} className={cls} style={style} {...props}>
|
|
78
|
+
{content}
|
|
79
|
+
</a>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button
|
|
85
|
+
type={type}
|
|
86
|
+
className={cls}
|
|
87
|
+
style={style}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
onClick={onClick}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
{content}
|
|
93
|
+
</button>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default Button;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/components/buttons/Button.stories.jsx
|
|
2
|
+
import Button from "./Button";
|
|
3
|
+
|
|
4
|
+
/** @type {import('@storybook/react').Meta<typeof Button>} */
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/Button",
|
|
7
|
+
component: Button,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
argTypes: {
|
|
12
|
+
intent: {
|
|
13
|
+
control: "select",
|
|
14
|
+
options: ["primary", "secondary", "danger", "success"],
|
|
15
|
+
},
|
|
16
|
+
appearance: {
|
|
17
|
+
control: "select",
|
|
18
|
+
options: ["solid", "outline", "ghost"],
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
control: "select",
|
|
22
|
+
options: ["sm", "md", "lg"],
|
|
23
|
+
},
|
|
24
|
+
disabled: { control: "boolean" },
|
|
25
|
+
fullWidth: { control: "boolean" },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
|
|
31
|
+
export const Primary = {
|
|
32
|
+
args: {
|
|
33
|
+
text: "Get started",
|
|
34
|
+
intent: "primary",
|
|
35
|
+
appearance: "solid",
|
|
36
|
+
size: "md",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Secondary = {
|
|
41
|
+
args: { text: "Learn more", intent: "secondary" },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Danger = {
|
|
45
|
+
args: { text: "Delete", intent: "danger" },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const Success = {
|
|
49
|
+
args: { text: "Confirm", intent: "success" },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Outline = {
|
|
53
|
+
args: { text: "Outline button", appearance: "outline" },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Ghost = {
|
|
57
|
+
args: { text: "Ghost button", appearance: "ghost" },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Small = {
|
|
61
|
+
args: { text: "Small", size: "sm" },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const Large = {
|
|
65
|
+
args: { text: "Large", size: "lg" },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const WithIcon = {
|
|
69
|
+
args: {
|
|
70
|
+
text: "Download",
|
|
71
|
+
icon: <span>⬇</span>,
|
|
72
|
+
intent: "primary",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const WithBadge = {
|
|
77
|
+
args: {
|
|
78
|
+
text: "Notifications",
|
|
79
|
+
badge: 5,
|
|
80
|
+
intent: "secondary",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const Disabled = {
|
|
85
|
+
args: { text: "Disabled", disabled: true },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const AsLink = {
|
|
89
|
+
args: { text: "Visit site", href: "#", intent: "primary" },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const AllIntents = {
|
|
93
|
+
render: () => (
|
|
94
|
+
<div style={{ display: "flex", gap: "12px", flexWrap: "wrap" }}>
|
|
95
|
+
{["primary", "secondary", "danger", "success"].map((intent) => (
|
|
96
|
+
<Button key={intent} intent={intent} text={intent} />
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const AllAppearances = {
|
|
103
|
+
render: () => (
|
|
104
|
+
<div style={{ display: "flex", gap: "12px", flexWrap: "wrap" }}>
|
|
105
|
+
{["solid", "outline", "ghost"].map((appearance) => (
|
|
106
|
+
<Button key={appearance} appearance={appearance} text={appearance} />
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
),
|
|
110
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/components/buttons/FavoriteHeartButton.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import HeartIcon from "../icons/HeartIcon.jsx";
|
|
4
|
+
import Button from "./Button.jsx";
|
|
5
|
+
import styles from "../../styles/components/buttons/FavoriteHeartButton.module.css";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* FavoriteHeartButton — toggleable heart/favorite button.
|
|
9
|
+
* Returns null when isOwner=true (owners cannot favorite their own items).
|
|
10
|
+
*
|
|
11
|
+
* @param {boolean} isFavorited - current favorite state
|
|
12
|
+
* @param {function} onClick
|
|
13
|
+
* @param {boolean} [disabled=false]
|
|
14
|
+
* @param {boolean} [isOwner=false] - if true, renders nothing
|
|
15
|
+
* @param {string} [className]
|
|
16
|
+
* @param {object} [style]
|
|
17
|
+
*/
|
|
18
|
+
const FavoriteHeartButton = ({
|
|
19
|
+
isFavorited,
|
|
20
|
+
onClick,
|
|
21
|
+
disabled,
|
|
22
|
+
isOwner = false,
|
|
23
|
+
className = "",
|
|
24
|
+
style,
|
|
25
|
+
}) => {
|
|
26
|
+
if (isOwner) return null;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Button
|
|
30
|
+
onClick={onClick}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
className={`${styles.favoriteHeartButton} ${isFavorited ? styles.favorited : styles.notFavorited} ${className}`}
|
|
33
|
+
style={style}
|
|
34
|
+
aria-label={isFavorited ? "Unfavorite" : "Favorite"}
|
|
35
|
+
title={isFavorited ? "Unfavorite" : "Favorite"}
|
|
36
|
+
icon={<HeartIcon style={{ width: 20, height: 20 }} />}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default FavoriteHeartButton;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/components/buttons/FavoriteHeartButton.stories.jsx
|
|
2
|
+
"use client";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import FavoriteHeartButton from "./FavoriteHeartButton";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/Buttons/FavoriteHeartButton",
|
|
8
|
+
component: FavoriteHeartButton,
|
|
9
|
+
parameters: { layout: "centered" },
|
|
10
|
+
argTypes: {
|
|
11
|
+
isFavorited: { control: "boolean" },
|
|
12
|
+
disabled: { control: "boolean" },
|
|
13
|
+
isOwner: { control: "boolean" },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
|
|
19
|
+
const FavDemo = ({ initial = false }) => {
|
|
20
|
+
const [fav, setFav] = useState(initial);
|
|
21
|
+
return (
|
|
22
|
+
<FavoriteHeartButton
|
|
23
|
+
isFavorited={fav}
|
|
24
|
+
onClick={() => setFav((v) => !v)}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const NotFavorited = {
|
|
30
|
+
render: () => <FavDemo initial={false} />,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Favorited = {
|
|
34
|
+
render: () => <FavDemo initial={true} />,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Disabled = {
|
|
38
|
+
args: { isFavorited: false, disabled: true, onClick: () => {} },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const HiddenForOwner = {
|
|
42
|
+
args: { isFavorited: false, isOwner: true, onClick: () => {} },
|
|
43
|
+
parameters: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: { story: "Returns null when isOwner=true — nothing renders." },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/components/buttons/ScrollButton.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Button from "./Button.jsx";
|
|
4
|
+
import { FaArrowUp, FaArrowDown } from "../icons/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ScrollButton — icon-only button for scroll up/down actions.
|
|
8
|
+
*
|
|
9
|
+
* @param {"up"|"down"} [direction="up"]
|
|
10
|
+
* @param {function} onClick
|
|
11
|
+
* @param {string} [className]
|
|
12
|
+
* @param {object} [style]
|
|
13
|
+
*/
|
|
14
|
+
const ScrollButton = ({
|
|
15
|
+
direction = "up",
|
|
16
|
+
onClick,
|
|
17
|
+
className = "",
|
|
18
|
+
style = {},
|
|
19
|
+
}) => {
|
|
20
|
+
const icon = direction === "up" ? <FaArrowUp /> : <FaArrowDown />;
|
|
21
|
+
const label = direction === "up" ? "Scroll to top" : "Scroll to bottom";
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Button
|
|
25
|
+
icon={icon}
|
|
26
|
+
onClick={onClick}
|
|
27
|
+
className={className}
|
|
28
|
+
appearance="ghost"
|
|
29
|
+
aria-label={label}
|
|
30
|
+
title={label}
|
|
31
|
+
style={{ display: "flex", ...style }}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default ScrollButton;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/components/buttons/ScrollButton.stories.jsx
|
|
2
|
+
import ScrollButton from "./ScrollButton";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Buttons/ScrollButton",
|
|
6
|
+
component: ScrollButton,
|
|
7
|
+
parameters: { layout: "centered" },
|
|
8
|
+
argTypes: {
|
|
9
|
+
direction: { control: "select", options: ["up", "down"] },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
export const Up = {
|
|
16
|
+
args: { direction: "up", onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }) },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Down = {
|
|
20
|
+
args: { direction: "down", onClick: () => window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }) },
|
|
21
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/components/buttons/ThreeDotButton.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Button from "./Button.jsx";
|
|
4
|
+
import styles from "../../styles/components/buttons/ThreeDotButton.module.css";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ThreeDotButton — compact vertical three-dot menu trigger.
|
|
8
|
+
* Pure props-driven: all navigation/action logic passed via onClick.
|
|
9
|
+
*
|
|
10
|
+
* @param {function} [onClick]
|
|
11
|
+
* @param {string} [ariaLabel="More options"]
|
|
12
|
+
* @param {string} [className]
|
|
13
|
+
*/
|
|
14
|
+
const ThreeDotButton = ({
|
|
15
|
+
className = "",
|
|
16
|
+
ariaLabel = "More options",
|
|
17
|
+
onClick,
|
|
18
|
+
}) => {
|
|
19
|
+
return (
|
|
20
|
+
<Button
|
|
21
|
+
className={`${styles.threeDotButton} ${className}`}
|
|
22
|
+
onClick={onClick}
|
|
23
|
+
aria-label={ariaLabel}
|
|
24
|
+
title={ariaLabel}
|
|
25
|
+
icon={
|
|
26
|
+
<span className={styles.dotsWrapper} aria-hidden="true">
|
|
27
|
+
<span className={styles.dot} />
|
|
28
|
+
<span className={styles.dot} />
|
|
29
|
+
<span className={styles.dot} />
|
|
30
|
+
</span>
|
|
31
|
+
}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default ThreeDotButton;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/components/buttons/ThreeDotButton.stories.jsx
|
|
2
|
+
import ThreeDotButton from "./ThreeDotButton";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Buttons/ThreeDotButton",
|
|
6
|
+
component: ThreeDotButton,
|
|
7
|
+
parameters: { layout: "centered" },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
export const Default = {
|
|
13
|
+
args: { onClick: () => alert("Menu opened"), ariaLabel: "More options" },
|
|
14
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/components/common/Container.jsx
|
|
2
|
+
import styles from "../../styles/components/common/Container.module.css";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Container — a centered max-width wrapper.
|
|
6
|
+
*
|
|
7
|
+
* @param {"default"|"narrow"|"wide"|"full"} [size="default"]
|
|
8
|
+
* @param {React.ElementType} [as="div"]
|
|
9
|
+
*/
|
|
10
|
+
const Container = ({
|
|
11
|
+
children,
|
|
12
|
+
size = "default",
|
|
13
|
+
className,
|
|
14
|
+
style,
|
|
15
|
+
as: Tag = "div",
|
|
16
|
+
...props
|
|
17
|
+
}) => {
|
|
18
|
+
const sizeClass =
|
|
19
|
+
size === "narrow"
|
|
20
|
+
? styles.narrow
|
|
21
|
+
: size === "wide"
|
|
22
|
+
? styles.wide
|
|
23
|
+
: size === "full"
|
|
24
|
+
? styles.full
|
|
25
|
+
: "";
|
|
26
|
+
|
|
27
|
+
const cls = [styles.container, sizeClass, className ?? ""]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(" ");
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Tag className={cls} style={style} {...props}>
|
|
33
|
+
{children}
|
|
34
|
+
</Tag>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default Container;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/components/common/Section.jsx
|
|
2
|
+
import styles from "../../styles/components/common/Section.module.css";
|
|
3
|
+
import Container from "./Container.jsx";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Section — a full-width page section with optional title/subtitle.
|
|
7
|
+
*
|
|
8
|
+
* @param {"default"|"alternate"|"card"} [variant="default"]
|
|
9
|
+
* @param {"default"|"tight"|"flush"} [padding="default"]
|
|
10
|
+
* @param {string} [title]
|
|
11
|
+
* @param {string} [subtitle]
|
|
12
|
+
* @param {"default"|"narrow"|"wide"|"full"} [containerSize="default"]
|
|
13
|
+
* @param {React.ElementType} [as="section"]
|
|
14
|
+
*/
|
|
15
|
+
const Section = ({
|
|
16
|
+
children,
|
|
17
|
+
variant = "default",
|
|
18
|
+
padding = "default",
|
|
19
|
+
title,
|
|
20
|
+
subtitle,
|
|
21
|
+
containerSize = "default",
|
|
22
|
+
className,
|
|
23
|
+
style,
|
|
24
|
+
as: Tag = "section",
|
|
25
|
+
...props
|
|
26
|
+
}) => {
|
|
27
|
+
const variantClass =
|
|
28
|
+
variant === "alternate"
|
|
29
|
+
? styles.alternate
|
|
30
|
+
: variant === "card"
|
|
31
|
+
? styles.card
|
|
32
|
+
: "";
|
|
33
|
+
|
|
34
|
+
const paddingClass =
|
|
35
|
+
padding === "tight"
|
|
36
|
+
? styles.tight
|
|
37
|
+
: padding === "flush"
|
|
38
|
+
? styles.flush
|
|
39
|
+
: "";
|
|
40
|
+
|
|
41
|
+
const cls = [styles.section, variantClass, paddingClass, className ?? ""]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join(" ");
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Tag className={cls} style={style} {...props}>
|
|
47
|
+
<Container size={containerSize}>
|
|
48
|
+
{(title || subtitle) && (
|
|
49
|
+
<div className={styles.header}>
|
|
50
|
+
{title && <h2 className={styles.title}>{title}</h2>}
|
|
51
|
+
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{children}
|
|
55
|
+
</Container>
|
|
56
|
+
</Tag>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default Section;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/components/common/Section.stories.jsx
|
|
2
|
+
import Section from "./Section";
|
|
3
|
+
import Button from "../buttons/Button.jsx";
|
|
4
|
+
|
|
5
|
+
/** @type {import('@storybook/react').Meta<typeof Section>} */
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Layout/Section",
|
|
8
|
+
component: Section,
|
|
9
|
+
parameters: { layout: "fullscreen" },
|
|
10
|
+
argTypes: {
|
|
11
|
+
variant: {
|
|
12
|
+
control: "select",
|
|
13
|
+
options: ["default", "alternate", "card"],
|
|
14
|
+
},
|
|
15
|
+
padding: {
|
|
16
|
+
control: "select",
|
|
17
|
+
options: ["default", "tight", "flush"],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
export const Default = {
|
|
25
|
+
args: {
|
|
26
|
+
title: "Section Title",
|
|
27
|
+
subtitle: "An optional subtitle providing more context about this section.",
|
|
28
|
+
children: (
|
|
29
|
+
<p style={{ textAlign: "center", color: "var(--secondary-text-color)" }}>
|
|
30
|
+
Section content goes here.
|
|
31
|
+
</p>
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Alternate = {
|
|
37
|
+
args: {
|
|
38
|
+
...Default.args,
|
|
39
|
+
variant: "alternate",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Card = {
|
|
44
|
+
args: {
|
|
45
|
+
...Default.args,
|
|
46
|
+
variant: "card",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const WithCTA = {
|
|
51
|
+
args: {
|
|
52
|
+
title: "Ready to get started?",
|
|
53
|
+
subtitle: "Join thousands of creators building on Sanvika.",
|
|
54
|
+
children: (
|
|
55
|
+
<div style={{ display: "flex", justifyContent: "center", gap: "12px" }}>
|
|
56
|
+
<Button intent="primary">Get started</Button>
|
|
57
|
+
<Button intent="secondary" appearance="outline">
|
|
58
|
+
Learn more
|
|
59
|
+
</Button>
|
|
60
|
+
</div>
|
|
61
|
+
),
|
|
62
|
+
variant: "alternate",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/components/icons/BellIcon.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
export const BellIcon = ({ className, style, title = "Notifications" }) => (
|
|
5
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className={className} style={style} role="img">
|
|
6
|
+
<title>{title}</title>
|
|
7
|
+
<path d="M12 2a4 4 0 00-4 4v1.586L6.293 9.293A1 1 0 006 10v2l-1 1v1h14v-1l-1-1v-2a1 1 0 00-.293-.707L16 7.586V6a4 4 0 00-4-4zm0 18a2.5 2.5 0 01-2.45-2h4.9A2.5 2.5 0 0112 20z" />
|
|
8
|
+
</svg>
|
|
9
|
+
);
|
|
10
|
+
export default BellIcon;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const ChevronDown = ({ className, style, title = "Open" }) => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
width="1em"
|
|
8
|
+
height="1em"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
aria-hidden="true"
|
|
11
|
+
className={className}
|
|
12
|
+
style={style}
|
|
13
|
+
role="img"
|
|
14
|
+
>
|
|
15
|
+
<title>{title}</title>
|
|
16
|
+
<path d="M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default ChevronDown;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const EmailIcon = ({ className, style }) => (
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" className={className} style={style} aria-hidden="true">
|
|
5
|
+
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
|
6
|
+
</svg>
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export default EmailIcon;
|