@cratis/scene.blueprint.default 1.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.storybook/preview.tsx +44 -0
  2. package/README.md +25 -12
  3. package/dist/cjs/shell/Breadcrumb.js +47 -10
  4. package/dist/cjs/shell/Breadcrumb.js.map +1 -1
  5. package/dist/cjs/shell/ConfigPanel.js +111 -82
  6. package/dist/cjs/shell/ConfigPanel.js.map +1 -1
  7. package/dist/cjs/shell/Sidebar.js +7 -3
  8. package/dist/cjs/shell/Sidebar.js.map +1 -1
  9. package/dist/cjs/shell/Topbar.js +7 -3
  10. package/dist/cjs/shell/Topbar.js.map +1 -1
  11. package/dist/cjs/shell/UserMenu.js +48 -21
  12. package/dist/cjs/shell/UserMenu.js.map +1 -1
  13. package/dist/esm/for_AppShell/when_rendering_a_gallery_screen.js +2 -1
  14. package/dist/esm/for_AppShell/when_rendering_a_gallery_screen.js.map +1 -1
  15. package/dist/esm/shell/Breadcrumb.d.ts.map +1 -1
  16. package/dist/esm/shell/Breadcrumb.js +49 -12
  17. package/dist/esm/shell/Breadcrumb.js.map +1 -1
  18. package/dist/esm/shell/ConfigPanel.d.ts.map +1 -1
  19. package/dist/esm/shell/ConfigPanel.js +112 -83
  20. package/dist/esm/shell/ConfigPanel.js.map +1 -1
  21. package/dist/esm/shell/MenuEntry.d.ts +6 -0
  22. package/dist/esm/shell/MenuEntry.d.ts.map +1 -0
  23. package/dist/esm/shell/MenuEntry.js +2 -0
  24. package/dist/esm/shell/MenuEntry.js.map +1 -0
  25. package/dist/esm/shell/Sidebar.d.ts.map +1 -1
  26. package/dist/esm/shell/Sidebar.js +7 -3
  27. package/dist/esm/shell/Sidebar.js.map +1 -1
  28. package/dist/esm/shell/Topbar.d.ts.map +1 -1
  29. package/dist/esm/shell/Topbar.js +7 -3
  30. package/dist/esm/shell/Topbar.js.map +1 -1
  31. package/dist/esm/shell/UserMenu.d.ts.map +1 -1
  32. package/dist/esm/shell/UserMenu.js +48 -21
  33. package/dist/esm/shell/UserMenu.js.map +1 -1
  34. package/dist/esm/shell/index.d.ts +1 -0
  35. package/dist/esm/shell/index.d.ts.map +1 -1
  36. package/dist/esm/shell/index.js +1 -0
  37. package/dist/esm/shell/index.js.map +1 -1
  38. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  39. package/for_AppShell/when_rendering_a_gallery_screen.tsx +10 -1
  40. package/package.json +28 -12
  41. package/shell/Breadcrumb.tsx +44 -8
  42. package/shell/ConfigPanel.tsx +72 -61
  43. package/shell/MenuEntry.ts +27 -0
  44. package/shell/Sidebar.tsx +5 -4
  45. package/shell/Topbar.tsx +5 -4
  46. package/shell/UserMenu.tsx +32 -17
  47. package/shell/index.ts +1 -0
  48. package/shell/layout.css +5 -5
  49. package/.storybook/preview.ts +0 -20
@@ -2,16 +2,25 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { render, screen } from '@testing-library/react';
5
+ import { PrimeReactProvider } from '@primereact/core';
5
6
  import { SceneElementView, coreComponents, corePackage, mergePackageRegistries } from '@cratis/scene.react';
6
7
  import { defaultBlueprint } from '../defaultBlueprint';
7
8
  import { composeScreenElement, galleryComponentCatalog, galleryPreviewProfile, galleryScreen, resolveElementComponentNames } from '../gallery';
8
9
 
9
10
  const registry = mergePackageRegistries([corePackage, defaultBlueprint]);
10
11
 
