@canmingir/link 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,313 @@
1
+ import React from "react";
2
+
3
+ import { Box, Card, Stack } from "@mui/material";
4
+ import { alpha, useTheme } from "@mui/material/styles";
5
+
6
+ export function MediaAvatarCard({
7
+ sx,
8
+ leftContent,
9
+ rightContent,
10
+ background,
11
+ overlayColor,
12
+ backgroundClassName = "animated-background-image",
13
+ }) {
14
+ const theme = useTheme();
15
+
16
+ return (
17
+ <Card
18
+ sx={{
19
+ p: 0,
20
+ minWidth: 350,
21
+ maxWidth: 450,
22
+ height: 120,
23
+ borderRadius: 1.5,
24
+ textAlign: "left",
25
+ position: "relative",
26
+ display: "flex",
27
+ flexDirection: "row",
28
+ boxShadow: (theme) => theme.shadows[3],
29
+ "&:hover": {
30
+ boxShadow: (theme) => theme.shadows[6],
31
+ },
32
+ overflow: "hidden",
33
+ [`&:hover .${backgroundClassName}`]: {
34
+ width: "100%",
35
+ },
36
+ ...sx,
37
+ }}
38
+ >
39
+ {background && (
40
+ <Box
41
+ className={backgroundClassName}
42
+ sx={{
43
+ width: 100,
44
+ height: "100%",
45
+ position: "absolute",
46
+ top: 0,
47
+ left: 0,
48
+ zIndex: 0,
49
+ backgroundImage: `url(${background})`,
50
+ backgroundSize: "cover",
51
+ backgroundPosition: "center",
52
+ transition: theme.transitions.create("width", {
53
+ duration: theme.transitions.duration.standard,
54
+ easing: theme.transitions.easing.easeInOut,
55
+ }),
56
+ "&::after": {
57
+ content: '""',
58
+ position: "absolute",
59
+ inset: 0,
60
+ backgroundColor:
61
+ overlayColor || alpha(theme.palette.grey[900], 0.48),
62
+ },
63
+ }}
64
+ />
65
+ )}
66
+
67
+ <Stack
68
+ direction="column"
69
+ spacing={1}
70
+ sx={{
71
+ width: 100,
72
+ height: "100%",
73
+ justifyContent: "center",
74
+ alignItems: "center",
75
+ borderRadius: "8px 0 0 8px",
76
+ position: "relative",
77
+ zIndex: 1,
78
+ }}
79
+ >
80
+ {leftContent}
81
+ </Stack>
82
+
83
+ <Stack
84
+ direction="column"
85
+ spacing={1}
86
+ sx={{
87
+ flex: 1,
88
+ padding: 2,
89
+ zIndex: 1,
90
+ position: "relative",
91
+ }}
92
+ >
93
+ {rightContent}
94
+ </Stack>
95
+ </Card>
96
+ );
97
+ }
98
+
99
+ export function HeaderCard({
100
+ sx,
101
+ header,
102
+ children,
103
+ height = 140,
104
+ padding = 2,
105
+ }) {
106
+ const theme = useTheme();
107
+
108
+ return (
109
+ <Card
110
+ sx={{
111
+ p: padding,
112
+ minWidth: 200,
113
+ maxWidth: 420,
114
+ height,
115
+ borderRadius: 3,
116
+ position: "relative",
117
+ overflow: "hidden",
118
+ backgroundColor: theme.palette.background.paper,
119
+ transition: "all 0.3s ease-in-out",
120
+ "&:hover": {
121
+ transform: "translateY(-4px)",
122
+ boxShadow: theme.shadows[8],
123
+ "&:before": {
124
+ height: "100%",
125
+ },
126
+ },
127
+ "&:before": {
128
+ content: '""',
129
+ width: "100%",
130
+ height: "40%",
131
+ position: "absolute",
132
+ top: 0,
133
+ left: 0,
134
+ background: `linear-gradient(135deg, ${alpha(
135
+ theme.palette.secondary.light,
136
+ 0.2
137
+ )}, ${alpha(theme.palette.primary.main, 0.3)})`,
138
+ borderRadius: "8px 8px 0 0",
139
+ transition: "height 0.3s ease-in-out",
140
+ },
141
+ ...sx,
142
+ }}
143
+ >
144
+ <Stack
145
+ spacing={2}
146
+ sx={{
147
+ height: "100%",
148
+ position: "relative",
149
+ zIndex: 1,
150
+ }}
151
+ >
152
+ {header}
153
+ {children}
154
+ </Stack>
155
+ </Card>
156
+ );
157
+ }
158
+
159
+ export function SideStripeCard({
160
+ sx,
161
+ stripeContent,
162
+ mainContent,
163
+ minWidth = 200,
164
+ maxWidth = 290,
165
+ height = 100,
166
+ stripeWidth = 65,
167
+ }) {
168
+ const theme = useTheme();
169
+
170
+ return (
171
+ <Box
172
+ sx={{
173
+ cursor: "pointer",
174
+ }}
175
+ >
176
+ <Card
177
+ sx={{
178
+ display: "flex",
179
+ flexDirection: "row",
180
+ alignItems: "center",
181
+ minWidth,
182
+ maxWidth,
183
+ height,
184
+ borderRadius: 1.5,
185
+ boxShadow: 3,
186
+ position: "relative",
187
+ overflow: "hidden",
188
+ "&:hover": {
189
+ boxShadow: 6,
190
+ "& .animated-background-selector": {
191
+ width: "100%",
192
+ borderRadius: 1.5,
193
+ },
194
+ "& .responsibility-title, & .responsibility-description": {
195
+ color: theme.palette.getContrastText(theme.palette.primary.light),
196
+ },
197
+ },
198
+ ...sx,
199
+ }}
200
+ >
201
+ <Box
202
+ className="animated-background-selector"
203
+ sx={{
204
+ position: "absolute",
205
+ top: 0,
206
+ left: 0,
207
+ bottom: 0,
208
+ width: stripeWidth,
209
+ backgroundColor: theme.palette.primary.light,
210
+ borderRadius: "8px 0 0 8px",
211
+ transition: theme.transitions.create(["width", "border-radius"], {
212
+ duration: theme.transitions.duration.short,
213
+ }),
214
+ zIndex: 0,
215
+ }}
216
+ />
217
+ <Stack
218
+ direction="column"
219
+ spacing={1}
220
+ sx={{
221
+ position: "relative",
222
+ zIndex: 1,
223
+ width: stripeWidth,
224
+ height: "100%",
225
+ display: "flex",
226
+ justifyContent: "center",
227
+ alignItems: "center",
228
+ padding: 1,
229
+ }}
230
+ >
231
+ {stripeContent}
232
+ </Stack>
233
+
234
+ <Stack
235
+ direction="column"
236
+ spacing={1}
237
+ sx={{
238
+ padding: 2,
239
+ position: "relative",
240
+ zIndex: 1,
241
+ }}
242
+ >
243
+ {mainContent}
244
+ </Stack>
245
+ </Card>
246
+ </Box>
247
+ );
248
+ }
249
+
250
+ export function AvatarRoleCard({
251
+ sx,
252
+ leftContent,
253
+ topRightContent,
254
+ bottomContent,
255
+ }) {
256
+ const theme = useTheme();
257
+
258
+ return (
259
+ <Card
260
+ sx={{
261
+ p: 2.5,
262
+ width: 230,
263
+ height: 120,
264
+ borderRadius: 2,
265
+ position: "relative",
266
+ backgroundColor: theme.palette.background.paper,
267
+ transition: "all 0.3s ease-in-out",
268
+ "&:hover": {
269
+ transform: "translateY(-4px)",
270
+ boxShadow: theme.shadows[8],
271
+ "&:before": {
272
+ height: "100%",
273
+ },
274
+ },
275
+ "&:before": {
276
+ content: '""',
277
+ width: "100%",
278
+ height: "40%",
279
+ position: "absolute",
280
+ top: 0,
281
+ left: 0,
282
+ background: `linear-gradient(135deg, ${alpha(
283
+ theme.palette.secondary.light,
284
+ 0.2
285
+ )}, ${alpha(theme.palette.primary.main, 0.3)})`,
286
+ borderRadius: "8px 8px 0 0",
287
+ transition: "height 0.3s ease-in-out",
288
+ },
289
+ ...sx,
290
+ }}
291
+ >
292
+ <Stack
293
+ spacing={1}
294
+ sx={{
295
+ height: "100%",
296
+ position: "relative",
297
+ zIndex: 1,
298
+ }}
299
+ >
300
+ <Stack
301
+ direction="row"
302
+ justifyContent="space-between"
303
+ alignItems="flex-start"
304
+ >
305
+ {leftContent}
306
+ {topRightContent}
307
+ </Stack>
308
+
309
+ {bottomContent && <Stack spacing={0.5}>{bottomContent}</Stack>}
310
+ </Stack>
311
+ </Card>
312
+ );
313
+ }
@@ -0,0 +1,196 @@
1
+ import { Iconify } from "@canmingir/link/platform/components";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { alpha, styled } from "@mui/material/styles";
5
+
6
+ const ANIMATION_DELAY_MS = 200;
7
+
8
+ const MainContainer = styled("div", {
9
+ shouldForwardProp: (prop) => prop !== "$style",
10
+ })(({ theme, $style = {} }) => {
11
+ const {
12
+ minWidth = 120,
13
+ minHeight = 120,
14
+ maxWidth = 160,
15
+ maxHeight = 160,
16
+ borderRadius = 16,
17
+ borderColor = "rgba(255, 255, 255, 0.2)",
18
+ bgFrom,
19
+ bgTo,
20
+ } = $style;
21
+
22
+ const defaultFrom = alpha(theme.palette.secondary.light, 0.2);
23
+ const defaultTo = alpha(theme.palette.primary.main, 0.3);
24
+
25
+ const hoveredFrom = alpha(theme.palette.primary.main, 0.3);
26
+ const hoveredTo = alpha(theme.palette.secondary.light, 0.2);
27
+
28
+ return {
29
+ display: "flex",
30
+ flexDirection: "column",
31
+ alignItems: "center",
32
+ color: "#ffffff",
33
+ borderRadius,
34
+ padding: "16px 20px",
35
+ border: `1px solid ${borderColor}`,
36
+ backdropFilter: "blur(10px)",
37
+ transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
38
+ cursor: "pointer",
39
+ position: "relative",
40
+ overflow: "hidden",
41
+ minWidth,
42
+ minHeight,
43
+ maxWidth,
44
+ maxHeight,
45
+ background: `linear-gradient(135deg, ${bgFrom || defaultFrom}, ${
46
+ bgTo || defaultTo
47
+ })`,
48
+ boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
49
+
50
+ '&[data-hovered="true"]': {
51
+ background: `linear-gradient(135deg, ${bgTo || hoveredFrom}, ${
52
+ bgFrom || hoveredTo
53
+ })`,
54
+ boxShadow: "0 4px 20px rgba(0, 0, 0, 0.3)",
55
+ },
56
+ };
57
+ });
58
+
59
+ const IconContainer = styled("div", {
60
+ shouldForwardProp: (prop) => prop !== "$style",
61
+ })(({ $style = {} }) => {
62
+ const {
63
+ bg = "rgba(255, 255, 255, 0.1)",
64
+ hoverBg = "rgba(255, 255, 255, 0.15)",
65
+ borderRadius = 12,
66
+ padding = 8,
67
+ borderColor = "rgba(255, 255, 255, 0.3)",
68
+ marginBottom = 12,
69
+ } = $style;
70
+
71
+ return {
72
+ backgroundColor: bg,
73
+ borderRadius,
74
+ padding,
75
+ marginBottom,
76
+ transition: "all 0.4s ease",
77
+ backdropFilter: "blur(5px)",
78
+ border: `1px solid ${borderColor}`,
79
+
80
+ '[data-hovered="true"] &': {
81
+ backgroundColor: hoverBg,
82
+ },
83
+ };
84
+ });
85
+
86
+ const LabelText = styled("div", {
87
+ shouldForwardProp: (prop) => prop !== "$style",
88
+ })(({ $style = {} }) => {
89
+ const {
90
+ color = "#ffffff",
91
+ fontWeight = 600,
92
+ fontSize = 13,
93
+ letterSpacing = 0.5,
94
+ } = $style;
95
+
96
+ return {
97
+ fontWeight,
98
+ fontSize,
99
+ letterSpacing: `${letterSpacing}px`,
100
+ textAlign: "center",
101
+ transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
102
+ textShadow: "none",
103
+ lineHeight: "1.4",
104
+ opacity: 0,
105
+ transform: "translateY(10px)",
106
+ color,
107
+
108
+ '&[data-animated="true"]': {
109
+ opacity: 1,
110
+ transform: "translateY(0)",
111
+ },
112
+ };
113
+ });
114
+
115
+ const getIcon = (node) => {
116
+ if (node.icon) return node.icon;
117
+ if (node.type === "CONDITION" || node.type === "decision")
118
+ return "mdi:help-circle-outline";
119
+ if (node.type === "NORMAL") return "mdi:checkbox-blank-circle-outline";
120
+ return "mdi:cube-outline";
121
+ };
122
+
123
+ const InfoNode = ({ node, nodeStyle = {} }) => {
124
+ const [animated, setAnimated] = useState(false);
125
+ const [isHovered, setIsHovered] = useState(false);
126
+
127
+ const label =
128
+ node.label || node.title || node.name || node.id || "Responsibility";
129
+
130
+ useEffect(() => {
131
+ const timer = setTimeout(() => setAnimated(true), ANIMATION_DELAY_MS);
132
+ return () => clearTimeout(timer);
133
+ }, []);
134
+
135
+ const layoutStyle = {
136
+ minWidth: nodeStyle.cardWidth || nodeStyle.minWidth || 120,
137
+ minHeight: nodeStyle.minHeight || 120,
138
+ maxWidth: nodeStyle.maxWidth || 160,
139
+ maxHeight: nodeStyle.maxHeight || 160,
140
+ borderRadius: typeof nodeStyle.shape === "number" ? nodeStyle.shape : 16,
141
+ borderColor: nodeStyle.borderColor || "rgba(255, 255, 255, 0.2)",
142
+ bgFrom: nodeStyle.bgFrom,
143
+ bgTo: nodeStyle.bgTo,
144
+ };
145
+
146
+ const iconContainerStyle = {
147
+ iconBg: nodeStyle.iconBg,
148
+ iconHoverBg: nodeStyle.iconHoverBg,
149
+ iconBorderRadius: nodeStyle.iconRadius,
150
+ iconPadding: nodeStyle.iconPadding,
151
+ iconBorderColor: nodeStyle.iconBorderColor,
152
+ iconMarginBottom: nodeStyle.iconMarginBottom,
153
+ };
154
+
155
+ const labelStyle = {
156
+ labelColor: nodeStyle.labelColor,
157
+ labelFontSize: nodeStyle.labelFontSize,
158
+ labelFontWeight: nodeStyle.labelFontWeight,
159
+ labelLetterSpacing: nodeStyle.labelLetterSpacing,
160
+ };
161
+
162
+ const icon = getIcon(node);
163
+
164
+ return (
165
+ <MainContainer
166
+ $style={layoutStyle}
167
+ data-hovered={isHovered}
168
+ onMouseEnter={() => setIsHovered(true)}
169
+ onMouseLeave={() => setIsHovered(false)}
170
+ >
171
+ <IconContainer $style={iconContainerStyle}>
172
+ <Iconify
173
+ icon={icon}
174
+ width={28}
175
+ height={28}
176
+ sx={{
177
+ transition: "all 0.6s cubic-bezier(0.4, 0, 0.2, 1)",
178
+ transform: animated
179
+ ? isHovered
180
+ ? "rotate(360deg) scale(1.1)"
181
+ : "rotate(0deg) scale(1)"
182
+ : "rotate(-90deg) scale(0.8)",
183
+ filter: "none",
184
+ color: nodeStyle.iconColor || "#ffffff",
185
+ }}
186
+ />
187
+ </IconContainer>
188
+
189
+ <LabelText data-animated={animated} $style={labelStyle}>
190
+ {label}
191
+ </LabelText>
192
+ </MainContainer>
193
+ );
194
+ };
195
+
196
+ export default InfoNode;
@@ -0,0 +1,37 @@
1
+ import AnimatedNode from "./AnimatedNode";
2
+ import React from "react";
3
+
4
+ import { Card, CircularProgress, Typography } from "@mui/material";
5
+
6
+ const LoadingNode = ({ visible, delay }) => (
7
+ <AnimatedNode visible={visible} delay={delay}>
8
+ <Card
9
+ sx={{
10
+ p: 2,
11
+ width: 180,
12
+ height: 100,
13
+ display: "flex",
14
+ flexDirection: "column",
15
+ alignItems: "center",
16
+ justifyContent: "center",
17
+ gap: 1,
18
+ borderRadius: 1,
19
+ bgcolor: "background.paper",
20
+ }}
21
+ >
22
+ <CircularProgress size={24} />
23
+ <Typography
24
+ variant="caption"
25
+ sx={{
26
+ textAlign: "center",
27
+ fontSize: "0.5rem",
28
+ color: "text.secondary",
29
+ }}
30
+ >
31
+ Loading...
32
+ </Typography>
33
+ </Card>
34
+ </AnimatedNode>
35
+ );
36
+
37
+ export default LoadingNode;
@@ -0,0 +1,114 @@
1
+ export const toPxNumber = (v, fallback = 1) => {
2
+ if (v == null) return fallback;
3
+ if (typeof v === "number") return v;
4
+ const n = parseFloat(String(v));
5
+ return Number.isFinite(n) ? n : fallback;
6
+ };
7
+
8
+ export const getBaseStyleForVariant = (v) => {
9
+ switch (v) {
10
+ case "card":
11
+ return {
12
+ lineColor: "#BDBDBD",
13
+ lineWidth: "2px",
14
+ lineStyle: "solid",
15
+ gap: 56,
16
+ shape: 8,
17
+ bg: "background.paper",
18
+ hoverBg: "grey.100",
19
+ borderColor: "#9E9E9E",
20
+ };
21
+ case "pill":
22
+ return {
23
+ lineColor: "#9C27B0",
24
+ lineWidth: "2px",
25
+ lineStyle: "dashed",
26
+ gap: 48,
27
+ shape: 9999,
28
+ bg: "rgba(156, 39, 176, 0.08)",
29
+ hoverBg: "rgba(156, 39, 176, 0.16)",
30
+ borderColor: "#9C27B0",
31
+ };
32
+ case "simple":
33
+ default:
34
+ return {
35
+ lineColor: "#E0E0E0",
36
+ lineWidth: "1.5px",
37
+ lineStyle: "solid",
38
+ gap: 40,
39
+ shape: 4,
40
+ bg: "background.default",
41
+ hoverBg: "grey.100",
42
+ borderColor: "#E0E0E0",
43
+ };
44
+ }
45
+ };
46
+
47
+ export const getDecisionNodeStyle = (nodeType) => {
48
+ const styles = {
49
+ start: { bg: "#E8F5E9", borderColor: "#4CAF50", shape: 8 },
50
+ decision: { bg: "#FFF3E0", borderColor: "#FF9800", shape: 24 },
51
+ process: { bg: "#E3F2FD", borderColor: "#2196F3", shape: 8 },
52
+ end: { bg: "#FFEBEE", borderColor: "#F44336", shape: 8 },
53
+ };
54
+ return styles[nodeType] || styles.process;
55
+ };
56
+
57
+ export const applySemanticTokens = (styleObj, base) => {
58
+ const s = { ...styleObj };
59
+
60
+ const borderWeightMap = { light: 1, normal: 3, bold: 5 };
61
+ if (
62
+ typeof s.border === "string" &&
63
+ ["light", "normal", "bold"].includes(s.border)
64
+ ) {
65
+ const w = borderWeightMap[s.border];
66
+ if (s.borderWidth == null) s.borderWidth = w;
67
+ if (s.lineWidth == null) s.lineWidth = `${w}px`;
68
+ if (!s.borderColor)
69
+ s.borderColor = base.borderColor || base.lineColor || "#E0E0E0";
70
+ } else if (
71
+ typeof s.border === "string" &&
72
+ (s.border.startsWith("#") || s.border.startsWith("rgb"))
73
+ ) {
74
+ s.borderColor = s.border;
75
+ }
76
+
77
+ // size: small | medium | large
78
+ const sizeMap = {
79
+ small: { cardWidth: 140, gap: 20 },
80
+ medium: { cardWidth: 180, gap: 30 },
81
+ large: { cardWidth: 220, gap: 40 },
82
+ };
83
+ if (s.size && sizeMap[s.size]) {
84
+ const { cardWidth, gap } = sizeMap[s.size];
85
+ if (s.cardWidth == null) s.cardWidth = cardWidth;
86
+ if (s.gap == null) s.gap = gap;
87
+ }
88
+
89
+ // shape: "square" | "vertical" (affects dimensions)
90
+ if (s.shape === "square") {
91
+ const defaultSize = 140;
92
+ if (s.cardWidth != null && s.minHeight == null) s.minHeight = s.cardWidth;
93
+ else if (s.minHeight != null && s.cardWidth == null)
94
+ s.cardWidth = s.minHeight;
95
+ else if (s.cardWidth == null && s.minHeight == null) {
96
+ s.cardWidth = defaultSize;
97
+ s.minHeight = defaultSize;
98
+ }
99
+ }
100
+
101
+ if (s.shape === "vertical") {
102
+ const defaultWidth = 160;
103
+ if (s.cardWidth == null) s.cardWidth = defaultWidth;
104
+ if (s.minHeight == null) s.minHeight = Math.max(s.cardWidth * 1.3, 110);
105
+ }
106
+
107
+ // shadow: "none" | "soft" | "heavy"
108
+ const shadowVariantMap = { none: 0, soft: 2, heavy: 6 };
109
+ if (s.shadow && shadowVariantMap[s.shadow] != null && s.shadowLevel == null) {
110
+ s.shadowLevel = shadowVariantMap[s.shadow];
111
+ }
112
+
113
+ return s;
114
+ };
package/src/lib/index.js CHANGED
@@ -21,3 +21,16 @@ export { default as TableHeadCustom } from "./TableHeadCustom/TableHeadCustom";
21
21
  export { default as TableSelectedAction } from "./TableSelectedAction/TableSelectedAction";
22
22
  export { default as useTable } from "./useTable/useTable";
23
23
  export { default as useChart } from "./useChart/useChart";
24
+
25
+ export { default as Flow } from "./Flow/Flow";
26
+
27
+ export {
28
+ HeaderCard,
29
+ MediaAvatarCard,
30
+ SideStripeCard,
31
+ AvatarRoleCard,
32
+ } from "./Flow/layouts/CardLayout";
33
+
34
+ export { default as ActionNode } from "./Flow/layouts/ActionNode";
35
+
36
+ export { default as InfoNode } from "./Flow/layouts/InfoNode";
@@ -1,7 +1,7 @@
1
- import { alpha, useTheme } from "@mui/material/styles";
2
-
3
1
  import merge from "lodash/merge";
4
- import { useResponsive } from "src/hooks/use-responsive";
2
+ import { useResponsive } from "../../hooks/use-responsive";
3
+
4
+ import { alpha, useTheme } from "@mui/material/styles";
5
5
 
6
6
  export default function useChart(options) {
7
7
  const theme = useTheme();