Skip to content

Commit 30daeaa

Browse files
author
Hrithik-Gavankar
committed
Merge branch 'main' into NGUI-176-video-component
2 parents 16ec75e + e0b5d0f commit 30daeaa

File tree

9 files changed

+712
-276
lines changed

9 files changed

+712
-276
lines changed

README.md

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ This npm package provides a collection of reusable Patternfly React components t
99

1010
## Provides:
1111

12-
* Patternfly React Components
12+
- Patternfly React Components
1313
- OneCardWrapper
14+
- ImageComponent
1415
- TableWrapper
1516
- VideoPlayerWrapper
1617
* Dynamic Component Renderer
1718
- DynamicComponents
1819
* Supported Components
19-
- `one-card`, `table`, `video-player`
20+
- `one-card`, `image`, `table`, `video-player`
2021
- `video-player` supports YouTube video URLs and direct video file URLs
2122

2223
## Installation
2324

2425
**Pre-requisites:**
26+
2527
- React 18+
2628
- TypeScript
2729

@@ -34,27 +36,28 @@ npm install @rhngui/patternfly-react-renderer
3436
### OneCard Component
3537

3638
```jsx
37-
import { OneCardWrapper } from '@rhngui/patternfly-react-renderer';
39+
import { OneCardWrapper } from "@rhngui/patternfly-react-renderer";
3840

3941
const mockData = {
4042
title: "Movie Details",
41-
image: "https://image.tmdb.org/t/p/w440_and_h660_face/uXDfjJbdP4ijW5hWSBrPrlKpxab.jpg",
43+
image:
44+
"https://image.tmdb.org/t/p/w440_and_h660_face/uXDfjJbdP4ijW5hWSBrPrlKpxab.jpg",
4245
fields: [
4346
{
4447
name: "Title",
4548
data_path: "movie.title",
46-
data: ["Toy Story"]
49+
data: ["Toy Story"],
4750
},
4851
{
4952
name: "Year",
5053
data_path: "movie.year",
51-
data: [1995]
54+
data: [1995],
5255
},
5356
{
5457
name: "Genres",
5558
data_path: "movie.genres",
56-
data: ["Animation", "Adventure"]
57-
}
59+
data: ["Animation", "Adventure"],
60+
},
5861
],
5962
imageSize: "md",
6063
id: "movie-card",
@@ -65,6 +68,66 @@ function App() {
6568
}
6669
```
6770

71+
### Image Component
72+
73+
```jsx
74+
import { DynamicComponent } from "@rhngui/patternfly-react-renderer";
75+
76+
const imageConfig = {
77+
component: "image",
78+
title: "Movie Poster",
79+
image:
80+
"https://image.tmdb.org/t/p/w440_and_h660_face/uXDfjJbdP4ijW5hWSBrPrlKpxab.jpg",
81+
id: "movie-poster-image",
82+
};
83+
84+
function App() {
85+
return <DynamicComponent config={imageConfig} />;
86+
}
87+
```
88+
89+
### Table Component
90+
91+
```jsx
92+
import { DynamicComponent } from "@rhngui/patternfly-react-renderer";
93+
94+
const tableConfig = {
95+
component: "table",
96+
title: "Movie Statistics",
97+
id: "movie-stats-table",
98+
fields: [
99+
{
100+
name: "Movie Title",
101+
data_path: "movies.title",
102+
data: ["Toy Story", "Finding Nemo", "The Incredibles"],
103+
},
104+
{
105+
name: "Release Year",
106+
data_path: "movies.year",
107+
data: [1995, 2003, 2004],
108+
},
109+
{
110+
name: "Genres",
111+
data_path: "movies.genres",
112+
data: [
113+
["Animation", "Adventure"],
114+
["Animation", "Adventure"],
115+
["Animation", "Action"],
116+
],
117+
},
118+
{
119+
name: "Rating",
120+
data_path: "movies.rating",
121+
data: [8.3, 8.1, 8.0],
122+
},
123+
],
124+
};
125+
126+
function App() {
127+
return <DynamicComponent config={tableConfig} />;
128+
}
129+
```
130+
68131
### VideoPlayer Component
69132

70133
```jsx
@@ -82,13 +145,8 @@ function App() {
82145
}
83146
```
84147

85-
**Supported Video Formats:**
86-
- YouTube URLs (e.g., `https://www.youtube.com/embed/VIDEO_ID`)
87-
- Direct video file URLs (e.g., `https://example.com/video.mp4`)
88-
- Automatic thumbnail generation for YouTube videos
89-
- Poster image fallback support
90-
91148
## Links
92-
* [Documentation](https://redhat-ux.github.io/next-gen-ui-agent/guide/renderer/patternfly_npm/)
93-
* [Source Code](https://github.com/RedHat-UX/next-gen-ui-agent/tree/main/libs_js/next_gen_ui_react)
94-
* [Contributing](https://redhat-ux.github.io/next-gen-ui-agent/development/contributing/)
149+
150+
- [Documentation](https://redhat-ux.github.io/next-gen-ui-agent/guide/renderer/patternfly_npm/)
151+
- [Source Code](https://github.com/RedHat-UX/next-gen-ui-agent/tree/main/libs_js/next_gen_ui_react)
152+
- [Contributing](https://redhat-ux.github.io/next-gen-ui-agent/development/contributing/)

src/components/DynamicComponents.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import "@patternfly/react-core/dist/styles/base.css";
22
import "@patternfly/chatbot/dist/css/main.css";
3+
import "../global.css";
4+
35
import isArray from "lodash/isArray";
46
import isEmpty from "lodash/isEmpty";
57
import map from "lodash/map";

src/components/ImageComponent.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core";
2+
import React, { useState } from "react";
3+
4+
interface ImageComponentProps {
5+
component: "image";
6+
id: string;
7+
image?: string | null;
8+
title: string;
9+
className?: string;
10+
}
11+
12+
const ImageComponent: React.FC<ImageComponentProps> = ({
13+
id,
14+
image,
15+
title,
16+
className,
17+
}) => {
18+
const [imageError, setImageError] = useState(false);
19+
20+
return (
21+
<Card id={id} className={className}>
22+
<CardHeader>
23+
<CardTitle>{title}</CardTitle>
24+
</CardHeader>
25+
<CardBody>
26+
{image && !imageError ? (
27+
<img
28+
src={image}
29+
alt={title}
30+
className="image-component-img"
31+
onError={() => setImageError(true)}
32+
/>
33+
) : (
34+
<div className="image-component-placeholder">
35+
{imageError ? "Image failed to load" : "No image provided"}
36+
</div>
37+
)}
38+
</CardBody>
39+
</Card>
40+
);
41+
};
42+
43+
export default ImageComponent;

src/components/TableWrapper.tsx

Lines changed: 66 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
Chart,
3-
ChartBar,
4-
ChartAxis,
5-
ChartThemeColor,
6-
} from "@patternfly/react-charts/victory";
1+
import { Card, CardBody } from "@patternfly/react-core";
72
import {
83
Table,
94
Thead,
@@ -13,118 +8,83 @@ import {
138
Td,
149
Caption,
1510
} from "@patternfly/react-table";
16-
import { useState } from "react";
1711

18-
const TableWrapper = ({
19-
columns,
20-
rows,
21-
caption,
22-
variant,
23-
graph,
24-
selectable = false,
25-
onRowSelect,
26-
actions,
27-
setCustomData,
28-
}) => {
29-
const [selectedRows, setSelectedRows] = useState([]);
12+
interface FieldData {
13+
name: string;
14+
data_path: string;
15+
data: (string | number | boolean | null | (string | number)[])[];
16+
}
3017

31-
const toggleRow = (index) => {
32-
const newSelected = selectedRows.includes(index)
33-
? selectedRows.filter((i) => i !== index)
34-
: [...selectedRows, index];
35-
setSelectedRows(newSelected);
18+
interface TableWrapperProps {
19+
component: "table";
20+
title: string;
21+
id: string;
22+
fields: FieldData[];
23+
className?: string;
24+
}
3625

37-
onRowSelect?.(newSelected.map((i) => rows[i]));
38-
setCustomData(newSelected.map((i) => rows[i]));
39-
};
26+
const TableWrapper = (props: TableWrapperProps) => {
27+
const { title, id, fields, className } = props;
28+
// Transform fields data into table format
29+
const transformFieldsToTableData = () => {
30+
if (!fields || fields.length === 0) return { columns: [], rows: [] };
31+
32+
// Find the maximum number of data items across all fields
33+
const maxDataLength = Math.max(...fields.map((field) => field.data.length));
34+
35+
// Create columns from field names
36+
const transformedColumns = fields.map((field) => ({
37+
key: field.name,
38+
label: field.name,
39+
}));
4040

41-
const toggleAllRows = () => {
42-
const allSelected = selectedRows.length === rows.length;
43-
const newSelected = allSelected ? [] : rows.map((_, i) => i);
44-
setSelectedRows(newSelected);
41+
// Create rows based on the maximum data length
42+
const transformedRows = [];
43+
for (let i = 0; i < maxDataLength; i++) {
44+
const row: Record<string, string | number | null> = {};
45+
fields.forEach((field) => {
46+
const value = field.data[i];
47+
if (value === null || value === undefined) {
48+
row[field.name] = "";
49+
} else if (Array.isArray(value)) {
50+
row[field.name] = value.join(", ");
51+
} else {
52+
row[field.name] = String(value);
53+
}
54+
});
55+
transformedRows.push(row);
56+
}
4557

46-
onRowSelect?.(newSelected.map((i) => rows[i]));
47-
setCustomData(newSelected.map((i) => rows[i]));
58+
return { columns: transformedColumns, rows: transformedRows };
4859
};
4960

50-
const graphData =
51-
graph && graph.column
52-
? rows.map((row) => ({ x: row[columns[0].key], y: row[graph.column] }))
53-
: [];
61+
const { columns, rows } = transformFieldsToTableData();
5462

5563
return (
56-
<div>
57-
<Table variant={variant} borders={variant !== "compactBorderless"}>
58-
{caption && (
59-
<Caption>
60-
<div
61-
style={{
62-
display: "flex",
63-
justifyContent: "space-between",
64-
alignItems: "center",
65-
}}
66-
>
67-
<span>{caption}</span>
68-
{actions}
69-
</div>
70-
</Caption>
71-
)}
72-
<Thead>
73-
<Tr>
74-
{selectable && (
75-
<Th>
76-
<input
77-
type="checkbox"
78-
aria-label="Select all rows"
79-
checked={selectedRows.length === rows.length}
80-
onChange={toggleAllRows}
81-
/>
82-
</Th>
83-
)}
84-
{columns.map((col, index) => (
85-
<Th key={index}>{col.label}</Th>
86-
))}
87-
</Tr>
88-
</Thead>
89-
<Tbody>
90-
{rows.map((row, rowIndex) => (
91-
<Tr key={rowIndex} data-testid={`row-${row.id ?? rowIndex}`}>
92-
{selectable && (
93-
<Td>
94-
<input
95-
type="checkbox"
96-
aria-label={`Select row ${rowIndex}`}
97-
checked={selectedRows.includes(rowIndex)}
98-
onChange={() => toggleRow(rowIndex)}
99-
/>
100-
</Td>
101-
)}
102-
{columns.map((col, colIndex) => (
103-
<Td key={colIndex}>{row[col.key]}</Td>
64+
<Card id={id} className={className}>
65+
<CardBody>
66+
<Table variant="compact" borders>
67+
<Caption>{title}</Caption>
68+
<Thead>
69+
<Tr>
70+
{columns.map((col, index) => (
71+
<Th key={index}>{col.label}</Th>
10472
))}
10573
</Tr>
106-
))}
107-
</Tbody>
108-
</Table>
109-
110-
{graph && (
111-
<div style={{ height: "300px" }}>
112-
<Chart
113-
ariaTitle={graph.title || "Chart"}
114-
domainPadding={{ x: [30, 25] }}
115-
height={300}
116-
width={600}
117-
themeColor={ChartThemeColor.multiUnordered}
118-
>
119-
<ChartAxis />
120-
<ChartAxis dependentAxis />
121-
<ChartBar data={graphData} barWidth={30} />
122-
</Chart>
123-
</div>
124-
)}
125-
</div>
74+
</Thead>
75+
<Tbody>
76+
{rows.map((row, rowIndex) => (
77+
<Tr key={rowIndex} data-testid={`row-${rowIndex}`}>
78+
{columns.map((col, colIndex) => (
79+
<Td key={colIndex}>{row[col.key]}</Td>
80+
))}
81+
</Tr>
82+
))}
83+
</Tbody>
84+
</Table>
85+
</CardBody>
86+
</Card>
12687
);
12788
};
12889

12990
export default TableWrapper;
130-

0 commit comments

Comments
 (0)