12
+ // PrimeReact 11 components resolve their configuration through `PrimeReactProvider` and throw without
13
+ // one, and these screens render five of them - the topbar toggle, the sidebar pin, the user menu, the
14
+ // breadcrumb and the configurator's drawer. No preset is handed over: these specs assert structure and
15
+ // text, and a theme would only add runtime CSS nothing here reads.
11
16
  function renderGalleryScreen(name: string) {
12
17
  const galleryItem = galleryScreen(name)!;
13
18
  const element = resolveElementComponentNames(composeScreenElement(galleryItem), galleryPreviewProfile, galleryComponentCatalog);
14
- render(<SceneElementView element={element} registry={registry} resolveBinding={() => undefined} />);
19
+ render(
20
+ <PrimeReactProvider>
21
+ <SceneElementView element={element} registry={registry} resolveBinding={() => undefined} />
22
+ </PrimeReactProvider>,
23
+ );
15
24
  }
16
25
 
17
26
  function has(selector: string): boolean {
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@cratis/scene.blueprint.default",
3
- "version": "1.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "The default blueprint: application shell layouts, the screen and dialog templates that go inside them, the components that fill their slots, and two themes.",
5
+ "keywords": [
6
+ "cratis",
7
+ "scene",
8
+ "react",
9
+ "ui",
10
+ "templates",
11
+ "blueprint",
12
+ "themes",
13
+ "low-code"
14
+ ],
5
15
  "author": "Cratis",
6
16
  "license": "MIT",
7
17
  "repository": {
@@ -43,26 +53,32 @@
43
53
  "build-storybook": "storybook build"
44
54
  },
45
55
  "dependencies": {
46
- "@cratis/scene.engine": "1.1.0",
47
- "@cratis/scene.model": "1.1.0",
48
- "@cratis/scene.react": "1.1.0"
56
+ "@cratis/scene.engine": "3.0.0",
57
+ "@cratis/scene.model": "3.0.0",
58
+ "@cratis/scene.react": "3.0.0"
49
59
  },
50
60
  "devDependencies": {
51
- "@cratis/scene.engine": "1.1.0",
52
- "@cratis/scene.model": "1.1.0",
53
- "@cratis/scene.react": "1.1.0",
61
+ "@cratis/scene.engine": "3.0.0",
62
+ "@cratis/scene.model": "3.0.0",
63
+ "@cratis/scene.react": "3.0.0",
64
+ "@primereact/core": "11.1.0",
65
+ "@primereact/headless": "11.1.0",
66
+ "@primeuix/themes": "^3.0.0",
54
67
  "@storybook/addon-links": "^10.4.1",
55
68
  "@storybook/react": "^10.4.1",
56
69
  "@storybook/react-vite": "^10.4.1",
57
- "primeicons": "^7.0.0",
58
- "primereact": "10.9.8",
70
+ "primeicons": "8.0.0",
71
+ "primereact": "11.1.0",
59
72
  "react": "^19.2.6",
60
73
  "react-dom": "^19.2.6",
61
74
  "storybook": "^10.4.1"
62
75
  },
63
76
  "peerDependencies": {
64
- "primereact": "^10.9.0",
65
- "react": "^18.0.0 || ^19.0.0",
66
- "react-dom": "^18.0.0 || ^19.0.0"
77
+ "@primereact/core": "^11.0.0",
78
+ "@primereact/headless": "^11.0.0",
79
+ "primeicons": "^8.0.0",
80
+ "primereact": "^11.0.0",
81
+ "react": "^19.0.0",
82
+ "react-dom": "^19.0.0"
67
83
  }
68
84
  }
@@ -1,23 +1,31 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { BreadCrumb } from 'primereact/breadcrumb';
5
- import { MenuItem as PrimeMenuItem } from 'primereact/menuitem';
4
+ import { Fragment } from 'react';
5
+ import { Breadcrumb as PrimeBreadcrumb } from 'primereact/breadcrumb';
6
6
  import { RegisteredComponentProps } from '@cratis/scene.react';
7
+ import { MenuEntry } from './MenuEntry';
7
8
  import { readOptionalString, readRecords, recordString } from './elementProperties';
8
9
 
9
10
  /**
10
11
  * The trail above the content.
11
12
  *
12
- * PrimeReact's own `BreadCrumb` does the rendering rather than hand-written markup, because a breadcrumb
13
+ * PrimeReact's own `Breadcrumb` does the rendering rather than hand-written markup, because a breadcrumb
13
14
  * is an ordinary component with no layout-mode behavior at all - it looks the same in every one of the
14
- * eight modes. The shell only writes CSS where PrimeReact 10 genuinely has no answer, and this is not one
15
- * of those places.
15
+ * eight modes. The shell only writes CSS where PrimeReact genuinely has no answer, and this is not one of
16
+ * those places: `Breadcrumb.Root` still supplies the `nav`, its `aria-label` and the list semantics, which
17
+ * is the part that is easy to get wrong by hand.
18
+ *
19
+ * What PrimeReact 11 no longer supplies is the `model` array - the component is composed now, and the
20
+ * `primereact/menuitem` type the array was made of is gone with it, which is why the trail is mapped into
21
+ * this package's own {@link MenuEntry}. The last entry is `Breadcrumb.Current` rather than a link: a trail
22
+ * whose final crumb navigates to the page already open is a link that does nothing, and it takes
23
+ * `aria-current='page'` away from the one element that should carry it.
16
24
  */
17
25
  export function Breadcrumb({ element }: RegisteredComponentProps) {
18
26
  const home = readOptionalString(element, 'homeTargetScreen');
19
- const model = readRecords(element, 'items').map(
20
- (item): PrimeMenuItem => ({
27
+ const trail = readRecords(element, 'items').map(
28
+ (item): MenuEntry => ({
21
29
  label: recordString(item, 'label'),
22
30
  url: recordString(item, 'targetScreen') ? `#/${recordString(item, 'targetScreen')}` : undefined,
23
31
  }),
@@ -25,7 +33,35 @@ export function Breadcrumb({ element }: RegisteredComponentProps) {
25
33
 
26
34
  return (
27
35
  <div data-scene-id={element.id}>
28
- <BreadCrumb model={model} home={home ? { icon: 'pi pi-home', url: `#/${home}` } : undefined} />
36
+ <PrimeBreadcrumb.Root>
37
+ <PrimeBreadcrumb.List>
38
+ {home && (
39
+ <PrimeBreadcrumb.Item>
40
+ <PrimeBreadcrumb.Link as='a' href={`#/${home}`} aria-label='Home'>
41
+ <i className='pi pi-home' aria-hidden='true' />
42
+ </PrimeBreadcrumb.Link>
43
+ </PrimeBreadcrumb.Item>
44
+ )}
45
+ {trail.map((entry, index) => (
46
+ <Fragment key={`${index}-${entry.label}`}>
47
+ {(!!home || index > 0) && (
48
+ <PrimeBreadcrumb.Separator>
49
+ <i className='pi pi-chevron-right' aria-hidden='true' />
50
+ </PrimeBreadcrumb.Separator>
51
+ )}
52
+ <PrimeBreadcrumb.Item>
53
+ {entry.url ? (
54
+ <PrimeBreadcrumb.Link as='a' href={entry.url}>
55
+ {entry.label}
56
+ </PrimeBreadcrumb.Link>
57
+ ) : (
58
+ <PrimeBreadcrumb.Current>{entry.label}</PrimeBreadcrumb.Current>
59
+ )}
60
+ </PrimeBreadcrumb.Item>
61
+ </Fragment>
62
+ ))}
63
+ </PrimeBreadcrumb.List>
64
+ </PrimeBreadcrumb.Root>
29
65
  </div>
30
66
  );
31
67
  }
@@ -1,9 +1,8 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { useState } from 'react';
5
4
  import { Button } from 'primereact/button';
6
- import { Sidebar } from 'primereact/sidebar';
5
+ import { Drawer } from 'primereact/drawer';
7
6
  import { RegisteredComponentProps } from '@cratis/scene.react';
8
7
  import { ColorScheme, MenuTheme, colorSchemes, menuThemes, useLayoutConfig } from '../configuration';
9
8
  import { LayoutModeSwitcher } from './LayoutModeSwitcher';
@@ -30,76 +29,88 @@ const menuThemeLabels: Record<MenuTheme, string> = {
30
29
  * theme - because those are the four choices that actually change how an application feels, and a
31
30
  * configurator with more knobs than that becomes a settings screen nobody finishes reading.
32
31
  *
33
- * The panel itself is PrimeReact 10's `Sidebar`, which is an overlay drawer rather than an app-shell
34
- * sidebar - this is the one use it was designed for. (v11 renames it `Drawer` precisely to stop the
35
- * confusion, and gives the app-shell role to a new `Sidebar`.)
32
+ * The panel is `primereact/drawer`, which is the slide-in overlay this component has always wanted.
33
+ * PrimeReact 11 gave the name `Sidebar` to something else entirely - a new app-shell primitive with
34
+ * `SidebarLayout`/`SidebarAside`/`SidebarPanel` parts - so the v10 `Sidebar` import this file used to
35
+ * carry would still resolve after the upgrade and would silently render an app shell inside the shell.
36
+ * That is the one rename in the whole library that fails quietly rather than loudly, which is why it is
37
+ * called out here: this is `Drawer`, deliberately, and it is not the {@link Sidebar} next door either -
38
+ * that one is this package's own component for the docked panel's chrome.
39
+ *
40
+ * Open state is the drawer's own. `Drawer.Trigger` renders in place through `Drawer.Root`'s fragment, so
41
+ * the floating cog sits exactly where it did while getting `aria-expanded`/`aria-controls` from the
42
+ * component rather than from hand-written props that can drift out of step with the panel.
36
43
  */
37
44
  export function ConfigPanel({ element, slots }: RegisteredComponentProps) {
38
- const [isVisible, setIsVisible] = useState(false);
39
45
  const { config, setColorScheme, setMenuTheme } = useLayoutConfig();
40
46
  const title = readString(element, 'title', 'Settings');
41
47
  const showsMenuTheme = readBoolean(element, 'showMenuTheme', true);
42
48
  const showsMode = readBoolean(element, 'showLayoutMode', true);
43
49
 
44
50
  return (
45
- <>
46
- <Button
47
- type='button'
48
- rounded
49
- icon='pi pi-cog'
50
- className='layout-config-button'
51
- aria-label={title}
52
- data-scene-id={element.id}
53
- onClick={() => setIsVisible(true)}
54
- />
55
- <Sidebar visible={isVisible} position='right' header={title} onHide={() => setIsVisible(false)}>
56
- <div className='layout-config-panel'>
57
- <div>
58
- <h3 className='layout-config-section-title'>Color scheme</h3>
59
- <div className='layout-config-options' role='group' aria-label='Color scheme'>
60
- {colorSchemes.map(scheme => (
61
- <button
62
- key={scheme}
63
- type='button'
64
- className='layout-config-option'
65
- aria-pressed={config.colorScheme === scheme}
66
- onClick={() => setColorScheme(scheme)}>
67
- {colorSchemeLabels[scheme]}
68
- </button>
69
- ))}
70
- </div>
71
- </div>
51
+ <Drawer.Root position='right'>
52
+ <Drawer.Trigger as={Button} rounded iconOnly className='layout-config-button' aria-label={title} data-scene-id={element.id}>
53
+ <i className='pi pi-cog' aria-hidden='true' />
54
+ </Drawer.Trigger>
55
+ <Drawer.Portal>
56
+ <Drawer.Backdrop />
57
+ <Drawer.Popup>
58
+ <Drawer.Header>
59
+ <Drawer.Title>{title}</Drawer.Title>
60
+ <Drawer.Close aria-label={`Close ${title}`}>
61
+ <i className='pi pi-times' aria-hidden='true' />
62
+ </Drawer.Close>
63
+ </Drawer.Header>
64
+ <Drawer.Content>
65
+ <div className='layout-config-panel'>
66
+ <div>
67
+ <h3 className='layout-config-section-title'>Color scheme</h3>
68
+ <div className='layout-config-options' role='group' aria-label='Color scheme'>
69
+ {colorSchemes.map(scheme => (
70
+ <button
71
+ key={scheme}
72
+ type='button'
73
+ className='layout-config-option'
74
+ aria-pressed={config.colorScheme === scheme}
75
+ onClick={() => setColorScheme(scheme)}>
76
+ {colorSchemeLabels[scheme]}
77
+ </button>
78
+ ))}
79
+ </div>
80
+ </div>
72
81
 
73
- <ThemeSwitcher
74
- element={{ ...element, id: `${element.id}-theme`, properties: { ...element.properties, label: 'Theme' } }}
75
- slots={{}}
76
- />
82
+ <ThemeSwitcher
83
+ element={{ ...element, id: `${element.id}-theme`, properties: { ...element.properties, label: 'Theme' } }}
84
+ slots={{}}
85
+ />
77
86
 
78
- {showsMode && (
79
- <LayoutModeSwitcher element={{ ...element, id: `${element.id}-mode`, properties: { label: 'Menu mode' } }} slots={{}} />
80
- )}
87
+ {showsMode && (
88
+ <LayoutModeSwitcher element={{ ...element, id: `${element.id}-mode`, properties: { label: 'Menu mode' } }} slots={{}} />
89
+ )}
81
90
 
82
- {showsMenuTheme && (
83
- <div>
84
- <h3 className='layout-config-section-title'>Menu theme</h3>
85
- <div className='layout-config-options' role='group' aria-label='Menu theme'>
86
- {menuThemes.map(menuTheme => (
87
- <button
88
- key={menuTheme}
89
- type='button'
90
- className='layout-config-option'
91
- aria-pressed={config.menuTheme === menuTheme}
92
- onClick={() => setMenuTheme(menuTheme)}>
93
- {menuThemeLabels[menuTheme]}
94
- </button>
95
- ))}
96
- </div>
97
- </div>
98
- )}
91
+ {showsMenuTheme && (
92
+ <div>
93
+ <h3 className='layout-config-section-title'>Menu theme</h3>
94
+ <div className='layout-config-options' role='group' aria-label='Menu theme'>
95
+ {menuThemes.map(menuTheme => (
96
+ <button
97
+ key={menuTheme}
98
+ type='button'
99
+ className='layout-config-option'
100
+ aria-pressed={config.menuTheme === menuTheme}
101
+ onClick={() => setMenuTheme(menuTheme)}>
102
+ {menuThemeLabels[menuTheme]}
103
+ </button>
104
+ ))}
105
+ </div>
106
+ </div>
107
+ )}
99
108
 
100
- {slots.content}
101
- </div>
102
- </Sidebar>
103
- </>
109
+ {slots.content}
110
+ </div>
111
+ </Drawer.Content>
112
+ </Drawer.Popup>
113
+ </Drawer.Portal>
114
+ </Drawer.Root>
104
115
  );
105
116
  }
@@ -0,0 +1,27 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * One entry in an overlay menu or a breadcrumb trail.
6
+ *
7
+ * PrimeReact 10 shipped this shape as `primereact/menuitem` and both the user menu and the breadcrumb
8
+ * handed an array of it to a `model` prop. PrimeReact 11 deleted that module along with every
9
+ * `model`-driven menu - the components are compositional now - so the *data* half of the contract no
10
+ * longer has an owner in the library. It belongs to whoever reads the element properties, which is this
11
+ * package: the shell is what turns a template's `{ label, icon, targetScreen }` records into rendered
12
+ * entries, so the type it maps them into lives beside the components that map them rather than being
13
+ * re-declared, slightly differently, in each one.
14
+ *
15
+ * It is deliberately the three fields the shell renders rather than a port of PrimeReact 10's full
16
+ * `MenuItem`. Fields nothing renders are fields nothing keeps honest.
17
+ */
18
+ export interface MenuEntry {
19
+ /** What to show for the entry. */
20
+ label: string;
21
+
22
+ /** The icon font class to draw before the label, or undefined for a label on its own. */
23
+ icon?: string;
24
+
25
+ /** Where activating the entry navigates, or undefined when the entry is the page already shown. */
26
+ url?: string;
27
+ }
package/shell/Sidebar.tsx CHANGED
@@ -30,13 +30,14 @@ export function Sidebar({ element, slots }: RegisteredComponentProps) {
30
30
  {isPinnable && (
31
31
  <Button
32
32
  type='button'
33
- text
33
+ variant='text'
34
34
  rounded
35
- icon={config.isSidebarAnchored ? 'pi pi-lock' : 'pi pi-lock-open'}
35
+ iconOnly
36
36
  aria-label={config.isSidebarAnchored ? 'Unpin the menu' : 'Pin the menu open'}
37
37
  aria-pressed={config.isSidebarAnchored}
38
- onClick={toggleSidebarAnchor}
39
- />
38
+ onClick={toggleSidebarAnchor}>
39
+ <i className={config.isSidebarAnchored ? 'pi pi-lock' : 'pi pi-lock-open'} aria-hidden='true' />
40
+ </Button>
40
41
  )}
41
42
  </div>
42
43
  {slots.content}
package/shell/Topbar.tsx CHANGED
@@ -27,13 +27,14 @@ export function Topbar({ element, slots }: RegisteredComponentProps) {
27
27
  {showsToggle && (
28
28
  <Button
29
29
  type='button'
30
- text
30
+ variant='text'
31
31
  rounded
32
- icon='pi pi-bars'
32
+ iconOnly
33
33
  aria-label={config.isSidebarOpen ? 'Close the menu' : 'Open the menu'}
34
34
  aria-expanded={config.isSidebarOpen}
35
- onClick={toggleSidebar}
36
- />
35
+ onClick={toggleSidebar}>
36
+ <i className='pi pi-bars' aria-hidden='true' />
37
+ </Button>
37
38
  )}
38
39
  {slots.logo}
39
40
  {slots.start}
@@ -1,12 +1,11 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { useRef } from 'react';
5
4
  import { Avatar } from 'primereact/avatar';
6
5
  import { Button } from 'primereact/button';
7
6
  import { Menu as PrimeMenu } from 'primereact/menu';
8
- import { MenuItem as PrimeMenuItem } from 'primereact/menuitem';
9
7
  import { RegisteredComponentProps } from '@cratis/scene.react';
8
+ import { MenuEntry } from './MenuEntry';
10
9
  import { readRecords, readString, recordString } from './elementProperties';
11
10
 
12
11
  /**
@@ -14,16 +13,22 @@ import { readRecords, readString, recordString } from './elementProperties';
14
13
  *
15
14
  * The popup is PrimeReact's `Menu` rather than hand-written markup: an overlay that positions itself,
16
15
  * traps focus and closes on escape is framework behavior, and re-implementing it in a layout package is
17
- * how a shell ends up with its own subtly broken menu. The shell hand-writes CSS only where PrimeReact 10
16
+ * how a shell ends up with its own subtly broken menu. The shell hand-writes CSS only where PrimeReact
18
17
  * has no primitive at all - the app-shell modes - and uses the library everywhere else.
18
+ *
19
+ * PrimeReact 11 replaced the `model` array with composition, so the entries are rendered here rather than
20
+ * handed over as data - and `primereact/menuitem`, the type that array was made of, went with it, which is
21
+ * why they are mapped into this package's own {@link MenuEntry}. The trade is worth taking: `Menu.Trigger`
22
+ * now owns opening the popup and reporting `aria-haspopup`/`aria-expanded` for it, so the ref, the toggle
23
+ * handler and the hand-maintained aria that used to sit here - and could drift out of step with the popup
24
+ * they described - are gone.
19
25
  */
20
26
  export function UserMenu({ element }: RegisteredComponentProps) {
21
- const menu = useRef<PrimeMenu>(null);
22
27
  const name = readString(element, 'name');
23
28
  const role = readString(element, 'role');
24
29
  const initials = readString(element, 'initials', name.slice(0, 1).toUpperCase());
25
30
  const items = readRecords(element, 'items').map(
26
- (item): PrimeMenuItem => ({
31
+ (item): MenuEntry => ({
27
32
  label: recordString(item, 'label'),
28
33
  icon: recordString(item, 'icon') || undefined,
29
34
  url: recordString(item, 'targetScreen') ? `#/${recordString(item, 'targetScreen')}` : undefined,
@@ -32,22 +37,32 @@ export function UserMenu({ element }: RegisteredComponentProps) {
32
37
 
33
38
  return (
34
39
  <div className='layout-user-menu' data-scene-id={element.id}>
35
- <Avatar label={initials} shape='circle' />
40
+ <Avatar.Root shape='circle'>
41
+ <Avatar.Fallback>{initials}</Avatar.Fallback>
42
+ </Avatar.Root>
36
43
  <span className='layout-user-menu-name'>
37
44
  <span>{name}</span>
38
45
  {role && <span className='layout-user-menu-role'>{role}</span>}
39
46
  </span>
40
- <PrimeMenu ref={menu} model={items} popup id={`${element.id}-menu`} />
41
- <Button
42
- type='button'
43
- text
44
- rounded
45
- icon='pi pi-angle-down'
46
- aria-label={`Open the menu for ${name}`}
47
- aria-controls={`${element.id}-menu`}
48
- aria-haspopup
49
- onClick={event => menu.current?.toggle(event)}
50
- />
47
+ <PrimeMenu.Root>
48
+ <PrimeMenu.Trigger as={Button} variant='text' rounded iconOnly aria-label={`Open the menu for ${name}`}>
49
+ <i className='pi pi-angle-down' aria-hidden='true' />
50
+ </PrimeMenu.Trigger>
51
+ <PrimeMenu.Portal>
52
+ <PrimeMenu.Positioner>
53
+ <PrimeMenu.Popup>
54
+ <PrimeMenu.List>
55
+ {items.map(item => (
56
+ <PrimeMenu.Item key={item.label} as='a' href={item.url}>
57
+ {item.icon && <i className={item.icon} aria-hidden='true' />}
58
+ <span>{item.label}</span>
59
+ </PrimeMenu.Item>
60
+ ))}
61
+ </PrimeMenu.List>
62
+ </PrimeMenu.Popup>
63
+ </PrimeMenu.Positioner>
64
+ </PrimeMenu.Portal>
65
+ </PrimeMenu.Root>
51
66
  </div>
52
67
  );
53
68
  }
package/shell/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  export * from './elementProperties';
5
+ export * from './MenuEntry';
5
6
  export * from './ThemeOption';
6
7
  export * from './AppShell';
7
8
  export * from './FullPageShell';
package/shell/layout.css CHANGED
@@ -4,11 +4,11 @@
4
4
  /*
5
5
  * Cratis Scene - default layout package stylesheet.
6
6
  *
7
- * Every mode this shell implements is hand-written here. PrimeReact 10 has no app-shell primitive to
8
- * lean on: its `Sidebar` is an overlay drawer (renamed `Drawer` in v11), not a dockable application
9
- * sidebar, so the docked/rail/reveal/drawer behaviors have to be CSS. PrimeReact 11 introduces a real
10
- * `Sidebar` with `variant`/`collapsible`/`side`/`overlay`/`openOnHover` props and a `Sidebar.Layout`
11
- * registry that would replace most of this file - see the README for what a v11 port collapses.
7
+ * Every mode this shell implements is hand-written here. PrimeReact 11 does ship a real app-shell
8
+ * `Sidebar` - a genuinely new primitive, not v10's overlay drawer, which is now `Drawer` - and adopting
9
+ * it would replace most of this file. That is a deliberate follow-up rather than something the version
10
+ * bump did: rewriting eight layout modes onto a new primitive is a behavior change. See the README for
11
+ * what such a port collapses and what would stay this package's own.
12
12
  *
13
13
  * TOKEN BRIDGE
14
14
  * ------------
@@ -1,20 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import type { Preview } from '@storybook/react';
5
-
6
- // PrimeReact 10 ships its base styles and one theme per file, and the shell's own stylesheet layers on
7
- // top of them. Loading them here rather than from a story keeps every story looking the same and matches
8
- // what a host does once, at its entry point.
9
- import 'primereact/resources/primereact.min.css';
10
- import 'primereact/resources/themes/lara-light-indigo/theme.css';
11
- import 'primeicons/primeicons.css';
12
- import '../shell/layout.css';
13
-
14
- const preview: Preview = {
15
- parameters: {
16
- controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } },
17
- },
18
- };
19
-
20
- export default preview;