@hitachivantara/uikit-cli 6.0.1

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 (109) hide show
  1. package/README.md +46 -0
  2. package/package.json +68 -0
  3. package/src/app-shell.js +106 -0
  4. package/src/baselines/app-shell/vite/_gitignore +30 -0
  5. package/src/baselines/app-shell/vite/_oxlintrc.json +5 -0
  6. package/src/baselines/app-shell/vite/_package.json +55 -0
  7. package/src/baselines/app-shell/vite/public/locales/en/example.json +8 -0
  8. package/src/baselines/app-shell/vite/src/lib/data/config.ts +15 -0
  9. package/src/baselines/app-shell/vite/src/lib/i18n.ts +44 -0
  10. package/src/baselines/app-shell/vite/src/pages/Example/index.tsx +25 -0
  11. package/src/baselines/app-shell/vite/src/providers/Provider.tsx +31 -0
  12. package/src/baselines/app-shell/vite/src/tests/mocks.ts +1 -0
  13. package/src/baselines/app-shell/vite/src/tests/providers.tsx +13 -0
  14. package/src/baselines/app-shell/vite/src/tests/setupTests.ts +24 -0
  15. package/src/baselines/app-shell/vite/src/types/theme.d.ts +8 -0
  16. package/src/baselines/app-shell/vite/src/types/vite-env.d.ts +1 -0
  17. package/src/baselines/app-shell/vite/tsconfig.json +10 -0
  18. package/src/baselines/app-shell/vite/tsconfig.node.json +9 -0
  19. package/src/baselines/app-shell/vite/uno.config.ts +6 -0
  20. package/src/baselines/app-shell/vite/vite.config.ts +45 -0
  21. package/src/baselines/vite/_gitignore +30 -0
  22. package/src/baselines/vite/_oxlintrc.json +5 -0
  23. package/src/baselines/vite/_package.json +53 -0
  24. package/src/baselines/vite/index.html +18 -0
  25. package/src/baselines/vite/public/favicon.ico +0 -0
  26. package/src/baselines/vite/public/locales/en/common.json +16 -0
  27. package/src/baselines/vite/public/locales/en/home.json +4 -0
  28. package/src/baselines/vite/public/logo192.png +0 -0
  29. package/src/baselines/vite/src/App.tsx +31 -0
  30. package/src/baselines/vite/src/assets/HitachiLogo.tsx +27 -0
  31. package/src/baselines/vite/src/components/common/Loading/Loading.test.tsx +18 -0
  32. package/src/baselines/vite/src/components/common/Loading/Loading.tsx +15 -0
  33. package/src/baselines/vite/src/components/common/Loading/index.ts +1 -0
  34. package/src/baselines/vite/src/context/NavigationContext.tsx +67 -0
  35. package/src/baselines/vite/src/lib/i18n.ts +29 -0
  36. package/src/baselines/vite/src/main.tsx +12 -0
  37. package/src/baselines/vite/src/pages/Home/index.tsx +13 -0
  38. package/src/baselines/vite/src/pages/NotFound/NotFound.tsx +39 -0
  39. package/src/baselines/vite/src/pages/NotFound/index.tsx +1 -0
  40. package/src/baselines/vite/src/pages/layout/navigation.tsx +82 -0
  41. package/src/baselines/vite/src/routes.tsx +14 -0
  42. package/src/baselines/vite/src/tests/mocks.ts +1 -0
  43. package/src/baselines/vite/src/tests/providers.tsx +13 -0
  44. package/src/baselines/vite/src/tests/setupTests.ts +24 -0
  45. package/src/baselines/vite/src/types/theme.d.ts +8 -0
  46. package/src/baselines/vite/src/vite-env.d.ts +1 -0
  47. package/src/baselines/vite/tsconfig.json +10 -0
  48. package/src/baselines/vite/tsconfig.node.json +9 -0
  49. package/src/baselines/vite/uno.config.ts +6 -0
  50. package/src/baselines/vite/vite.config.ts +31 -0
  51. package/src/contents.js +63 -0
  52. package/src/create.js +172 -0
  53. package/src/index.js +22 -0
  54. package/src/navigation.js +21 -0
  55. package/src/package.js +37 -0
  56. package/src/plop-templates/README.md.hbs +10 -0
  57. package/src/plop-templates/app-shell/app-shell.config.ts.hbs +54 -0
  58. package/src/plop-templates/app-shell/index.html.hbs +15 -0
  59. package/src/plopfile.js +61 -0
  60. package/src/templates/AssetInventory/CardView.tsx +167 -0
  61. package/src/templates/AssetInventory/ListView.tsx +56 -0
  62. package/src/templates/AssetInventory/data.tsx +255 -0
  63. package/src/templates/AssetInventory/index.tsx +198 -0
  64. package/src/templates/AssetInventory/usePaginationData.ts +158 -0
  65. package/src/templates/Canvas/Context.tsx +49 -0
  66. package/src/templates/Canvas/ListView.tsx +189 -0
  67. package/src/templates/Canvas/Node.tsx +203 -0
  68. package/src/templates/Canvas/Sidebar.tsx +51 -0
  69. package/src/templates/Canvas/StatusEdge.tsx +75 -0
  70. package/src/templates/Canvas/StickyNode.tsx +475 -0
  71. package/src/templates/Canvas/Table.tsx +202 -0
  72. package/src/templates/Canvas/TreeView.tsx +211 -0
  73. package/src/templates/Canvas/dependencies.json +7 -0
  74. package/src/templates/Canvas/index.tsx +363 -0
  75. package/src/templates/Canvas/styles.tsx +41 -0
  76. package/src/templates/Canvas/utils.tsx +70 -0
  77. package/src/templates/Dashboard/GridPanel.tsx +33 -0
  78. package/src/templates/Dashboard/Kpi.tsx +107 -0
  79. package/src/templates/Dashboard/Map.styles.ts +681 -0
  80. package/src/templates/Dashboard/Map.tsx +71 -0
  81. package/src/templates/Dashboard/data.ts +67 -0
  82. package/src/templates/Dashboard/dependencies.json +11 -0
  83. package/src/templates/Dashboard/index.tsx +173 -0
  84. package/src/templates/DetailsView/KPIs.tsx +70 -0
  85. package/src/templates/DetailsView/MetadataItem.tsx +35 -0
  86. package/src/templates/DetailsView/Properties.tsx +127 -0
  87. package/src/templates/DetailsView/Table.tsx +104 -0
  88. package/src/templates/DetailsView/data.ts +67 -0
  89. package/src/templates/DetailsView/index.tsx +102 -0
  90. package/src/templates/DetailsView/usePaginationData.ts +155 -0
  91. package/src/templates/DetailsView/utils.ts +51 -0
  92. package/src/templates/Form/index.tsx +107 -0
  93. package/src/templates/KanbanBoard/ColumnContainer.tsx +89 -0
  94. package/src/templates/KanbanBoard/TaskCard.tsx +130 -0
  95. package/src/templates/KanbanBoard/data.tsx +140 -0
  96. package/src/templates/KanbanBoard/dependencies.json +6 -0
  97. package/src/templates/KanbanBoard/index.tsx +179 -0
  98. package/src/templates/KanbanBoard/styles.tsx +76 -0
  99. package/src/templates/KanbanBoard/types.ts +21 -0
  100. package/src/templates/ListView/Indicator.tsx +42 -0
  101. package/src/templates/ListView/Kpi.tsx +120 -0
  102. package/src/templates/ListView/Table.tsx +55 -0
  103. package/src/templates/ListView/data.tsx +179 -0
  104. package/src/templates/ListView/dependencies.json +5 -0
  105. package/src/templates/ListView/index.tsx +245 -0
  106. package/src/templates/ListView/usePaginationData.ts +158 -0
  107. package/src/templates/Welcome/index.tsx +101 -0
  108. package/src/templates/package.json +30 -0
  109. package/src/utils.js +37 -0
