diff --git a/AGENTS.md b/AGENTS.md index aaca5ffa7..7d51e61f9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,6 +66,17 @@ Keep that peer list in step with the enforced set in the test above. Use the Known live instance: [#1735](https://github.com/layer5io/sistent/issues/1735) (`date-fns` in `src/custom/UniversalFilter.tsx`). +## New public exports need an explicit root re-export + +`rollup-plugin-dts` (used by tsup for the declaration bundle) silently drops symbols that reach +the root barrel only through a nested `export * from './custom'` (or `./base`, etc.) - the runtime +export in `dist/index.*js` survives, but the declaration is missing from `dist/index.d.ts`, so +`import { Foo, type FooProps } from '@sistent/sistent'` fails type-checking downstream. When you add +a new public component or type in a `src//` subtree, also add an explicit +`export { Foo, type FooProps } from './/Foo';` to `src/index.tsx` (see the documented block +of examples there, e.g. `FeedbackButton`, `NavigationItem`). Verify by building and grepping +`dist/index.d.ts` for the symbol - a green `jest`/lint run will not catch this. + ## Repo state that looks broken but is pre-existing `prettier --check` fails on ~82 files and `tsc --noEmit` reports errors across `src/` (including diff --git a/src/custom/NavigationNavbar/index.tsx b/src/custom/NavigationNavbar/index.tsx index f9b89250a..6dd0d95ca 100644 --- a/src/custom/NavigationNavbar/index.tsx +++ b/src/custom/NavigationNavbar/index.tsx @@ -1,3 +1,4 @@ import NavigationNavbar from './navigationNavbar'; +export type { NavigationItem } from './navigationNavbar'; export { NavigationNavbar }; diff --git a/src/custom/NavigationNavbar/navigationNavbar.tsx b/src/custom/NavigationNavbar/navigationNavbar.tsx index d657dbb0a..2bb024c4a 100644 --- a/src/custom/NavigationNavbar/navigationNavbar.tsx +++ b/src/custom/NavigationNavbar/navigationNavbar.tsx @@ -3,13 +3,34 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { ListItemTextProps, MenuListProps, useMediaQuery, useTheme } from '@mui/material'; import React, { MouseEvent, useState } from 'react'; import { Collapse, Divider, ListItemText, MenuItem } from '../../base'; +import { type PermissionAction } from '../PermissionProvider'; +import { type Key } from '../permissions'; import { IconWrapper, MenuItemList, MenuItemSubList, MenuListStyle, SubIconWrapper } from './style'; -type NavigationItem = { +export type NavigationItem = { id: string; title: string; - icon: React.ReactNode; + icon?: React.ReactNode; + /** + * Legacy boolean permission flag. + * When `permissionKey` is provided, this field is ignored. + * @deprecated Prefer `permissionKey` for automatic PermissionShield support. + */ permission?: boolean; + /** + * Sistent permission key for automatic PermissionShield integration. + * When provided, the underlying `MenuItem` receives this key and handles + * disabled state + shield tooltip automatically. Takes precedence over `permission`. + */ + permissionKey?: Key; + /** + * Determines behavior when the user lacks the required permission. + * Only used when `permissionKey` is provided. + * + * - `'showShield'` (default) — disables the item and shows a shield icon. + * - `'hide'` — renders nothing. + */ + permissionAction?: PermissionAction; onClick: () => void; subItems?: NavigationItem[]; addDivider?: boolean; @@ -41,7 +62,6 @@ const NavigationNavbar: React.FC = ({ {navigationItems.map((item) => { const isOpen = openSectionId === item.id; - const permission = item.permission ?? true; const addDivider = item.addDivider ?? false; const showOnWeb = item.showOnWeb ?? true; @@ -50,10 +70,17 @@ const NavigationNavbar: React.FC = ({ return null; } + // When permissionKey is provided, let MenuItem handle permission gating. + // Otherwise fall back to the legacy boolean `permission` field. + const usePermissionKey = !!item.permissionKey; + const legacyPermission = item.permission ?? true; + return ( @@ -73,19 +100,26 @@ const NavigationNavbar: React.FC = ({ {item.subItems && ( - {item.subItems.map((subItem) => ( - - - {subItem.icon && {subItem.icon}} - - - - ))} + {item.subItems.map((subItem) => { + const useSubPermissionKey = !!subItem.permissionKey; + const subLegacyPermission = subItem.permission ?? true; + + return ( + + + {subItem.icon && {subItem.icon}} + + + + ); + })} )} {addDivider && } diff --git a/src/custom/index.tsx b/src/custom/index.tsx index dd445847f..65e924249 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -70,6 +70,7 @@ export { export { InputSearchField } from './InputSearchField'; export { LearningContent } from './LearningContent'; export { NavigationNavbar } from './NavigationNavbar'; +export type { NavigationItem } from './NavigationNavbar'; export { Note } from './Note'; export { Panel } from './Panel'; export { diff --git a/src/index.tsx b/src/index.tsx index 02ed7a8c4..a1fb32597 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -48,6 +48,14 @@ export { export { DataTableToolbar, type DataTableToolbarProps } from './custom/DataTableToolbar'; +// Same nested-barrel dts-drop quirk as FeedbackButton above: reaching the entry +// only through `export * from './custom'`, rollup-plugin-dts drops both the +// `NavigationNavbar` component declaration and the `NavigationItem` type from the +// bundled d.ts, so `import { NavigationNavbar, type NavigationItem } from +// "@sistent/sistent"` fails type-checking despite the runtime exports existing. +// The explicit re-export forces both declarations into the published bundle. +export { NavigationNavbar, type NavigationItem } from './custom/NavigationNavbar'; + // Same nested-barrel dts-drop quirk as FeedbackButton above. `createCanShow` is // worse than a missing type: consumers still resolve it at runtime, so the import // silently degrades to `any` and its `eventBus` argument stops being