Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<domain>/` subtree, also add an explicit
`export { Foo, type FooProps } from './<domain>/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
Expand Down
1 change: 1 addition & 0 deletions src/custom/NavigationNavbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import NavigationNavbar from './navigationNavbar';
export type { NavigationItem } from './navigationNavbar';

export { NavigationNavbar };
68 changes: 51 additions & 17 deletions src/custom/NavigationNavbar/navigationNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -41,7 +62,6 @@ const NavigationNavbar: React.FC<NavigationNavbarProps> = ({
<MenuListStyle {...MenuListProps} dense>
{navigationItems.map((item) => {
const isOpen = openSectionId === item.id;
const permission = item.permission ?? true;
const addDivider = item.addDivider ?? false;

const showOnWeb = item.showOnWeb ?? true;
Expand All @@ -50,10 +70,17 @@ const NavigationNavbar: React.FC<NavigationNavbarProps> = ({
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 (
<React.Fragment key={item.id}>
<MenuItem
disabled={!permission}
disabled={usePermissionKey ? undefined : !legacyPermission}
permissionKey={item.permissionKey}
permissionAction={item.permissionAction}
onClick={item.onClick}
data-testid={`nav-item-${item.id}`}
>
Expand All @@ -73,19 +100,26 @@ const NavigationNavbar: React.FC<NavigationNavbarProps> = ({
</MenuItem>
{item.subItems && (
<Collapse in={isOpen} timeout="auto" unmountOnExit variant="submenu">
{item.subItems.map((subItem) => (
<MenuItem
key={subItem.id}
disabled={!subItem.permission}
onClick={subItem.onClick}
data-testid={`nav-subitem-${subItem.id}`}
>
<MenuItemSubList>
{subItem.icon && <SubIconWrapper>{subItem.icon}</SubIconWrapper>}
<ListItemText primary={subItem.title} {...ListItemTextProps} />
</MenuItemSubList>
</MenuItem>
))}
{item.subItems.map((subItem) => {
const useSubPermissionKey = !!subItem.permissionKey;
const subLegacyPermission = subItem.permission ?? true;

return (
<MenuItem
key={subItem.id}
disabled={useSubPermissionKey ? undefined : !subLegacyPermission}
permissionKey={subItem.permissionKey}
permissionAction={subItem.permissionAction}
onClick={subItem.onClick}
data-testid={`nav-subitem-${subItem.id}`}
>
<MenuItemSubList>
{subItem.icon && <SubIconWrapper>{subItem.icon}</SubIconWrapper>}
<ListItemText primary={subItem.title} {...ListItemTextProps} />
</MenuItemSubList>
</MenuItem>
);
})}
</Collapse>
)}
{addDivider && <Divider />}
Expand Down
1 change: 1 addition & 0 deletions src/custom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading