diff --git a/package.json b/package.json index 6ad470b..17982d2 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.4.3", "@wojtekmaj/react-hooks": "^1.17.2", - "ag-grid-community": "^25.1.0", - "ag-grid-react": "^25.1.0", + "ag-grid-community": "30.0.6", + "ag-grid-react": "30.0.6", "axios": "^1.2.1", "branch-sdk": "^2.59.0", "clsx": "^1.1.1", @@ -166,7 +166,7 @@ "main": "index.js", "repository": { "type": "git", - "url": "git+ssh://git@bitbucket.org/zennya/web-component-library.git" + "url": "git+ssh://git@bitbucket.org/zennya/web-component-library.git?sha=f4c76a6023c8f317fb0c329937c88c9981a5e09b" }, "author": "", "license": "ISC", diff --git a/src/components/DataGridv3/CustomHeader.jsx b/src/components/DataGridv3/CustomHeader.jsx new file mode 100644 index 0000000..276fb31 --- /dev/null +++ b/src/components/DataGridv3/CustomHeader.jsx @@ -0,0 +1,233 @@ +import React, {useState, useEffect, useRef, useCallback, useMemo, useContext, memo} from 'react' +import { AgGridColumn, AgGridReact } from 'ag-grid-react'; +import {GridContext, actions} from "./GridContext"; +import { render } from 'react-dom'; +import { debounce } from 'lodash'; +import { makeStyles } from 'tss-react/mui'; +import {Autocomplete, Box, Button, Checkbox, Chip, Divider, FormControl, Grid, IconButton, InputLabel, InputAdornment, ListItemText , MenuItem, MenuList, OutlinedInput, Popover, Select, TextField, Typography, useTheme} from '@mui/material' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSort , faSortUp, faSortDown, faPlus, faBars, faFilter, faThumbtack, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; + +const CustomHeader= memo((props)=>{ + const [state,dispatch] = useContext(GridContext) + const [anchorEl, setAnchorEl] = useState(null) + const [isHovered, setIsHovered] = useState(false) + const [currentSort, setCurrentSort] = useState(false) + const [isColHidden, setIsColHidden] = useState(false) + + const handlePopoverClick = (event) => { + setAnchorEl(event.currentTarget); + } + + const handlePopoverClose = () => { + setAnchorEl(null); + } + // Pinning + const handlePinRight = () => { + props.columnApi.setColumnPinned(props.column.colId,'right') + handlePopoverClose() + } + const handlePinLeft = () => { + props.columnApi.setColumnPinned(props.column.colId,'left') + handlePopoverClose() + } + const handleUnpin = () => { + props.columnApi.setColumnPinned(props.column.colId, false) + handlePopoverClose() + } + const handleHide = () => { + props.columnApi.setColumnVisible(props.column.colId,false) + } + const handleUnhide = () => { + props.columnApi.getColumns().map((element)=>{ + props.columnApi.setColumnVisible(element.colId,true) + }) + } + + function onSortClicked(event) { + // alternative client side sort + props.progressSort(event.shiftKey); + }; + + const checkColHidden= ()=>{ + props.columnApi.getColumns().map((element)=>{ + if(!element.visible){ + return setIsColHidden(true) + } + }) + return setIsColHidden(false) + } + + useEffect(() => { + if(state?.sortDirection != null){ + if(state?.sortColumn && state?.sortColumn === props?.column?.colId){ + return setCurrentSort(true) + }else{ + return setCurrentSort(false) + } + }else{ + return setCurrentSort(false) + } + + }, [state?.sortColumn]) + + const setHoveredTrue =()=>{ + setIsHovered(true) + } + const setHoveredFalse= ()=>{ + setIsHovered(false) + } + + const handleSort=(props)=>{ + if( state?.sortColumn !== null && props?.column?.colId == state?.sortColumn){ + if(state?.sortDirection === 'asc'){ + dispatch({ + type: actions.SORT_DIRECTION, + payload:{ + sortDirection: 'desc' + } + }) + + }else if(state?.sortDirection === 'desc'){ + // setSortDirection(null) + dispatch({ + type: actions.SORT_DIRECTION, + payload:{ + sortDirection: null + } + }) + dispatch({ + type:actions.SORT_COLUMN, + payload:{ + sortColumn: null + } + }) + }else{ + // setSortDirection('asc') + dispatch({ + type: actions.SORT_DIRECTION, + payload:{ + sortDirection: 'asc' + } + }) + } + }else{//set to asc + dispatch({ + type:actions.SORT_COLUMN, + payload:{ + sortColumn: props?.column?.colId + } + }) + dispatch({ + type: actions.SORT_DIRECTION, + payload:{ + sortDirection: 'asc' + } + }) + } + } + + return ( +
+ + + + + + { (props.column.pinned === null || props.column.pinned === 'left') && ( +
+ +
) + } + { (props.column.pinned === null || props.column.pinned === 'right') && ( +
+ +
) + } + { (props.column.pinned === 'right' || props.column.pinned === 'left') && ( +
+ +
) + } + + +
+ +
+ + +
+ +
+ +
+
+ { + //this is a sample of a client-side sorting + // onSortClicked(event) + // }} + + onClick={(event)=> {if(props?.enableSorting) handleSort(props)}} + onTouchStart ={(event)=>{if(props?.enableSorting) handleSort(props)}} + onMouseEnter={setHoveredTrue} + onMouseLeave={setHoveredFalse} + > + {props?.displayName} + + { props?.enableSorting && (isHovered || currentSort) && ( + (!currentSort || state?.sortDirection === null )? + <> + + + + : state?.sortDirection === 'desc' ? + <> + + + : + <> + + + )} + + +
+ ) +}) + +export default CustomHeader \ No newline at end of file diff --git a/src/components/DataGridv3/DataGrid.jsx b/src/components/DataGridv3/DataGrid.jsx new file mode 100644 index 0000000..023eeea --- /dev/null +++ b/src/components/DataGridv3/DataGrid.jsx @@ -0,0 +1,109 @@ +import React, {useState, useEffect, useRef, useCallback, useMemo, useContext, memo} from 'react' +import { AgGridColumn, AgGridReact } from 'ag-grid-react'; +import {GridContext, actions} from "./GridContext"; +import CustomHeader from "./CustomHeader" +import { render } from 'react-dom'; +import { debounce } from 'lodash'; +import { makeStyles } from 'tss-react/mui'; +import {Autocomplete, Box, Button, Checkbox, Chip, Divider, FormControl, Grid, IconButton, InputLabel, InputAdornment, ListItemText , MenuItem, MenuList, OutlinedInput, Popover, Select, TextField, Typography, useTheme} from '@mui/material' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSort , faSortUp, faSortDown, faPlus, faBars, faFilter, faThumbtack,faEyeSlash } from '@fortawesome/free-solid-svg-icons'; + +//======================= MAIN ===================== +function DataGrid(props) { + const [state,dispatch] = useContext(GridContext) + const [sortColumn, setSortColumn] = useState(null) + const [sortDirection, setSortDirection] = useState(null) + const gridRef = useRef() + + + const components = { + agColumnHeader: CustomHeader + } + + const defaultColDef = { + resizable: true, + flex:1 + } + const columnTypes ={ + centerAligned: { + cellStyle: { + display: 'flex ', + justifyContent: 'center', + alignItems: 'center ' + } + }, + } + + useEffect(() => { + if(state.loading == true){ + // Start Loading + gridRef?.current?.api?.showLoadingOverlay() + + }else{ + // Remove loading + gridRef?.current?.api?.hideOverlay() + + } + }, [state.loading]) + + useEffect(() => { + dispatch({ + type:actions.SORT_DIRECTION, + payload:{ + sortDirection: sortDirection + } + }) + }, [sortDirection]) + + + useEffect(() => { + dispatch({ + type:actions.SORT_COLUMN, + payload:{ + sortColumn: sortColumn + } + }) + }, [sortColumn]) + + + + useEffect(() => { + console.log("props passed") + dispatch({ + type: actions.LOAD_ROWS, + payload:{ + rows: props?.gridProps?.rows ?? [] + } + }) + dispatch({ + type: actions.LOAD_COLUMNS, + payload:{ + columns: props?.gridProps?.columns ?? [] + } + }) + + }, []) + + return ( +
+ Please wait while your rows are loading` + + // } + {...props.gridProps} + /> +
+ ) +} + + + +export default DataGrid \ No newline at end of file diff --git a/src/components/DataGridv3/GenerateMockData.js b/src/components/DataGridv3/GenerateMockData.js new file mode 100644 index 0000000..7a9dd7b --- /dev/null +++ b/src/components/DataGridv3/GenerateMockData.js @@ -0,0 +1,47 @@ +import React from 'react' + +function getRandomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function getRandomTitle() { + const names = ['Breaking Bad', 'Peaky Blinders', 'Game Of Thrones', 'The Boys', 'Missing', 'The Three Idiots', 'Overlord', 'Taken']; + return names[getRandomNumber(0, names.length - 1)]; +} + +function getRandomComment() { + const comments = [ + 'Great work!', + 'Needs improvement.', + 'Very informative.', + 'Not clear enough.', + 'Excellent!', + 'Could be better.', + 'Nice job!', + 'Confusing.', + ]; + return comments[getRandomNumber(0, comments.length - 1)]; +} + +function GenerateMockData(numRows) { + const rowData = []; + let idCounter = 1; + + for (let i = 0; i < numRows; i++) { + const row = { + id: idCounter++, + title: getRandomTitle(), + location_scope: ['Global', 'Office', 'Regional'][getRandomNumber(0, 2)], + reviews: getRandomNumber(1, 10), + comments: getRandomComment(), + filler: Math.random() < 0.5, // true or false randomly + enabled: Math.random() < 0.5, // true or false randomly + }; + + rowData.push(row); + } + + return rowData; +} + +export default GenerateMockData \ No newline at end of file diff --git a/src/components/DataGridv3/GridContext.jsx b/src/components/DataGridv3/GridContext.jsx new file mode 100644 index 0000000..9c30b23 --- /dev/null +++ b/src/components/DataGridv3/GridContext.jsx @@ -0,0 +1,93 @@ +import {createContext, useReducer} from 'react' + + +export const initialState = { + columns: [], + rows:[], + loading: false, + sortColumn: null, + sortDirection: null, + filterColumns: [], + search: null +} + +export const actions ={ + RESET_GRID:'RESET_GRID', + SORT_COLUMN: 'SORT_COLUMN', + SORT_DIRECTION:'SORT_DIRECTION', + FILTER_COLUMN: 'FILTER_COLUMN', + CLEAR_FILTER_COLUMN:'CLEAR_FILTER_COLUMN', + LOAD_COLUMNS: 'LOAD_COLUMNS', + LOAD_ROWS: 'LOAD_ROWS', + SET_LOADING: 'SET_LOADING', + SET_LOADING_DONE: 'SET_LOADING_DONE', + SET_SEARCH: 'SET_SEARCH', + TEST_DISPATCH: 'TEST_DISPATCH' +} + +export const GridContext = createContext() + +export function gridReducer(state,action){ + switch (action.type) { + case actions.TEST_DISPATCH: + return {...state} + case actions.SET_SEARCH: + return { + ...state, + search: action.payload.search + } + case actions.SORT_COLUMN: + return { + ...state, + sortColumn: action.payload.sortColumn + } + case actions.SORT_DIRECTION: + return { + ...state, + sortDirection: action.payload.sortDirection + } + case actions.FILTER_COLUMN: + return { + ...state, + filterColumns: action.payload.filterColumns ?? [] + } + case actions.CLEAR_FILTER_COLUMN: + return { + ...state + } + case actions.LOAD_COLUMNS: + return { + ...state, + columns: action.payload.columns + } + case actions.LOAD_ROWS: + return { + ...state, + rows: action.payload.rows + } + case actions.SET_LOADING: + return { + ...state, + loading: true + } + case actions.SET_LOADING_DONE: + return { + ...state, + loading: false + } + default: { + throw new Error(`Unhandled action type: ${action.type}`); + } + } +} + +export function DataGridProvider({ children }){ + const [state, dispatch] = useReducer(gridReducer, initialState); + return ( + + {children} + + ); +}; + +export default GridContext \ No newline at end of file diff --git a/src/components/DataGridv3/GridStyles.scss b/src/components/DataGridv3/GridStyles.scss new file mode 100644 index 0000000..cef9bf5 --- /dev/null +++ b/src/components/DataGridv3/GridStyles.scss @@ -0,0 +1,24 @@ +@use "~ag-grid-community/styles" as ag; + +@include ag.grid-styles(( + themes: (alpine, alpine-dark, belham ,belham-dark, material), + +)); + +.ag-theme-alpine { + &.custom-colors{ + --ag-header-background-color: hsl(177, 88%, 90%); + --ag-background-color: hsl(177, 88%, 97%); + --ag-odd-row-background-color: hsl(177, 88%, 95%); + --ag-alpine-active-color: hsl(0, 100%, 50%); + } +} + +.ag-theme-alpine-dark { + &.custom-border{ + --ag-borders: 1px; + } +} +// .ag-root-wrapper { +// // border: none +// } \ No newline at end of file diff --git a/src/components/DataGridv3/index.js b/src/components/DataGridv3/index.js new file mode 100644 index 0000000..8b8c3b0 --- /dev/null +++ b/src/components/DataGridv3/index.js @@ -0,0 +1,2 @@ +import DataGrid from "./DataGrid" +export {DataGrid}; \ No newline at end of file diff --git a/src/components/DataGridv3/index.stories.js b/src/components/DataGridv3/index.stories.js new file mode 100644 index 0000000..9dd4a98 --- /dev/null +++ b/src/components/DataGridv3/index.stories.js @@ -0,0 +1,270 @@ +import React, { useState, useEffect, useContext, useReducer, useMemo} from 'react' +import DataGrid from './DataGrid' +import { GridContext, actions, initialState, gridReducer , DataGridProvider} from './GridContext' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSort , faSortUp, faSortDown, faPlus, faBars, faFilter, faThumbtack,faEyeSlash } from '@fortawesome/free-solid-svg-icons'; +import {Button, Chip, IconButton, Paper, Typography } from '@mui/material' +import GenerateMockData from './GenerateMockData' +import './GridStyles.scss' +const DataGridv3 = { + title: "DataGrid v3", + component: DataGrid +} + +export default DataGridv3 + +export const DefaultStory = ({...args}) => { + const [state,dispatch] = useReducer(gridReducer,initialState) + const [sortColumn, setSortColumn] = useState(null) + const [sortDirection, setSortDirection] = useState(null) + return ( + + + + + + ) +} + +export const Default = DefaultStory.bind({}); + + +Default.args = { + gridProps:{ + rows:GenerateMockData(100), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + + +export const LightTheme = DefaultStory.bind({}) + +LightTheme.args = { + gridProps:{ + gridTheme: 'ag-theme-alpine', + rows:GenerateMockData(15), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + +export const CustomColors = DefaultStory.bind({}) + +CustomColors.args = { + gridProps:{ + gridTheme: 'ag-theme-alpine custom-colors', + rows:GenerateMockData(15), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + +export const CellAlignment = DefaultStory.bind({}) + +CellAlignment.args={ + gridProps:{ + rows:GenerateMockData(15), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true, type: 'rightAligned' }, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true,type: 'centerAligned'}, + { field: 'enabled',sortable:true, type: 'centerAligned'}, + ] + } +} + +export const CellRender = DefaultStory.bind({}) + +CellRender.args={ + gridProps:{ + rows:GenerateMockData(15), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true, cellRenderer: params => { + return ( +
+ {params.value ? + + + : + + } +
+ ) + }}, + { field: 'enabled',sortable:true, cellRenderer: params => { + return ( +
+ {params.value ? + True + : + False + } +
+ ) + }}, + ] + } +} + +const LoadingStory = ({...args}) => { + const [state,dispatch] = useReducer(gridReducer,initialState) + + return ( + + + + + + ) +} + +function DGWrapper({...args}){ + const [state,dispatch] = useContext(GridContext) + + return( +
+ + + +
+ ) +} + +export const LoadingSample = LoadingStory.bind({}) + +LoadingSample.args = { + gridProps:{ + rows:GenerateMockData(10), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + +export const TemplateHeader = DefaultStory.bind({}) + +TemplateHeader.args = { + headerType:'serverSide', + gridProps:{ + rows:GenerateMockData(10), + columns: [ + { field: 'id', headerName:"ID", sortable:true , width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + + +const CustomHeaderStory = ({...args}) => { + const [state,dispatch] = useReducer(gridReducer,initialState) + + return ( + + + + + + ) +} + +function DGWrapperCustomHeader({...args}){ + const [state,dispatch] = useContext(GridContext) + + const CustomHeaderComponent=(props)=>{ + return ( +
+ + {props.displayName} + +
) + } + + const components = useMemo(()=>{ + return { + agColumnHeader: CustomHeaderComponent + } + }) + return( +
+ +
+ ) +} + + +export const CustomHeader = CustomHeaderStory.bind({}) + + +CustomHeader.args = { + gridProps:{ + rows:GenerateMockData(10), + columns: [ + { field: 'id', headerName:"ID", sortable:true , flex:0, width:80 }, + { field: 'title', sortable:true }, + { field:'location_scope', headerName:'Scope', sortable: true }, + { field: 'reviews',sortable:true}, + { field: 'comments',sortable:true}, + { field: 'filler',sortable:true}, + { field: 'enabled',sortable:true}, + ] + } +} + +function emulateFetch(rows){ + setTimeout(() => { + return rows.append(GenerateMockData(10)) + }, 2000); +} \ No newline at end of file diff --git a/src/components/table/Table.js b/src/components/table/Table.js index 78a13e6..c311bfa 100644 --- a/src/components/table/Table.js +++ b/src/components/table/Table.js @@ -2,8 +2,10 @@ import React, { useState } from "react"; import { AgGridColumn, AgGridReact } from "ag-grid-react"; import LogoCellRender from "./logoCellRender"; import TagCellRender from "./tagCellRender"; -import "ag-grid-community/dist/styles/ag-grid.css"; -import "ag-grid-community/dist/styles/ag-theme-alpine.css"; +// import "ag-grid-community/dist/styles/ag-grid.css"; +// import "ag-grid-community/dist/styles/ag-theme-alpine.css"; +import 'ag-grid-community/styles/ag-grid.css'; // Core grid CSS, always needed +import 'ag-grid-community/styles/ag-theme-alpine.css'; // Optional theme CSS import "./table.css"; export const Table = () => { diff --git a/yarn.lock b/yarn.lock index d0b636a..bf68342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5610,17 +5610,17 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" -ag-grid-community@^25.1.0: - version "25.3.0" - resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-25.3.0.tgz#9a0acce6d35f0c23313aa559dbd8acd737d5ef19" - integrity sha512-Xe4NZG0PP9kvhila1uHU4BeVfLDCWBmcuBYLBZ+49jvK+jYpuwdAjV3AwIlxpZGRR3WTdBUvUkSz9rmi2DRE3Q== +ag-grid-community@latest: + version "30.0.5" + resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-30.0.5.tgz#284623c0af46f87bf0db84975caf7ae7461de59b" + integrity sha512-3AXrNx5hnH0/c4QNgNv5ub1Kna3wVPecCFq2ddMYQ6b7Ob8JhU4bMwcBf+GOiDepVc6I+Df6Bop6rGrc4O8OZw== -ag-grid-react@^25.1.0: - version "25.3.0" - resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-25.3.0.tgz#7dce91fc7ef1821765357517562ba49299d4fddb" - integrity sha512-sRu4Mgxe26GkCKZL9XNMf/jbxLrYOuJo0pumD+LgA3e1x0mKeG8fqMbDLBKfKthIZbTaopIiwn4basThPnVdTg== +ag-grid-react@latest: + version "30.0.5" + resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-30.0.5.tgz#12c10bf2e977a785f1142062ae8957dd82b2b2cb" + integrity sha512-CmO2T52M4DDBXUJJO3R+7GTNFCBSWh5eDBQWdble9W7LhggLcEiQjaGHc7ZZlZbY1mUW9ov0EACbSB4N0qMDPw== dependencies: - prop-types "^15.6.2" + prop-types "^15.8.1" agent-base@5: version "5.1.1"