@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.
- package/.storybook/preview.tsx +44 -0
- package/README.md +25 -12
- package/dist/cjs/shell/Breadcrumb.js +47 -10
- package/dist/cjs/shell/Breadcrumb.js.map +1 -1
- package/dist/cjs/shell/ConfigPanel.js +111 -82
- package/dist/cjs/shell/ConfigPanel.js.map +1 -1
- package/dist/cjs/shell/Sidebar.js +7 -3
- package/dist/cjs/shell/Sidebar.js.map +1 -1
- package/dist/cjs/shell/Topbar.js +7 -3
- package/dist/cjs/shell/Topbar.js.map +1 -1
- package/dist/cjs/shell/UserMenu.js +48 -21
- package/dist/cjs/shell/UserMenu.js.map +1 -1
- package/dist/esm/for_AppShell/when_rendering_a_gallery_screen.js +2 -1
- package/dist/esm/for_AppShell/when_rendering_a_gallery_screen.js.map +1 -1
- package/dist/esm/shell/Breadcrumb.d.ts.map +1 -1
- package/dist/esm/shell/Breadcrumb.js +49 -12
- package/dist/esm/shell/Breadcrumb.js.map +1 -1
- package/dist/esm/shell/ConfigPanel.d.ts.map +1 -1
- package/dist/esm/shell/ConfigPanel.js +112 -83
- package/dist/esm/shell/ConfigPanel.js.map +1 -1
- package/dist/esm/shell/MenuEntry.d.ts +6 -0
- package/dist/esm/shell/MenuEntry.d.ts.map +1 -0
- package/dist/esm/shell/MenuEntry.js +2 -0
- package/dist/esm/shell/MenuEntry.js.map +1 -0
- package/dist/esm/shell/Sidebar.d.ts.map +1 -1
- package/dist/esm/shell/Sidebar.js +7 -3
- package/dist/esm/shell/Sidebar.js.map +1 -1
- package/dist/esm/shell/Topbar.d.ts.map +1 -1
- package/dist/esm/shell/Topbar.js +7 -3
- package/dist/esm/shell/Topbar.js.map +1 -1
- package/dist/esm/shell/UserMenu.d.ts.map +1 -1
- package/dist/esm/shell/UserMenu.js +48 -21
- package/dist/esm/shell/UserMenu.js.map +1 -1
- package/dist/esm/shell/index.d.ts +1 -0
- package/dist/esm/shell/index.d.ts.map +1 -1
- package/dist/esm/shell/index.js +1 -0
- package/dist/esm/shell/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/for_AppShell/when_rendering_a_gallery_screen.tsx +10 -1
- package/package.json +28 -12
- package/shell/Breadcrumb.tsx +44 -8
- package/shell/ConfigPanel.tsx +72 -61
- package/shell/MenuEntry.ts +27 -0
- package/shell/Sidebar.tsx +5 -4
- package/shell/Topbar.tsx +5 -4
- package/shell/UserMenu.tsx +32 -17
- package/shell/index.ts +1 -0
- package/shell/layout.css +5 -5
- 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(
|
|
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": "
|
|
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": "
|
|
47
|
-
"@cratis/scene.model": "
|
|
48
|
-
"@cratis/scene.react": "
|
|
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": "
|
|
52
|
-
"@cratis/scene.model": "
|
|
53
|
-
"@cratis/scene.react": "
|
|
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": "
|
|
58
|
-
"primereact": "
|
|
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": "^
|
|
65
|
-
"
|
|
66
|
-
"
|
|
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
|
}
|
package/shell/Breadcrumb.tsx
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
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 `
|
|
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
|
|
15
|
-
*
|
|
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
|
|
20
|
-
(item):
|
|
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
|
-
<
|
|
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
|
}
|
package/shell/ConfigPanel.tsx
CHANGED
|
@@ -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 {
|
|
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
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<div className='layout-config-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
<ThemeSwitcher
|
|
83
|
+
element={{ ...element, id: `${element.id}-theme`, properties: { ...element.properties, label: 'Theme' } }}
|
|
84
|
+
slots={{}}
|
|
85
|
+
/>
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
{showsMode && (
|
|
88
|
+
<LayoutModeSwitcher element={{ ...element, id: `${element.id}-mode`, properties: { label: 'Menu mode' } }} slots={{}} />
|
|
89
|
+
)}
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
package/shell/UserMenu.tsx
CHANGED
|
@@ -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
|
|
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):
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
* ------------
|
package/.storybook/preview.ts
DELETED
|
@@ -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;
|