diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc48c4..f8d8ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [v2.6.0](https://github.com/netservicespa/astrea-react-ds/commit/94a729a8687c6be837e21e10fe1523dac3b19442) (Nov 15 2024) + +## New Additions + +- **NsNotificationList**: + - Introduced the new `NsNotificationList` component. + - Added translation support. + - Exported types for usage. + - Created a Storybook story for demonstration. + +- **NsDataGrid**: + - Column Visibility Menu: Introduced a menu to manage column visibility, dynamically positionable using the render function within the table component. + +- **Storybook**: + - Added Figma integration within Storybook stories. + +## Improved + +- **NsCard**: + - Added `sx` support for enhanced customization. + +- **NsTooltip**: + - Expanded applicability, allowing tooltips to be applied to any element instead of just icons. + +- **Storybook**: + - Removed `@storybook/addon-mdx-gfm` as it is no longer necessary. + - Integrated `Remark` to support GitHub Flavored Markdown (GFM) in Storybook documentation. + - Upgraded Storybook to version 8.4. + +- **NsDataGrid**: + - Custom Layout Support: Updated `NsDataGridClient` to accept a custom renderer for flexible layouts. + - Translation Structure: Introduced `table.controls` section for handling translations, starting with `columnVisibility: "Columns Visibility"`. + - Dynamic Alignment: The column visibility button is now right-aligned using `justifyContent: 'flex-end`. + - Removed `Typography` within table cells to enable full cell customization. + +- **NsNotification**: + - Added the ability to include links in notifications. + - Introduced a `markAllAsRead` function. + - Enhanced translation support. + +- **NsHeader**: + - Added cascading menu support via dropdown, with an example in Storybook. + - Fixed `infoBox` positioning to align on the right. + - Removed unnecessary SVG margins. + +- **NsFeedback**: + - Improved colors based on accessibility testing. + +- **NsDropdown**: + - Added support for disabling the overlay. + +## Fixed + +- **Exports**: + - Removed `types.ts` file; type declarations are now included within component files. + - Resolved deprecated `assert.CallTracker` issue for compatibility with newer Node.js versions. + # [v2.5.0](https://github.com/netservicespa/astrea-react-ds/commit/2395ae49e5f9e3d2443a81a162c3c1c094ccebed) (Nov 5 2024) ## New Additions diff --git a/package.json b/package.json index cf4e167..83aeb38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netservice/astrea-react-ds", - "version": "2.6.0", + "version": "2.7.0", "main": "dist/umd/index.min.js", "module": "dist/esm/index.js", "license": "Apache-2.0", diff --git a/src/components/components/Dropdown/NsDropDown.tsx b/src/components/components/Dropdown/NsDropDown.tsx index db65170..19cc93c 100644 --- a/src/components/components/Dropdown/NsDropDown.tsx +++ b/src/components/components/Dropdown/NsDropDown.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react'; +import React, { useState, useId } from 'react'; import { Box, Divider, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material'; -import HomeIcon from '@mui/icons-material/Home'; import LogoutIcon from '@mui/icons-material/Logout'; -import { alpha, styled } from '@mui/material/styles'; - +import { alpha, styled, SxProps, Theme, useTheme } from '@mui/material/styles'; +import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined'; /** * DynamicLink/ dropdown Component * @author vadim.chilinciuc @@ -11,7 +10,7 @@ import { alpha, styled } from '@mui/material/styles'; export interface IDropdownItems { name: string; - path: string; + path: string | IDropdownItems[]; icon?: React.ReactElement; } export interface IDropDownConfiguration { @@ -23,6 +22,8 @@ export interface IDropDownConfiguration { vertical?: any; horizontal?: any; }; + hover?: boolean; + dropDownIcon?: React.ReactElement | boolean; } export interface IDropDown { /** @@ -53,6 +54,7 @@ export interface IDropDown { * Extra configuration for Backdrop. */ overlay?: boolean; + icon?: boolean | React.ReactElement; } export interface DynamicLinkProps { @@ -70,12 +72,11 @@ export interface DynamicLinkProps { * The clickable element (e.g., icon, div, component) that triggers the redirection. */ children: any; + sx?: SxProps; } export const StyledLink = styled('a')(({ theme }) => ({ color: `${theme.palette.primary.main}`, - backgroundColor: 'transparent !important', - margin: '0px !important', })); const StyledMenu = styled(Menu)<{ overlay: boolean }>(({ theme, overlay }) => ({ @@ -84,34 +85,35 @@ const StyledMenu = styled(Menu)<{ overlay: boolean }>(({ theme, overlay }) => ({ }, })); -export const DynamicLink = ({ router, to, children }: DynamicLinkProps) => { +type ExtendChildrenProps = { + children: React.ReactElement; + icon?: boolean | React.ReactElement; + isOpen: boolean; +}; +export const DynamicLink = ({ router, to, children, sx }: DynamicLinkProps) => { const isReactRouter = typeof router?.history !== 'undefined'; const isNextRouter = typeof router?.push !== 'undefined'; - + const commonStyle = { + className: 'font-semiBold', + style: { textDecoration: 'none', cursor: 'pointer', margin: '0px' }, + sx, + }; if (isReactRouter) { return ( - router.history.push(to)} - className={'font-semiBold'} - style={{ textDecoration: 'none', cursor: 'pointer', margin: '0px' }} - > + router.history.push(to)} {...commonStyle}> {children} ); } else if (isNextRouter) { return ( - router.push(to)} - className={'font-semiBold'} - style={{ textDecoration: 'none', cursor: 'pointer', margin: '0px' }} - > + router.push(to)} {...commonStyle}> {children} ); } else { // Handle the case when neither React Router nor Next.js Router is detected return ( - + {children} ); @@ -125,10 +127,11 @@ export const NsDropDown = ({ children, dropDownConfiguration, overlay = false, + icon = false, }: IDropDown) => { + const theme = useTheme(); const [isOpen, setIsOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); - const handleMenuOpen = (event: React.MouseEvent) => { setIsOpen(true); setAnchorEl(event.currentTarget); @@ -146,15 +149,22 @@ export const NsDropDown = ({ const renderMenuItems = () => { if (Array.isArray(dropdownItems)) { - const items = dropdownItems.map((item, index: number) => ( - - - {item.icon && {item.icon}} - {item.name} - - {index < dropdownItems.length - 1 && } - - )); + const items = dropdownItems.map((item, index) => { + if (typeof item.path === 'string') { + return ( + + + + {item.icon && item.icon} + {item.name} + + + {index < dropdownItems.length - 1 && } + + ); + } + return null; + }); if (onLogout) { items.push( @@ -176,10 +186,97 @@ export const NsDropDown = ({ } }; + const ExtendChildren = ({ children, icon, isOpen }: ExtendChildrenProps) => { + return ( + + {React.Children.map(children, (child, i) => { + if (React.isValidElement(child)) { + return ( + + {React.cloneElement(child)} + {icon && + (isOpen ? ( + typeof icon === 'boolean' ? ( + + ) : ( + <>{icon} + ) + ) : typeof icon === 'boolean' ? ( + + ) : ( + + {icon} + + ))} + + ); + } + return child; + })} + + ); + }; + return ( <> - - {React.isValidElement(children) && React.Children.only(children)} + + React.ReactElement; +export type ColumnDef = BaseColumnDef & { + meta?: { + hide?: boolean; // Proprietà per nascondere le colonne + [key: string]: any; + }; +}; + export interface NsDataGridCommonProps extends TableContainerProps { /** * An array of column definitions for the grid. diff --git a/src/components/components/datatable/NsDataGridBase.tsx b/src/components/components/datatable/NsDataGridBase.tsx index d739dc1..8d8091c 100644 --- a/src/components/components/datatable/NsDataGridBase.tsx +++ b/src/components/components/datatable/NsDataGridBase.tsx @@ -53,14 +53,16 @@ export interface NsDataGridOptions { /** The available page sizes */ rowsPerPageOptions?: number[]; }; + bodyTextAlign?: 'center' | 'left' | 'right'; + headerJustifyContent?: 'space-evenly' | 'flex-start' | 'flex-end'; + /** Specify selected Row */ + selectedRow?: string; } export interface NsDataGridBaseProps extends TableContainerProps { table: Table; options?: NsDataGridOptions; debug?: boolean; - bodyTextAlign?: 'center' | 'left' | 'right'; - headerJustifyContent?: 'space-evenly' | 'flex-start' | 'flex-end'; } /** @@ -131,8 +133,6 @@ export function NsDataGridBase({ table, options = {}, debug = false, - headerJustifyContent, - bodyTextAlign, // Mui TableContainer props sx, component = Paper, @@ -180,7 +180,9 @@ export function NsDataGridBase({ } }} > - + {header.isPlaceholder ? null @@ -206,7 +208,7 @@ export function NsDataGridBase({ ))} - + ); diff --git a/src/components/components/datatable/NsDataGridClient.tsx b/src/components/components/datatable/NsDataGridClient.tsx index f298661..1ee0846 100644 --- a/src/components/components/datatable/NsDataGridClient.tsx +++ b/src/components/components/datatable/NsDataGridClient.tsx @@ -64,7 +64,7 @@ export function NsDataGridClient({ data, - columns, + columns: columns.filter((column) => !column.meta?.hide), defaultColumn, columnResizeMode: 'onChange', getCoreRowModel: getCoreRowModel(), diff --git a/src/components/components/datatable/NsDataGridServer.tsx b/src/components/components/datatable/NsDataGridServer.tsx index e26f9f4..747ace1 100644 --- a/src/components/components/datatable/NsDataGridServer.tsx +++ b/src/components/components/datatable/NsDataGridServer.tsx @@ -111,7 +111,7 @@ export function NsDataGridServer>({}); const [rowSelection, setRowSelection] = React.useState({}); - const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({}); // Notify event listener of filter change React.useEffect(() => { @@ -170,7 +170,18 @@ export function NsDataGridServer [sort.id, sort.desc ? 'desc' : 'asc']), ) as ColumnSorting; // Call fetcher with pagination and sorting - fetcher(pagination, mySorting, filters).then(setPage); + // fetcher(pagination, mySorting, filters).then(setPage); + fetcher(pagination, mySorting, filters).then((data) => { + setPage(data); + // if (data.data.length > 0) { + // setRowSelection({ [data.data[0].id!]: true }); + // } + console.log('options', options); + if (options.selectedRow) { + const firstRowId = options.selectedRow; + setRowSelection({ [firstRowId]: true }); + } + }); }, [pagination, sorting, filters, fetcher]); // Reset pagination on filter change @@ -188,7 +199,7 @@ export function NsDataGridServer !column.meta?.hide), defaultColumn, enableColumnResizing: options?.resizable, columnResizeMode: 'onChange', @@ -231,7 +242,14 @@ export function NsDataGridServer ); const TableComponent = ; - const TablePagerComponent = ; + const TablePagerComponent = ( + + ); const ColumnVisibilityComponent = ; return render(FilterContainerComponent, TableComponent, TablePagerComponent, children, ColumnVisibilityComponent); diff --git a/src/components/components/datatable/filtering/ColumnVisibilityMenu.tsx b/src/components/components/datatable/filtering/ColumnVisibilityMenu.tsx index 082add5..ab31b53 100644 --- a/src/components/components/datatable/filtering/ColumnVisibilityMenu.tsx +++ b/src/components/components/datatable/filtering/ColumnVisibilityMenu.tsx @@ -34,20 +34,45 @@ export function ColumnVisibilityMenu({ table }: ColumnVi anchorEl={anchorEl} open={isMenuOpen} onClose={handleMenuClose} - sx={{ maxHeight: 300, width: '20ch' }} + sx={{ + minWidth: 200, + width: 'auto', + }} + slotProps={{ + paper: { + sx: { + minWidth: 200, + width: 'auto', + }, + }, + }} > - {table.getAllLeafColumns().map((column) => ( - - - - - {column.id} - - ))} + {table.getAllLeafColumns().map((column) => { + // Determinare il testo da visualizzare + const header = typeof column.columnDef.header === 'string' + ? column.columnDef.header + : column.id; + + return ( + + + + + {header} + + ); + })} ); -} \ No newline at end of file +} diff --git a/src/components/components/notification/NsNotification.tsx b/src/components/components/notification/NsNotification.tsx index 4fefc24..d66d1c0 100644 --- a/src/components/components/notification/NsNotification.tsx +++ b/src/components/components/notification/NsNotification.tsx @@ -15,13 +15,13 @@ export interface IReadWrite { } export interface NotificationData { - notifications: IReadWrite[]; + notifications?: IReadWrite[]; markAsRead?: (...args: any[]) => any; /** * Link to show more */ showMore?: DynamicLinkProps; - totalCount: number; + totalCount?: number; } /** @@ -33,10 +33,10 @@ export interface NotificationData { * An object containing the notification data, including both read and unread notifications. */ export interface INotificationData { - read: NotificationData; - unread: NotificationData; + read?: NotificationData; + unread?: NotificationData; onlyButton?: Omit; - children: React.ReactNode; + children?: React.ReactNode; markAsRead?: (...args: any[]) => any; } @@ -84,32 +84,33 @@ const MainNotificationDiv = ({ )} - {notifications.map((item: any, index: number) => { - return ( - - - - {item.status === Status.valid ? ( - - ) : ( - - )} - - - {item.text} - - - - ); - })} + {notifications && + notifications.map((item: any, index: number) => { + return ( + + + + {item.status === Status.valid ? ( + + ) : ( + + )} + + + {item.text} + + + + ); + })} {isReadUnread === 'UNREAD' && markAsRead && ( + + + + + ); +}; diff --git a/src/components/patterns/navigation/NsHeader.tsx b/src/components/patterns/navigation/NsHeader.tsx index 124d723..ebc1bc9 100644 --- a/src/components/patterns/navigation/NsHeader.tsx +++ b/src/components/patterns/navigation/NsHeader.tsx @@ -88,6 +88,9 @@ export interface HeaderProps { configuration: { centralLogo?: boolean; dropDownConfiguration?: IDropDownConfiguration | undefined; + hover?: boolean; + dropDownIcon?: React.ReactElement | boolean; + notification?: boolean; }; } export const NsHeader = ({ diff --git a/src/components/patterns/navigation/headers/HorizontalHeader.tsx b/src/components/patterns/navigation/headers/HorizontalHeader.tsx index ab0f774..5aa7f94 100644 --- a/src/components/patterns/navigation/headers/HorizontalHeader.tsx +++ b/src/components/patterns/navigation/headers/HorizontalHeader.tsx @@ -3,7 +3,7 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import NotificationsNoneOutlinedIcon from '@mui/icons-material/NotificationsNoneOutlined'; -import { Box, Menu, MenuItem, Typography } from '@mui/material'; +import { Box } from '@mui/material'; import { styled } from '@mui/material/styles'; import React, { useState } from 'react'; import { DynamicLink, IDropdownItems, NsDropDown, StyledLink } from '../../../components/dropdown/NsDropDown'; @@ -32,75 +32,75 @@ const Logo = styled('svg')({ display: 'inline-block', }); const LogoContainer = styled('div')( - ({ theme, configuration }: any) => css` - background-color: ${theme.header.backgroundColor}; - border-bottom: 9px solid ${theme.header.borderColor}; - padding: 20px 20px; - display: flex; - justify-content: ${configuration?.centralLogo ? 'center' : 'space-between'}; - align-items: center; - flex-direction: row; - width: 100%; + ({ theme, configuration }: any) => css` + background-color: ${theme.header.backgroundColor}; + border-bottom: 9px solid ${theme.header.borderColor}; + padding: 20px 20px; + display: flex; + justify-content: ${configuration?.centralLogo ? 'center' : 'space-between'}; + align-items: center; + flex-direction: row; + width: 100%; - .titles { - text-align: left; - width: ${configuration?.centralLogo ? '494px' : 'auto'}; + .titles { + text-align: left; + width: ${configuration?.centralLogo ? '494px' : 'auto'}; - h1 { - font-weight: 600; - font-size: 1.7em; - line-height: 1; - margin: 0 0 2px 18px; - color: #fff; - } + h1 { + font-weight: 600; + font-size: 1.7em; + line-height: 1; + margin: 0 0 2px 18px; + color: #fff; + } - .subtitle { - font-size: 0.7em; - text-transform: uppercase; - color: #fff; - margin: 0 0 0 18px; + .subtitle { + font-size: 0.7em; + text-transform: uppercase; + color: #fff; + margin: 0 0 0 18px; + } } - } - a { - display: flex; - align-items: center; - min-width: ${configuration?.centralLogo ? '370px' : 'auto'}; - text-decoration: none; - color: ${theme.header.menuTextColor}; + a { + display: flex; + align-items: center; + min-width: ${configuration?.centralLogo ? '370px' : 'auto'}; + text-decoration: none; + color: ${theme.header.menuTextColor}; - &:focus { - background-color: ${theme.header.focusBackgroundColor}; - } + &:focus { + background-color: ${theme.header.focusBackgroundColor}; + } - &:focus-visible { - outline: 0; - } + &:focus-visible { + outline: 0; + } - svg { - display: inline-block; - vertical-align: top; - max-height: 70px; + svg { + display: inline-block; + vertical-align: top; + max-height: 70px; + } } - } - .mobile-menu-icon { - display: none !important; - cursor: pointer; - color: ${theme.header.backgroundColor}; - font-weight: 599; + .mobile-menu-icon { + display: none !important; + cursor: pointer; + color: ${theme.header.backgroundColor}; + font-weight: 599; - @media (max-width: 768px) { - display: block !important; + @media (max-width: 768px) { + display: block !important; + } } - } - @media (min-width: 768px) { - .mobile-menu-icon { - display: none !important; + @media (min-width: 768px) { + .mobile-menu-icon { + display: none !important; + } } - } - ` + `, ); const NavigationContainer = styled('div')( @@ -108,7 +108,6 @@ const NavigationContainer = styled('div')( display: flex; justify-content: space-between; background-color: ${!configuration?.centralLogo ? theme.header.menuBackgroundColor : null}; - padding: 0px 8px; @media (max-width: 768px) { display: none; transition: max-height 0.2s ease-out; @@ -121,20 +120,18 @@ const NavigationContainer = styled('div')( } nav { + height: 40px; display: flex; justify-content: flex-start; flex-direction: row; + align-items: center; a { - padding: 4px 10px; + padding: 0px 10px; font-weight: 600; display: flex; justify-content: space-between; - background-color: ${!configuration?.centralLogo ? theme.header.menuBackgroundColor : null}; - margin-bottom: 30px; - padding: 6px 8px; - color: ${theme.header.menuTextColor}; - + margin: 0px; @media (max-width: 768px) { display: none; transition: max-height 0.2s ease-out; @@ -177,23 +174,59 @@ const UserMenu: React.FC = ({ configuration, }) => { const { t } = useTranslation(); + const theme = useTheme(); + const [notificationState, setNotificationState] = useState(notificationData); return ( -