@@ -0,0 +1,67 @@
1
+ import useSWR from "swr";
2
+
3
+ import {
4
+ delay,
5
+ ServerPaginationProps,
6
+ useServerPagination,
7
+ } from "./usePaginationData";
8
+ import { createEntry, DetailsViewEntry } from "./utils";
9
+
10
+ // --- Data ---
11
+
12
+ const TOTAL = 10;
13
+
14
+ const db = [...Array(TOTAL).keys()].map(createEntry);
15
+
16
+ const model = {
17
+ description: "Model created from the example Jupyter Notebook",
18
+ status: "Critical",
19
+ severity: "Medium",
20
+ tags: ["test", "note"],
21
+ project: "Wine Quality",
22
+ progress: 0.87,
23
+ risk: 0.2,
24
+ asc: "Failure Prediction",
25
+ createdAt: "2022-05-24 14:32:50",
26
+ modifiedAt: "2022-05-24 14:32:50",
27
+ imageUrl: "https://i.imgur.com/5EW6x5r.jpg",
28
+ deploys: {
29
+ summary: [
30
+ { id: 1, title: "Success Requests", count: 4, diff: 2.02 },
31
+ { id: 2, title: "Error Requests", count: 2, diff: -1.63 },
32
+ { id: 3, title: "Open Requests", count: 12, diff: 1.84 },
33
+ ],
34
+ data: [
35
+ { id: 2, title: "Update Build", value: "2020/12/1" },
36
+ { id: 3, title: "Clean Data Logs", value: "2018/5/3" },
37
+ { id: 6, title: "Build", value: "46uYmU" },
38
+ ],
39
+ },
40
+ };
41
+
42
+ export type ModelDetails = typeof model;
43
+
44
+ // #region Endpoints
45
+ export interface PaginationDataProps
46
+ extends Omit<ServerPaginationProps<DetailsViewEntry>, "endpoint" | "db"> {
47
+ id: string;
48
+ }
49
+
50
+ export const usePaginationData = (props: PaginationDataProps) => {
51
+ const endpoint = `/model/${props.id}/events`;
52
+ return useServerPagination({ endpoint, db, ...props });
53
+ };
54
+
55
+ export const useModelData = () => {
56
+ return useSWR(
57
+ "model",
58
+ async () => {
59
+ // Loading
60
+ await delay(800);
61
+
62
+ return model;
63
+ },
64
+ { suspense: true },
65
+ );
66
+ };
67
+ // #endregion
@@ -0,0 +1,102 @@
1
+ import { ElementType, ReactNode, Suspense, useState } from "react";
2
+ import { css } from "@emotion/css";
3
+ import {
4
+ HvButton,
5
+ HvGlobalActions,
6
+ HvGlobalActionsProps,
7
+ HvGrid,
8
+ HvGridProps,
9
+ HvLoading,
10
+ theme,
11
+ } from "@hitachivantara/uikit-react-core";
12
+
13
+ import { KPIs } from "./KPIs";
14
+ import { Properties } from "./Properties";
15
+ import { Table } from "./Table";
16
+
17
+ const MODEL_ID = "123";
18
+
19
+ interface SectionProps extends HvGridProps {
20
+ variant?: HvGlobalActionsProps["variant"];
21
+ actions?: ReactNode;
22
+ component?: ElementType;
23
+ }
24
+
25
+ const Section = ({
26
+ title,
27
+ variant = "section",
28
+ component = "section",
29
+ actions,
30
+ ...others
31
+ }: SectionProps) => (
32
+ <>
33
+ {title && (
34
+ <HvGlobalActions variant={variant} title={title}>
35
+ {actions}
36
+ </HvGlobalActions>
37
+ )}
38
+ <Suspense fallback={<HvLoading className={css({ minHeight: 200 })} />}>
39
+ <HvGrid
40
+ container
41
+ component={component}
42
+ className={css({
43
+ marginTop: 0, // avoid collapse
44
+ marginBottom: theme.space.md,
45
+ })}
46
+ {...others}
47
+ />
48
+ </Suspense>
49
+ </>
50
+ );
51
+
52
+ const DetailsView = () => {
53
+ const [editing, setEditing] = useState(false);
54
+
55
+ const actions = editing ? (
56
+ <span>
57
+ <HvButton type="submit" form="properties" variant="primaryGhost">
58
+ Save
59
+ </HvButton>
60
+ <HvButton variant="secondaryGhost" onClick={() => setEditing(false)}>
61
+ Cancel
62
+ </HvButton>
63
+ </span>
64
+ ) : (
65
+ <HvButton variant="primaryGhost" onClick={() => setEditing(true)}>
66
+ Edit
67
+ </HvButton>
68
+ );
69
+
70
+ return (
71
+ <>
72
+ <Section title="Asset Details" variant="global">
73
+ <KPIs />
74
+ </Section>
75
+
76
+ <Section
77
+ id="properties"
78
+ title="Asset Properties"
79
+ actions={actions}
80
+ component={editing ? "form" : "section"}
81
+ onSubmit={(evt) => {
82
+ evt.preventDefault();
83
+ const formData = new FormData(evt.currentTarget as any);
84
+ console.log(Object.fromEntries(formData.entries()));
85
+ setEditing(false);
86
+ }}
87
+ >
88
+ <Properties editMode={editing} />
89
+ </Section>
90
+
91
+ <Section title="Events">
92
+ <HvGrid item xs={12}>
93
+ <Table modelId={MODEL_ID} />
94
+ </HvGrid>
95
+ </Section>
96
+ </>
97
+ );
98
+ };
99
+
100
+ export { DetailsView as Component };
101
+
102
+ export default DetailsView;
@@ -0,0 +1,155 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import useSWR from "swr";
3
+
4
+ // --- Types ---
5
+
6
+ type DataObject = Record<string, string | number | undefined>;
7
+
8
+ export interface ServerPaginationProps<T extends DataObject = DataObject> {
9
+ endpoint: string;
10
+ db: T[];
11
+ limit: number;
12
+ skip: number;
13
+ sort?: string;
14
+ order?: "desc" | "asc";
15
+ filter?: { id: string; value: string | number };
16
+ search?: string;
17
+ }
18
+
19
+ export interface PaginationData<T extends DataObject = DataObject> {
20
+ data: T[];
21
+ total: number;
22
+ pages: number;
23
+ }
24
+
25
+ // --- Utils ---
26
+
27
+ export const delay = (ms = 500) =>
28
+ new Promise((resolve) => {
29
+ setTimeout(() => resolve("Time passed"), ms);
30
+ });
31
+
32
+ const searchObj = (entry: DataObject, search: string) => {
33
+ return Object.keys(entry)
34
+ .filter((key) => key !== "id" && key !== "statusColor")
35
+ .find(
36
+ (key) =>
37
+ entry[key]?.toString().toLowerCase().search(search.toLowerCase()) !==
38
+ -1,
39
+ )?.length;
40
+ };
41
+
42
+ // --- Pagination hook ---
43
+
44
+ export function useServerPagination<T extends DataObject = DataObject>({
45
+ endpoint,
46
+ db,
47
+ limit,
48
+ skip,
49
+ sort,
50
+ order = "desc",
51
+ filter,
52
+ search,
53
+ }: ServerPaginationProps<T>) {
54
+ const initialState: PaginationData<T> = {
55
+ data: [],
56
+ pages: 0,
57
+ total: 0,
58
+ };
59
+ const [currData, setCurrData] = useState(initialState);
60
+ const [deleting, setDeleting] = useState(false);
61
+ const [allData, setAllData] = useState<T[]>(db);
62
+
63
+ const fetcher = useCallback(async () => {
64
+ // Loading
65
+ await delay(800);
66
+
67
+ const items =
68
+ search || filter
69
+ ? allData.filter((entry) => {
70
+ let keep = true;
71
+
72
+ if (search) {
73
+ keep = !!searchObj(entry, search);
74
+ }
75
+ if (filter && keep) {
76
+ keep =
77
+ entry[filter.id]?.toString().toLowerCase() ===
78
+ filter.value.toString().toLowerCase();
79
+ }
80
+
81
+ return keep;
82
+ })
83
+ : [...allData];
84
+
85
+ return {
86
+ data: items
87
+ .sort(
88
+ sort
89
+ ? (a, b) => {
90
+ return order === "desc"
91
+ ? (b[sort] || "")
92
+ .toString()
93
+ .localeCompare((a[sort] || "").toString(), undefined, {
94
+ numeric: true,
95
+ sensitivity: "base",
96
+ })
97
+ : (a[sort] || "")
98
+ .toString()
99
+ .localeCompare((b[sort] || "").toString(), undefined, {
100
+ numeric: true,
101
+ sensitivity: "base",
102
+ });
103
+ }
104
+ : undefined,
105
+ )
106
+ .slice(skip, skip + limit),
107
+ total: items.length,
108
+ pages: Math.ceil(items.length / limit),
109
+ };
110
+ }, [allData, filter, limit, order, search, skip, sort]);
111
+
112
+ const { data, isLoading, mutate } = useSWR<PaginationData<T>>(() => {
113
+ const params = new URLSearchParams({
114
+ skip: skip.toString(),
115
+ limit: limit.toString(),
116
+ ...(sort && { sort, order }),
117
+ ...(search && { search }),
118
+ ...(filter && { [filter.id]: filter.value }),
119
+ });
120
+
121
+ return `${endpoint}?${params.toString()}`;
122
+ }, fetcher);
123
+
124
+ // Prevents data from updating to undefined when loading (this crashes useHvData)
125
+ useEffect(() => {
126
+ if (!isLoading && data) {
127
+ setCurrData(data);
128
+ }
129
+ }, [isLoading, data]);
130
+
131
+ const deleteEntries = useCallback(
132
+ async (ids: string[]) => {
133
+ setDeleting(true);
134
+
135
+ setAllData((prev) =>
136
+ prev.filter((entry: any) => entry.id && !ids.includes(entry.id)),
137
+ );
138
+
139
+ const refreshedData = await fetcher();
140
+
141
+ mutate(refreshedData);
142
+
143
+ await delay(800);
144
+
145
+ setDeleting(false);
146
+ },
147
+ [fetcher, mutate],
148
+ );
149
+
150
+ return {
151
+ deleteEntries,
152
+ data: currData,
153
+ loading: isLoading || deleting,
154
+ };
155
+ }
@@ -0,0 +1,51 @@
1
+ import { HvTableColumnConfig } from "@hitachivantara/uikit-react-core";
2
+
3
+ // --- Table data utils ---
4
+
5
+ export type DetailsViewEntry = {
6
+ id: string;
7
+ name: string;
8
+ eventType: string;
9
+ status: string;
10
+ severity: string;
11
+ priority: string;
12
+ time: string;
13
+ temperature: string;
14
+ };
15
+
16
+ export const getColumns = (): HvTableColumnConfig<
17
+ DetailsViewEntry,
18
+ string
19
+ >[] => [
20
+ { Header: "Title", accessor: "name", style: { minWidth: 120 } },
21
+ { Header: "Event Type", accessor: "eventType", style: { minWidth: 100 } },
22
+ { Header: "Status", accessor: "status", style: { width: 120 } },
23
+ { Header: "Severity", accessor: "severity" },
24
+ { Header: "Priority", accessor: "priority" },
25
+ { Header: "Time", accessor: "time" },
26
+ { Header: "Temperature", accessor: "temperature" },
27
+ ];
28
+
29
+ const getOption = (opts: string[], i: number) => opts[i % opts.length];
30
+
31
+ const getTime = (priority: string, index: number) => {
32
+ let i = priority === "High" ? index + 4 : index + 3;
33
+ i = priority === "Medium" ? i + 30 : index + 20;
34
+ return `${i % 12}:${i % 60}:${i % 60}`;
35
+ };
36
+
37
+ const getPriority = (i: number) =>
38
+ (i % 2 > 0 && "High") || (i % 2 < 0 && "Medium") || "Low";
39
+
40
+ export const createEntry = (i: number): DetailsViewEntry => {
41
+ return {
42
+ id: `${i + 1}`,
43
+ name: `Event ${i + 1}`,
44
+ eventType: `Anomaly detection ${i % 4}`,
45
+ status: getOption(["Closed", "Open"], i),
46
+ severity: getOption(["Critical", "Major", "Average", "Minor"], i),
47
+ priority: getPriority(i),
48
+ time: getTime(getPriority(i), i),
49
+ temperature: `${i + 35}`,
50
+ };
51
+ };
@@ -0,0 +1,107 @@
1
+ import {
2
+ HvButton,
3
+ HvCheckBox,
4
+ HvDatePicker,
5
+ HvGrid,
6
+ HvInput,
7
+ HvOption,
8
+ HvRadio,
9
+ HvRadioGroup,
10
+ HvSelect,
11
+ HvTextArea,
12
+ HvTimePicker,
13
+ } from "@hitachivantara/uikit-react-core";
14
+ import { Phone } from "@hitachivantara/uikit-react-icons";
15
+
16
+ const countries = [
17
+ { id: "pt", label: "Portugal" },
18
+ { id: "sp", label: "Spain" },
19
+ { id: "fr", label: "France" },
20
+ { id: "de", label: "Germany" },
21
+ { id: "us", label: "United States" },
22
+ ];
23
+
24
+ const Form = () => (
25
+ <form
26
+ autoComplete="on"
27
+ onSubmit={(event) => {
28
+ event.preventDefault();
29
+ const formData = new FormData(event.currentTarget);
30
+ alert(JSON.stringify(Object.fromEntries(formData), null, 2));
31
+ }}
32
+ >
33
+ <HvGrid container maxWidth="md" rowSpacing="xs">
34
+ <HvGrid item xs={12} sm={6}>
35
+ <HvInput required name="name" label="Full Name" />
36
+ </HvGrid>
37
+ <HvGrid item xs={12} sm={6}>
38
+ <HvInput required type="email" name="email" label="Email" />
39
+ </HvGrid>
40
+ <HvGrid item xs={12} sm={6}>
41
+ <HvInput name="street-address" label="Address" />
42
+ </HvGrid>
43
+ <HvGrid item xs={12} sm={6}>
44
+ <HvSelect
45
+ required
46
+ name="country"
47
+ label="Country"
48
+ statusMessage="Country is required"
49
+ >
50
+ {countries.map(({ id, label }) => (
51
+ <HvOption key={id} value={id}>
52
+ {label}
53
+ </HvOption>
54
+ ))}
55
+ </HvSelect>
56
+ </HvGrid>
57
+ <HvGrid item xs={12} sm={6}>
58
+ <HvInput name="tel" label="Phone number" endAdornment={<Phone />} />
59
+ </HvGrid>
60
+ <HvGrid item xs={12} sm={6}>
61
+ <HvInput type="password" name="password" label="Password" />
62
+ </HvGrid>
63
+ <HvGrid item xs={12} component="hr" />
64
+ <HvGrid item xs={12} sm={6}>
65
+ <HvDatePicker name="bday" label="Birthday" placeholder="Select date" />
66
+ </HvGrid>
67
+ <HvGrid item xs={12} sm={6}>
68
+ <HvTimePicker name="startTime" label="Start time" />
69
+ </HvGrid>
70
+ <HvGrid item xs={12} component="hr" />
71
+ <HvGrid item xs={12}>
72
+ <HvTextArea
73
+ required
74
+ name="description"
75
+ description="Write a short description"
76
+ label="Description"
77
+ rows={3}
78
+ inputProps={{ minLength: 16, maxLength: 128 }}
79
+ minCharQuantity={16}
80
+ maxCharQuantity={128}
81
+ />
82
+ </HvGrid>
83
+ <HvGrid item xs={12}>
84
+ <HvRadioGroup required name="sex" label="Sex" orientation="horizontal">
85
+ <HvRadio value="male" label="Male" />
86
+ <HvRadio value="female" label="Female" />
87
+ <HvRadio value="other" label="Other" />
88
+ </HvRadioGroup>
89
+ </HvGrid>
90
+ <HvGrid item xs={12}>
91
+ <HvCheckBox
92
+ defaultChecked
93
+ name="subscribe"
94
+ label="Subscribe to newsletter"
95
+ value="yes"
96
+ />
97
+ </HvGrid>
98
+ <HvGrid item xs={12}>
99
+ <HvButton type="submit">Submit</HvButton>
100
+ </HvGrid>
101
+ </HvGrid>
102
+ </form>
103
+ );
104
+
105
+ export { Form as Component };
106
+
107
+ export default Form;
@@ -0,0 +1,89 @@
1
+ import { useMemo } from "react";
2
+ import { SortableContext, useSortable } from "@dnd-kit/sortable";
3
+ import { CSS } from "@dnd-kit/utilities";
4
+ import {
5
+ HvIconButton,
6
+ HvStack,
7
+ HvTypography,
8
+ } from "@hitachivantara/uikit-react-core";
9
+ import { Add } from "@hitachivantara/uikit-react-icons";
10
+
11
+ import classes from "./styles";
12
+ import { TaskCard } from "./TaskCard";
13
+ import { Column, Task } from "./types";
14
+
15
+ interface ColumnProps {
16
+ column: Column;
17
+ tasks?: Task[];
18
+ addTask?: (columnId: string) => void;
19
+ deleteTask?: (taskId: string) => void;
20
+ addEnabled?: boolean;
21
+ }
22
+
23
+ export const ColumnContainer = ({
24
+ column,
25
+ tasks,
26
+ addTask,
27
+ deleteTask,
28
+ addEnabled = true,
29
+ }: ColumnProps) => {
30
+ const tasksIds = useMemo(() => tasks?.map((task) => task.id), [tasks]) || [];
31
+
32
+ const {
33
+ setNodeRef,
34
+ attributes,
35
+ listeners,
36
+ transform,
37
+ transition,
38
+ isDragging,
39
+ } = useSortable({ id: column.id, data: { type: "Column", column } });
40
+
41
+ const style = {
42
+ transition,
43
+ transform: CSS.Transform.toString(transform),
44
+ };
45
+
46
+ return (
47
+ <div
48
+ ref={setNodeRef}
49
+ style={{
50
+ ...style,
51
+ ...(isDragging && {
52
+ opacity: 0.2,
53
+ }),
54
+ }}
55
+ className={classes.columnContainer}
56
+ >
57
+ <div className={classes.column}>
58
+ <div className={classes.columnHeader} {...attributes} {...listeners}>
59
+ <div className={classes.columnTitle}>
60
+ {column.icon}
61
+ <HvTypography variant="title3">{column.title}</HvTypography>
62
+ <div className={classes.count}>{tasks?.length}</div>
63
+ </div>
64
+ <HvIconButton
65
+ title="Add"
66
+ variant="primarySubtle"
67
+ onClick={() => addTask?.(column.id)}
68
+ disabled={!addEnabled}
69
+ >
70
+ <Add />
71
+ </HvIconButton>
72
+ </div>
73
+ <HvStack direction="column" spacing="md">
74
+ <SortableContext items={tasksIds}>
75
+ {tasks &&
76
+ tasks.map((task) => (
77
+ <TaskCard
78
+ key={task.id}
79
+ task={task}
80
+ color={column.color}
81
+ deleteTask={deleteTask}
82
+ />
83
+ ))}
84
+ </SortableContext>
85
+ </HvStack>
86
+ </div>
87
+ </div>
88
+ );
89
+ };
@@ -0,0 +1,130 @@
1
+ import { useSortable } from "@dnd-kit/sortable";
2
+ import { CSS } from "@dnd-kit/utilities";
3
+ import {
4
+ HvActionBar,
5
+ HvAvatar,
6
+ HvAvatarGroup,
7
+ HvButton,
8
+ HvCard,
9
+ HvCardContent,
10
+ HvCardHeader,
11
+ HvOverflowTooltip,
12
+ HvTypography,
13
+ } from "@hitachivantara/uikit-react-core";
14
+ import {
15
+ Delete,
16
+ Level0Good,
17
+ Level1,
18
+ Level3Bad,
19
+ Level4,
20
+ Level5,
21
+ } from "@hitachivantara/uikit-react-icons";
22
+
23
+ import classes from "./styles";
24
+ import { Column, Task } from "./types";
25
+
26
+ interface TaskProps {
27
+ task: Task;
28
+ color?: Column["color"];
29
+ deleteTask?: (taskId: string) => void;
30
+ }
31
+
32
+ const getStatusIcon = (statusLevel?: number) => {
33
+ switch (statusLevel) {
34
+ case 1:
35
+ return <Level1 color="info" />;
36
+ case 2:
37
+ return <Level3Bad color="warningStrong" />;
38
+ case 3:
39
+ return <Level4 color="negativeStrong" />;
40
+ case 4:
41
+ return <Level5 color="negativeDeep" />;
42
+ case 5:
43
+ return <Level0Good color="positive" />;
44
+ default:
45
+ return null;
46
+ }
47
+ };
48
+
49
+ export const TaskCard = ({ task, deleteTask }: TaskProps) => {
50
+ const {
51
+ setNodeRef,
52
+ attributes,
53
+ listeners,
54
+ transform,
55
+ transition,
56
+ isDragging,
57
+ } = useSortable({ id: task.id, data: { type: "Task", task } });
58
+
59
+ const style = {
60
+ transition,
61
+ transform: CSS.Transform.toString(transform),
62
+ };
63
+
64
+ return (
65
+ <div
66
+ ref={setNodeRef}
67
+ style={{
68
+ ...style,
69
+ ...(isDragging && {
70
+ opacity: 0.2,
71
+ }),
72
+ }}
73
+ {...attributes}
74
+ {...listeners}
75
+ >
76
+ <HvCard
77
+ bgcolor="bgContainer"
78
+ classes={{ root: classes.card, semanticBar: classes.cardSemanticBar }}
79
+ >
80
+ <HvCardHeader
81
+ title={
82
+ <>
83
+ <HvTypography variant="caption2">Some time ago</HvTypography>
84
+ <HvTypography variant="title3">{task.title}</HvTypography>
85
+ </>
86
+ }
87
+ className={classes.cardHeader}
88
+ icon={getStatusIcon(task.statusLevel)}
89
+ />
90
+ <HvCardContent>
91
+ <HvOverflowTooltip
92
+ paragraphOverflow
93
+ data={
94
+ <HvTypography>
95
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
96
+ vitae ex sem. Ut posuere nibh risus, vel pellentesque elit
97
+ varius sed. Etiam eleifend consequat tortor, ac ultrices nunc
98
+ viverra pretium. Praesent rutrum congue justo fermentum varius.
99
+ Aenean malesuada magna ut purus tincidunt tempor. Quisque urna
100
+ erat, finibus quis mi vitae, vehicula gravida dui. In vitae
101
+ elementum felis, sed interdum nunc. Integer varius nibh ac
102
+ congue rutrum. Pellentesque vel auctor risus. Cras quis
103
+ sollicitudin odio.
104
+ </HvTypography>
105
+ }
106
+ />
107
+ <HvButton
108
+ icon
109
+ onClick={() => deleteTask?.(task.id)}
110
+ classes={{ root: classes.cardDeleteIcon }}
111
+ >
112
+ <Delete />
113
+ </HvButton>
114
+ </HvCardContent>
115
+ <HvActionBar classes={{ root: classes.cardActions }}>
116
+ {task.users ? (
117
+ <HvAvatarGroup size="xs" maxVisible={2}>
118
+ {task.users.map((user) => (
119
+ <HvAvatar key={user.name} alt={user.name} src={user.avatar} />
120
+ ))}
121
+ </HvAvatarGroup>
122
+ ) : (
123
+ <HvButton variant="primaryGhost">Assign</HvButton>
124
+ )}
125
+ <div style={{ flex: 1 }} />
126
+ </HvActionBar>
127
+ </HvCard>
128
+ </div>
129
+ );
130
+ };