@evenicanpm/ui 1.3.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.
Files changed (47) hide show
  1. package/README.md +3 -0
  2. package/package.json +36 -0
  3. package/src/components/button/button.tsx +11 -0
  4. package/src/components/button/index.tsx +1 -0
  5. package/src/components/button/styles/index.ts +11 -0
  6. package/src/components/flex-box/flex-between.tsx +14 -0
  7. package/src/components/flex-box/flex-box.tsx +29 -0
  8. package/src/components/flex-box/flex-row-center.tsx +9 -0
  9. package/src/components/flex-box/index.ts +5 -0
  10. package/src/components/footer/footer.tsx +96 -0
  11. package/src/components/footer/index.ts +1 -0
  12. package/src/components/footer/styles/index.ts +58 -0
  13. package/src/components/header/header.tsx +50 -0
  14. package/src/components/header/index.ts +1 -0
  15. package/src/components/header/styles/index.ts +25 -0
  16. package/src/components/index.ts +12 -0
  17. package/src/components/nav-bar/index.ts +1 -0
  18. package/src/components/nav-bar/nav-bar.tsx +98 -0
  19. package/src/components/nav-bar/nav-item.tsx +72 -0
  20. package/src/components/nav-bar/styles/index.ts +65 -0
  21. package/src/components/responsive-pagination/index.ts +1 -0
  22. package/src/components/responsive-pagination/responsive-pagination.tsx +60 -0
  23. package/src/components/search-input/index.ts +1 -0
  24. package/src/components/search-input/search-input.tsx +28 -0
  25. package/src/components/site-logo/index.tsx +1 -0
  26. package/src/components/site-logo/site-logo.tsx +30 -0
  27. package/src/components/site-logo/styles/index.ts +34 -0
  28. package/src/components/sticky/Sticky.stories.tsx +89 -0
  29. package/src/components/sticky/index.ts +1 -0
  30. package/src/components/sticky/sticky.tsx +67 -0
  31. package/src/components/sticky/styles.ts +38 -0
  32. package/src/components/typography/index.ts +1 -0
  33. package/src/components/typography/typography.tsx +226 -0
  34. package/src/components/user/index.ts +1 -0
  35. package/src/components/user/styles/index.ts +20 -0
  36. package/src/components/user/user.tsx +74 -0
  37. package/src/index.ts +10 -0
  38. package/src/theme/build-mui-theme.ts +146 -0
  39. package/src/theme/components.ts +208 -0
  40. package/src/theme/index.ts +1 -0
  41. package/src/theme/theme-colors.ts +205 -0
  42. package/src/theme/theme-options.ts +113 -0
  43. package/src/theme/theme-provider.tsx +40 -0
  44. package/src/theme/utils.ts +17 -0
  45. package/src/utils/constants.ts +8 -0
  46. package/src/utils/index.ts +1 -0
  47. package/tsconfig.json +19 -0
@@ -0,0 +1,28 @@
1
+ // MUI ICON COMPONENT
2
+ import Search from "@mui/icons-material/Search";
3
+ import InputBase, { type InputBaseProps } from "@mui/material/InputBase";
4
+ import styled from "@mui/material/styles/styled";
5
+
6
+ // STYLED COMPONENT
7
+ const StyledInputBase = styled(InputBase)(({ theme }) => ({
8
+ fontSize: 14,
9
+ width: "100%",
10
+ height: "100%",
11
+ //maxWidth: 350,
12
+ fontWeight: 500,
13
+ padding: "0 1rem",
14
+ borderRadius: "8px",
15
+ color: theme.palette.grey[600],
16
+ backgroundColor: theme.palette.background.paper,
17
+ [theme.breakpoints.down("sm")]: { maxWidth: "100%" },
18
+ "::placeholder": { color: theme.palette.text.disabled },
19
+ }));
20
+
21
+ export function SearchInput(props: InputBaseProps) {
22
+ return (
23
+ <StyledInputBase
24
+ startAdornment={<Search sx={{ fontSize: 19, mr: 1 }} />}
25
+ {...props}
26
+ />
27
+ );
28
+ }
@@ -0,0 +1 @@
1
+ export * from "./site-logo";
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { LogoImage, LogoText, LogoWrapper } from "./styles";
4
+
5
+ //import Link from "next/link";
6
+
7
+ interface SiteLogoProps {
8
+ companyName?: string;
9
+ imgSrc?: string;
10
+ imgHeight?: number;
11
+ }
12
+
13
+ const SiteLogo: React.FC<SiteLogoProps> = ({
14
+ companyName,
15
+ imgSrc,
16
+ imgHeight,
17
+ }) => {
18
+ return (
19
+ <LogoWrapper href="/">
20
+ <LogoImage
21
+ src={`${imgSrc}`}
22
+ imgHeight={imgHeight}
23
+ alt={`${companyName ?? "Logo"} Logo`}
24
+ />
25
+ {companyName && <LogoText>{companyName}</LogoText>}
26
+ </LogoWrapper>
27
+ );
28
+ };
29
+
30
+ export { SiteLogo };
@@ -0,0 +1,34 @@
1
+ import styled from "@mui/material/styles/styled";
2
+ import Typography from "@mui/material/Typography";
3
+
4
+ interface LogoImageProps {
5
+ imgHeight?: number;
6
+ }
7
+
8
+ /* Styled component for Logo */
9
+ export const LogoWrapper = styled("a")(({ theme }) => ({
10
+ display: "flex",
11
+ alignItems: "center",
12
+ gap: theme.spacing(1.5),
13
+ textDecoration: "none",
14
+ }));
15
+
16
+ export const LogoText = styled(Typography)(({ theme }) => ({
17
+ ...theme.typography.h6,
18
+ color: theme.palette.text.primary,
19
+
20
+ [theme.breakpoints.down("sm")]: {
21
+ display: "none",
22
+ },
23
+ }));
24
+
25
+ export const LogoImage = styled("img", {
26
+ shouldForwardProp: (prop) => prop !== "imgHeight",
27
+ })<LogoImageProps>(({ imgHeight = 54, theme }) => ({
28
+ display: "block",
29
+ height: imgHeight,
30
+ width: "auto",
31
+ [theme.breakpoints.down("sm")]: {
32
+ height: imgHeight * 0.67, // smaller on mobile
33
+ },
34
+ }));
@@ -0,0 +1,89 @@
1
+ /* import { Box, Paper, Typography } from "@mui/material";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import Sticky from "./index";
4
+
5
+ const meta: Meta<typeof Sticky> = {
6
+ title: "Utility/Sticky",
7
+ component: Sticky,
8
+ parameters: {
9
+ layout: "fullscreen",
10
+ },
11
+ };
12
+ export default meta;
13
+ type Story = StoryObj<typeof Sticky>;
14
+
15
+ // helper scrollable content
16
+ const ScrollContent = () => (
17
+ <Box sx={{ height: "1500px", p: 2, bgcolor: "grey.100" }}>
18
+ <Typography>Scroll down to see Sticky in action.</Typography>
19
+ </Box>
20
+ );
21
+
22
+ export const Basic: Story = {
23
+ args: {
24
+ fixedOn: 100,
25
+ },
26
+ render: (args) => (
27
+ <Box>
28
+ <Sticky {...args}>
29
+ <Paper
30
+ sx={{
31
+ p: 2,
32
+ bgcolor: "primary.main",
33
+ color: "white",
34
+ textAlign: "center",
35
+ }}
36
+ >
37
+ <Typography>Basic Sticky Header</Typography>
38
+ </Paper>
39
+ </Sticky>
40
+ <ScrollContent />
41
+ </Box>
42
+ ),
43
+ };
44
+
45
+ export const WithScrollDistance: Story = {
46
+ args: {
47
+ fixedOn: 100,
48
+ scrollDistance: 50,
49
+ },
50
+ render: (args) => (
51
+ <Box>
52
+ <Sticky {...args}>
53
+ <Paper
54
+ sx={{
55
+ p: 2,
56
+ bgcolor: "secondary.main",
57
+ color: "white",
58
+ textAlign: "center",
59
+ }}
60
+ >
61
+ <Typography>Becomes sticky at 150px (100 + 50)</Typography>
62
+ </Paper>
63
+ </Sticky>
64
+ <ScrollContent />
65
+ </Box>
66
+ ),
67
+ };
68
+
69
+ export const Sidebar: Story = {
70
+ args: {
71
+ fixedOn: 200,
72
+ },
73
+ render: (args) => (
74
+ <Box sx={{ display: "flex", minHeight: "150vh" }}>
75
+ <Box sx={{ flex: 1, p: 2 }}>
76
+ <Typography>Main content goes here...</Typography>
77
+ <ScrollContent />
78
+ </Box>
79
+ <Box sx={{ width: 250, p: 2 }}>
80
+ <Sticky {...args}>
81
+ <Paper sx={{ p: 2, bgcolor: "info.main", color: "white" }}>
82
+ <Typography>Sticky Sidebar</Typography>
83
+ </Paper>
84
+ </Sticky>
85
+ </Box>
86
+ </Box>
87
+ ),
88
+ };
89
+ */
@@ -0,0 +1 @@
1
+ export * from "./sticky";
@@ -0,0 +1,67 @@
1
+ import clsx from "clsx";
2
+ import {
3
+ type PropsWithChildren,
4
+ useCallback,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import { StyledBox } from "./styles";
10
+
11
+ // ============================================================
12
+ interface Props extends PropsWithChildren {
13
+ fixedOn: number;
14
+ scrollDistance?: number;
15
+ onSticky?: (isFixed: boolean) => void;
16
+ }
17
+
18
+ // ============================================================
19
+
20
+ export function Sticky({
21
+ fixedOn,
22
+ onSticky,
23
+ scrollDistance = 0,
24
+ children,
25
+ }: Props) {
26
+ const [fixed, setFixed] = useState(false);
27
+ const elementRef = useRef<HTMLDivElement>(null);
28
+ const [height, setHeight] = useState(0);
29
+
30
+ const scrollListener = useCallback(() => {
31
+ if (!window) return;
32
+
33
+ const isFixed = window.scrollY >= fixedOn + scrollDistance;
34
+ setFixed(isFixed);
35
+ }, [fixedOn, scrollDistance]);
36
+
37
+ useEffect(() => {
38
+ if (!window) return;
39
+
40
+ window.addEventListener("scroll", scrollListener);
41
+ window.addEventListener("resize", scrollListener);
42
+
43
+ return () => {
44
+ window.removeEventListener("scroll", scrollListener);
45
+ window.removeEventListener("resize", scrollListener);
46
+ };
47
+ }, [scrollListener]);
48
+
49
+ useEffect(() => {
50
+ if (onSticky) onSticky(fixed);
51
+ }, [fixed, onSticky]);
52
+
53
+ useEffect(() => {
54
+ if (elementRef.current) {
55
+ setHeight(elementRef.current.offsetHeight);
56
+ scrollListener();
57
+ }
58
+ }, [scrollListener]);
59
+
60
+ return (
61
+ <StyledBox fixedOn={fixedOn} componentHeight={height} fixed={fixed}>
62
+ <div className={clsx({ hold: !fixed, fixed: fixed })} ref={elementRef}>
63
+ {children}
64
+ </div>
65
+ </StyledBox>
66
+ );
67
+ }
@@ -0,0 +1,38 @@
1
+ import { keyframes, styled } from "@mui/material/styles";
2
+
3
+ // ==============================================================
4
+ interface Props {
5
+ fixed?: boolean;
6
+ fixedOn?: number;
7
+ componentHeight?: number;
8
+ }
9
+ // ==============================================================
10
+
11
+ const slideDown = keyframes`
12
+ from {transform: translateY(-200%)}
13
+ to {transform: translateY(0)}
14
+ `;
15
+
16
+ export const StyledBox = styled("div", {
17
+ shouldForwardProp: (prop) =>
18
+ prop !== "componentHeight" && prop !== "fixed" && prop !== "fixedOn",
19
+ })<Props>(({ theme, componentHeight, fixedOn, fixed }) => ({
20
+ paddingTop: fixed ? componentHeight : 0,
21
+
22
+ "& .hold": {
23
+ zIndex: 5,
24
+ boxShadow: "none",
25
+ position: "relative",
26
+ },
27
+
28
+ "& .fixed": {
29
+ left: 0,
30
+ right: 0,
31
+ zIndex: 1500,
32
+ position: "fixed",
33
+ top: `${fixedOn}px`,
34
+ boxShadow: theme.shadows[2],
35
+ transition: "all 350ms ease-in-out",
36
+ animation: `${slideDown} 400ms ${theme.transitions.easing.easeInOut}`,
37
+ },
38
+ }));
@@ -0,0 +1 @@
1
+ export * from "./typography";
@@ -0,0 +1,226 @@
1
+ "use client";
2
+
3
+ import Box, { type BoxProps } from "@mui/material/Box";
4
+ import styled from "@mui/material/styles/styled";
5
+ import clsx from "clsx";
6
+
7
+ // ==============================================================
8
+ type Ellipsis = { ellipsis: number };
9
+ export interface EllipsisProps extends BoxProps {
10
+ ellipsis?: boolean;
11
+ }
12
+ // ==============================================================
13
+
14
+ const StyledBox = styled(Box, {
15
+ shouldForwardProp: (prop) => prop !== "ellipsis",
16
+ })<Ellipsis>(({ ellipsis }) => ({
17
+ ...(ellipsis && {
18
+ overflow: "hidden",
19
+ whiteSpace: "nowrap",
20
+ textOverflow: "ellipsis",
21
+ }),
22
+ }));
23
+
24
+ export function H1(props: EllipsisProps) {
25
+ const { ellipsis, children, className, ...others } = props;
26
+
27
+ return (
28
+ <StyledBox
29
+ fontSize={30}
30
+ component="h1"
31
+ fontWeight={700}
32
+ ellipsis={ellipsis ? 1 : 0}
33
+ {...(className && { className: clsx({ [className]: true }) })}
34
+ {...others}
35
+ >
36
+ {children}
37
+ </StyledBox>
38
+ );
39
+ }
40
+
41
+ export function H2(props: EllipsisProps) {
42
+ const { ellipsis, children, className, ...others } = props;
43
+
44
+ return (
45
+ <StyledBox
46
+ fontSize={25}
47
+ component="h2"
48
+ fontWeight={700}
49
+ ellipsis={ellipsis ? 1 : 0}
50
+ {...(className && { className: clsx({ [className]: true }) })}
51
+ {...others}
52
+ >
53
+ {children}
54
+ </StyledBox>
55
+ );
56
+ }
57
+
58
+ export function H3(props: EllipsisProps) {
59
+ const { ellipsis, children, className, ...others } = props;
60
+
61
+ return (
62
+ <StyledBox
63
+ fontSize={20}
64
+ component="h3"
65
+ fontWeight={700}
66
+ ellipsis={ellipsis ? 1 : 0}
67
+ {...(className && { className: clsx({ [className]: true }) })}
68
+ {...others}
69
+ >
70
+ {children}
71
+ </StyledBox>
72
+ );
73
+ }
74
+
75
+ export function H4(props: EllipsisProps) {
76
+ const { ellipsis, children, className, ...others } = props;
77
+
78
+ return (
79
+ <StyledBox
80
+ fontSize={17}
81
+ component="h4"
82
+ fontWeight={600}
83
+ ellipsis={ellipsis ? 1 : 0}
84
+ {...(className && { className: clsx({ [className]: true }) })}
85
+ {...others}
86
+ >
87
+ {children}
88
+ </StyledBox>
89
+ );
90
+ }
91
+
92
+ export function H5(props: EllipsisProps) {
93
+ const { ellipsis, children, className, ...others } = props;
94
+
95
+ return (
96
+ <StyledBox
97
+ fontSize={16}
98
+ component="h5"
99
+ lineHeight={1}
100
+ fontWeight={600}
101
+ ellipsis={ellipsis ? 1 : 0}
102
+ {...(className && { className: clsx({ [className]: true }) })}
103
+ {...others}
104
+ >
105
+ {children}
106
+ </StyledBox>
107
+ );
108
+ }
109
+
110
+ export function H6(props: EllipsisProps) {
111
+ const { ellipsis, children, className, ...others } = props;
112
+
113
+ return (
114
+ <StyledBox
115
+ fontSize={14}
116
+ component="h6"
117
+ fontWeight={600}
118
+ ellipsis={ellipsis ? 1 : 0}
119
+ {...(className && { className: clsx({ [className]: true }) })}
120
+ {...others}
121
+ >
122
+ {children}
123
+ </StyledBox>
124
+ );
125
+ }
126
+
127
+ export function Paragraph(props: EllipsisProps) {
128
+ const { ellipsis, children, className, ...others } = props;
129
+
130
+ return (
131
+ <StyledBox
132
+ fontSize={14}
133
+ component="p"
134
+ fontWeight={400}
135
+ ellipsis={ellipsis ? 1 : 0}
136
+ {...(className && { className: clsx({ [className]: true }) })}
137
+ {...others}
138
+ >
139
+ {children}
140
+ </StyledBox>
141
+ );
142
+ }
143
+
144
+ export function Small(props: EllipsisProps) {
145
+ const { ellipsis = false, children, className, ...others } = props;
146
+
147
+ return (
148
+ <StyledBox
149
+ fontSize={12}
150
+ fontWeight={400}
151
+ component="small"
152
+ ellipsis={ellipsis ? 1 : 0}
153
+ {...(className && { className: clsx({ [className]: true }) })}
154
+ {...others}
155
+ >
156
+ {children}
157
+ </StyledBox>
158
+ );
159
+ }
160
+
161
+ export function Span(props: EllipsisProps) {
162
+ const { ellipsis = false, children, className, ...others } = props;
163
+
164
+ return (
165
+ <StyledBox
166
+ component="span"
167
+ ellipsis={ellipsis ? 1 : 0}
168
+ {...(className && { className: clsx({ [className]: true }) })}
169
+ {...others}
170
+ >
171
+ {children}
172
+ </StyledBox>
173
+ );
174
+ }
175
+
176
+ export function Tiny(props: EllipsisProps) {
177
+ const { ellipsis = false, children, className, ...others } = props;
178
+
179
+ return (
180
+ <StyledBox
181
+ component="small"
182
+ fontSize={10}
183
+ fontWeight={400}
184
+ ellipsis={ellipsis ? 1 : 0}
185
+ {...(className && { className: clsx({ [className]: true }) })}
186
+ {...others}
187
+ >
188
+ {children}
189
+ </StyledBox>
190
+ );
191
+ }
192
+
193
+ export function Display(props: EllipsisProps) {
194
+ const { ellipsis, children, className, ...others } = props;
195
+
196
+ return (
197
+ <StyledBox
198
+ fontSize={48}
199
+ component="h1"
200
+ fontWeight={400}
201
+ ellipsis={ellipsis ? 1 : 0}
202
+ {...(className && { className: clsx({ [className]: true }) })}
203
+ {...others}
204
+ >
205
+ {children}
206
+ </StyledBox>
207
+ );
208
+ }
209
+
210
+ export function Subtitle(props: EllipsisProps) {
211
+ const { ellipsis, children, className, ...others } = props;
212
+
213
+ return (
214
+ <StyledBox
215
+ fontSize={11}
216
+ component="h1"
217
+ fontWeight={400}
218
+ sx={{ opacity: 0.7 }}
219
+ ellipsis={ellipsis ? 1 : 0}
220
+ {...(className && { className: clsx({ [className]: true }) })}
221
+ {...others}
222
+ >
223
+ {children}
224
+ </StyledBox>
225
+ );
226
+ }
@@ -0,0 +1 @@
1
+ export { default as User } from "./user";
@@ -0,0 +1,20 @@
1
+ import Avatar from "@mui/material/Avatar";
2
+ import IconButton from "@mui/material/IconButton";
3
+ import styled from "@mui/material/styles/styled";
4
+
5
+ export const UserIconButton = styled(IconButton)(({ theme }) => ({
6
+ padding: 4,
7
+ borderRadius: "50%",
8
+ "&:hover": {
9
+ backgroundColor: theme.palette.primary.light,
10
+ },
11
+ }));
12
+
13
+ export const UserAvatar = styled(Avatar)(({ theme }) => ({
14
+ width: 34,
15
+ height: 34,
16
+ fontSize: 14,
17
+ fontWeight: 500,
18
+ backgroundColor: theme.palette.primary.main,
19
+ color: theme.palette.secondary.main,
20
+ }));
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import { Menu, MenuItem } from "@mui/material";
4
+ import type React from "react";
5
+ import { useState } from "react";
6
+ import { Button } from "../button";
7
+ import { UserAvatar, UserIconButton } from "./styles/index";
8
+
9
+ export type UserProps = {
10
+ user?: {
11
+ name: string;
12
+ email: string;
13
+ image?: string;
14
+ };
15
+ isAuthenticated: boolean;
16
+ isLoading: boolean;
17
+ onLogin: () => void;
18
+ onLogout: () => void;
19
+ };
20
+
21
+ const User: React.FC<UserProps> = ({
22
+ user,
23
+ isAuthenticated,
24
+ isLoading,
25
+ onLogin,
26
+ onLogout,
27
+ }) => {
28
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
29
+ const menuOpen = Boolean(anchorEl);
30
+
31
+ const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) =>
32
+ setAnchorEl(event.currentTarget);
33
+ const handleMenuClose = () => setAnchorEl(null);
34
+
35
+ if (isLoading) return null;
36
+
37
+ return isAuthenticated ? (
38
+ <>
39
+ <UserIconButton
40
+ onClick={handleMenuOpen}
41
+ style={{ padding: 4, borderRadius: "50%" }}
42
+ >
43
+ <UserAvatar alt={user?.name ?? "User"} src={user?.image ?? undefined}>
44
+ {user?.name?.[0]}
45
+ </UserAvatar>
46
+ </UserIconButton>
47
+
48
+ <Menu
49
+ anchorEl={anchorEl}
50
+ open={menuOpen}
51
+ onClose={handleMenuClose}
52
+ anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
53
+ transformOrigin={{ vertical: "top", horizontal: "right" }}
54
+ >
55
+ <MenuItem disabled>{user?.email}</MenuItem>
56
+ <MenuItem onClick={handleMenuClose}>Profile</MenuItem>
57
+ <MenuItem
58
+ onClick={() => {
59
+ handleMenuClose();
60
+ onLogout();
61
+ }}
62
+ >
63
+ Logout
64
+ </MenuItem>
65
+ </Menu>
66
+ </>
67
+ ) : (
68
+ <Button onClick={onLogin} variant="contained">
69
+ Login
70
+ </Button>
71
+ );
72
+ };
73
+
74
+ export default User;
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Contains re-usable react components built on top of MUI,
3
+ * theme code for MUI and utilities for look and feel of
4
+ * e4 products.
5
+ *
6
+ * @module e4 UI
7
+ */
8
+ export * from "./components";
9
+ export * from "./theme";
10
+ export * from "./utils";