@cratis/scene.blueprint.default 4.39.0 → 4.41.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/ComponentName.ts +27 -0
- package/defaultBlueprint.ts +1 -0
- package/defaultBlueprintComponents.ts +10 -1
- package/dist/cjs/ComponentName.js +9 -0
- package/dist/cjs/ComponentName.js.map +1 -1
- package/dist/cjs/defaultBlueprint.js +18 -1
- package/dist/cjs/defaultBlueprint.js.map +1 -1
- package/dist/cjs/defaultBlueprintComponentMetadata.js +9 -0
- package/dist/cjs/defaultBlueprintComponentMetadata.js.map +1 -1
- package/dist/cjs/defaultBlueprintComponents.js +28 -1
- package/dist/cjs/defaultBlueprintComponents.js.map +1 -1
- package/dist/cjs/foundation/BulkBar.js +62 -0
- package/dist/cjs/foundation/BulkBar.js.map +1 -0
- package/dist/cjs/foundation/DataList.js +436 -0
- package/dist/cjs/foundation/DataList.js.map +1 -0
- package/dist/cjs/foundation/FormScreenBody.js +447 -0
- package/dist/cjs/foundation/FormScreenBody.js.map +1 -0
- package/dist/cjs/foundation/FoundationStates.js +2 -1
- package/dist/cjs/foundation/FoundationStates.js.map +1 -1
- package/dist/cjs/foundation/GlobalSearch.js +54 -0
- package/dist/cjs/foundation/GlobalSearch.js.map +1 -0
- package/dist/cjs/foundation/KanbanBoard.js +222 -0
- package/dist/cjs/foundation/KanbanBoard.js.map +1 -0
- package/dist/cjs/foundation/NotificationsButton.js +45 -0
- package/dist/cjs/foundation/NotificationsButton.js.map +1 -0
- package/dist/cjs/foundation/SubNavigation.js +72 -0
- package/dist/cjs/foundation/SubNavigation.js.map +1 -0
- package/dist/cjs/foundation/TabSet.js +101 -0
- package/dist/cjs/foundation/TabSet.js.map +1 -0
- package/dist/cjs/foundation/WizardProgress.js +185 -0
- package/dist/cjs/foundation/WizardProgress.js.map +1 -0
- package/dist/cjs/foundation/boardModel.js +67 -0
- package/dist/cjs/foundation/boardModel.js.map +1 -0
- package/dist/cjs/foundation/formModel.js +158 -0
- package/dist/cjs/foundation/formModel.js.map +1 -0
- package/dist/cjs/foundation/foundationComponentMetadata.js +54 -0
- package/dist/cjs/foundation/foundationComponentMetadata.js.map +1 -1
- package/dist/cjs/foundation/foundationDescriptors.js +400 -0
- package/dist/cjs/foundation/foundationDescriptors.js.map +1 -1
- package/dist/cjs/foundation/foundationSamples.js +303 -0
- package/dist/cjs/foundation/foundationSamples.js.map +1 -1
- package/dist/cjs/foundation/foundationStrings.js +72 -0
- package/dist/cjs/foundation/foundationStrings.js.map +1 -1
- package/dist/cjs/foundation/listModel.js +200 -0
- package/dist/cjs/foundation/listModel.js.map +1 -0
- package/dist/cjs/foundation/tabModel.js +41 -0
- package/dist/cjs/foundation/tabModel.js.map +1 -0
- package/dist/cjs/foundation/templates/boardScreenTemplate.js +76 -0
- package/dist/cjs/foundation/templates/boardScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/dashboardScreenTemplate.js +128 -0
- package/dist/cjs/foundation/templates/dashboardScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/formScreenTemplate.js +71 -0
- package/dist/cjs/foundation/templates/formScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/index.js +39 -0
- package/dist/cjs/foundation/templates/index.js.map +1 -0
- package/dist/cjs/foundation/templates/listScreenTemplate.js +124 -0
- package/dist/cjs/foundation/templates/listScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js +79 -0
- package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/recordPageTemplate.js +137 -0
- package/dist/cjs/foundation/templates/recordPageTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/templateParts.js +38 -0
- package/dist/cjs/foundation/templates/templateParts.js.map +1 -0
- package/dist/cjs/foundation/templates/wizardScreenTemplate.js +95 -0
- package/dist/cjs/foundation/templates/wizardScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/templates/workspaceScreenTemplate.js +62 -0
- package/dist/cjs/foundation/templates/workspaceScreenTemplate.js.map +1 -0
- package/dist/cjs/foundation/urlState.js +57 -0
- package/dist/cjs/foundation/urlState.js.map +1 -0
- package/dist/cjs/foundation/useCompactWidth.js +23 -0
- package/dist/cjs/foundation/useCompactWidth.js.map +1 -0
- package/dist/cjs/foundation/wizardModel.js +50 -0
- package/dist/cjs/foundation/wizardModel.js.map +1 -0
- package/dist/cjs/gallery/GalleryScreenPreview.js +76 -4
- package/dist/cjs/gallery/GalleryScreenPreview.js.map +1 -1
- package/dist/cjs/gallery/applicationChrome.js +18 -1
- package/dist/cjs/gallery/applicationChrome.js.map +1 -1
- package/dist/cjs/gallery/composeScreen.js +5 -3
- package/dist/cjs/gallery/composeScreen.js.map +1 -1
- package/dist/cjs/gallery/screens.js +83 -2
- package/dist/cjs/gallery/screens.js.map +1 -1
- package/dist/cjs/index.js +96 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/ComponentName.d.ts +10 -1
- package/dist/esm/ComponentName.d.ts.map +1 -1
- package/dist/esm/ComponentName.js +9 -0
- package/dist/esm/ComponentName.js.map +1 -1
- package/dist/esm/defaultBlueprint.d.ts.map +1 -1
- package/dist/esm/defaultBlueprint.js +18 -1
- package/dist/esm/defaultBlueprint.js.map +1 -1
- package/dist/esm/defaultBlueprintComponentMetadata.js +9 -0
- package/dist/esm/defaultBlueprintComponentMetadata.js.map +1 -1
- package/dist/esm/defaultBlueprintComponents.d.ts.map +1 -1
- package/dist/esm/defaultBlueprintComponents.js +28 -1
- package/dist/esm/defaultBlueprintComponents.js.map +1 -1
- package/dist/esm/foundation/BulkBar.d.ts +4 -0
- package/dist/esm/foundation/BulkBar.d.ts.map +1 -0
- package/dist/esm/foundation/BulkBar.js +59 -0
- package/dist/esm/foundation/BulkBar.js.map +1 -0
- package/dist/esm/foundation/DataList.d.ts +3 -0
- package/dist/esm/foundation/DataList.d.ts.map +1 -0
- package/dist/esm/foundation/DataList.js +434 -0
- package/dist/esm/foundation/DataList.js.map +1 -0
- package/dist/esm/foundation/FormScreenBody.d.ts +3 -0
- package/dist/esm/foundation/FormScreenBody.d.ts.map +1 -0
- package/dist/esm/foundation/FormScreenBody.js +445 -0
- package/dist/esm/foundation/FormScreenBody.js.map +1 -0
- package/dist/esm/foundation/FoundationStates.js +2 -1
- package/dist/esm/foundation/FoundationStates.js.map +1 -1
- package/dist/esm/foundation/GlobalSearch.d.ts +3 -0
- package/dist/esm/foundation/GlobalSearch.d.ts.map +1 -0
- package/dist/esm/foundation/GlobalSearch.js +52 -0
- package/dist/esm/foundation/GlobalSearch.js.map +1 -0
- package/dist/esm/foundation/KanbanBoard.d.ts +3 -0
- package/dist/esm/foundation/KanbanBoard.d.ts.map +1 -0
- package/dist/esm/foundation/KanbanBoard.js +220 -0
- package/dist/esm/foundation/KanbanBoard.js.map +1 -0
- package/dist/esm/foundation/NotificationsButton.d.ts +3 -0
- package/dist/esm/foundation/NotificationsButton.d.ts.map +1 -0
- package/dist/esm/foundation/NotificationsButton.js +43 -0
- package/dist/esm/foundation/NotificationsButton.js.map +1 -0
- package/dist/esm/foundation/SubNavigation.d.ts +3 -0
- package/dist/esm/foundation/SubNavigation.d.ts.map +1 -0
- package/dist/esm/foundation/SubNavigation.js +70 -0
- package/dist/esm/foundation/SubNavigation.js.map +1 -0
- package/dist/esm/foundation/TabSet.d.ts +3 -0
- package/dist/esm/foundation/TabSet.d.ts.map +1 -0
- package/dist/esm/foundation/TabSet.js +99 -0
- package/dist/esm/foundation/TabSet.js.map +1 -0
- package/dist/esm/foundation/WizardProgress.d.ts +3 -0
- package/dist/esm/foundation/WizardProgress.d.ts.map +1 -0
- package/dist/esm/foundation/WizardProgress.js +183 -0
- package/dist/esm/foundation/WizardProgress.js.map +1 -0
- package/dist/esm/foundation/boardModel.d.ts +22 -0
- package/dist/esm/foundation/boardModel.d.ts.map +1 -0
- package/dist/esm/foundation/boardModel.js +61 -0
- package/dist/esm/foundation/boardModel.js.map +1 -0
- package/dist/esm/foundation/formModel.d.ts +48 -0
- package/dist/esm/foundation/formModel.d.ts.map +1 -0
- package/dist/esm/foundation/formModel.js +147 -0
- package/dist/esm/foundation/formModel.js.map +1 -0
- package/dist/esm/foundation/foundationComponentMetadata.d.ts.map +1 -1
- package/dist/esm/foundation/foundationComponentMetadata.js +54 -0
- package/dist/esm/foundation/foundationComponentMetadata.js.map +1 -1
- package/dist/esm/foundation/foundationDescriptors.d.ts.map +1 -1
- package/dist/esm/foundation/foundationDescriptors.js +400 -0
- package/dist/esm/foundation/foundationDescriptors.js.map +1 -1
- package/dist/esm/foundation/foundationSamples.d.ts.map +1 -1
- package/dist/esm/foundation/foundationSamples.js +303 -0
- package/dist/esm/foundation/foundationSamples.js.map +1 -1
- package/dist/esm/foundation/foundationStrings.d.ts +72 -0
- package/dist/esm/foundation/foundationStrings.d.ts.map +1 -1
- package/dist/esm/foundation/foundationStrings.js +72 -0
- package/dist/esm/foundation/foundationStrings.js.map +1 -1
- package/dist/esm/foundation/index.d.ts +17 -0
- package/dist/esm/foundation/index.d.ts.map +1 -1
- package/dist/esm/foundation/index.js +17 -0
- package/dist/esm/foundation/index.js.map +1 -1
- package/dist/esm/foundation/listModel.d.ts +49 -0
- package/dist/esm/foundation/listModel.d.ts.map +1 -0
- package/dist/esm/foundation/listModel.js +183 -0
- package/dist/esm/foundation/listModel.js.map +1 -0
- package/dist/esm/foundation/tabModel.d.ts +10 -0
- package/dist/esm/foundation/tabModel.d.ts.map +1 -0
- package/dist/esm/foundation/tabModel.js +37 -0
- package/dist/esm/foundation/tabModel.js.map +1 -0
- package/dist/esm/foundation/templates/boardScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/boardScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/boardScreenTemplate.js +74 -0
- package/dist/esm/foundation/templates/boardScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/dashboardScreenTemplate.js +126 -0
- package/dist/esm/foundation/templates/dashboardScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/formScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/formScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/formScreenTemplate.js +69 -0
- package/dist/esm/foundation/templates/formScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/index.d.ts +11 -0
- package/dist/esm/foundation/templates/index.d.ts.map +1 -0
- package/dist/esm/foundation/templates/index.js +25 -0
- package/dist/esm/foundation/templates/index.js.map +1 -0
- package/dist/esm/foundation/templates/listScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/listScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/listScreenTemplate.js +122 -0
- package/dist/esm/foundation/templates/listScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/masterDetailScreenTemplate.js +77 -0
- package/dist/esm/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/recordPageTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/recordPageTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/recordPageTemplate.js +135 -0
- package/dist/esm/foundation/templates/recordPageTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/templateParts.d.ts +6 -0
- package/dist/esm/foundation/templates/templateParts.d.ts.map +1 -0
- package/dist/esm/foundation/templates/templateParts.js +33 -0
- package/dist/esm/foundation/templates/templateParts.js.map +1 -0
- package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/wizardScreenTemplate.js +93 -0
- package/dist/esm/foundation/templates/wizardScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts +3 -0
- package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts.map +1 -0
- package/dist/esm/foundation/templates/workspaceScreenTemplate.js +60 -0
- package/dist/esm/foundation/templates/workspaceScreenTemplate.js.map +1 -0
- package/dist/esm/foundation/urlState.d.ts +4 -0
- package/dist/esm/foundation/urlState.d.ts.map +1 -0
- package/dist/esm/foundation/urlState.js +53 -0
- package/dist/esm/foundation/urlState.js.map +1 -0
- package/dist/esm/foundation/useCompactWidth.d.ts +3 -0
- package/dist/esm/foundation/useCompactWidth.d.ts.map +1 -0
- package/dist/esm/foundation/useCompactWidth.js +20 -0
- package/dist/esm/foundation/useCompactWidth.js.map +1 -0
- package/dist/esm/foundation/wizardModel.d.ts +16 -0
- package/dist/esm/foundation/wizardModel.d.ts.map +1 -0
- package/dist/esm/foundation/wizardModel.js +43 -0
- package/dist/esm/foundation/wizardModel.js.map +1 -0
- package/dist/esm/gallery/GalleryScreenPreview.d.ts +13 -2
- package/dist/esm/gallery/GalleryScreenPreview.d.ts.map +1 -1
- package/dist/esm/gallery/GalleryScreenPreview.js +75 -5
- package/dist/esm/gallery/GalleryScreenPreview.js.map +1 -1
- package/dist/esm/gallery/applicationChrome.d.ts.map +1 -1
- package/dist/esm/gallery/applicationChrome.js +18 -1
- package/dist/esm/gallery/applicationChrome.js.map +1 -1
- package/dist/esm/gallery/composeScreen.d.ts.map +1 -1
- package/dist/esm/gallery/composeScreen.js +5 -3
- package/dist/esm/gallery/composeScreen.js.map +1 -1
- package/dist/esm/gallery/screens.d.ts.map +1 -1
- package/dist/esm/gallery/screens.js +83 -2
- package/dist/esm/gallery/screens.js.map +1 -1
- package/dist/esm/index.js +27 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/foundation/BulkBar.tsx +38 -0
- package/foundation/DataList.tsx +208 -0
- package/foundation/FormScreenBody.tsx +196 -0
- package/foundation/FoundationStates.tsx +1 -1
- package/foundation/GlobalSearch.tsx +33 -0
- package/foundation/KanbanBoard.tsx +97 -0
- package/foundation/NotificationsButton.tsx +28 -0
- package/foundation/SubNavigation.tsx +49 -0
- package/foundation/TabSet.tsx +65 -0
- package/foundation/WizardProgress.tsx +109 -0
- package/foundation/boardModel.ts +64 -0
- package/foundation/formModel.ts +146 -0
- package/foundation/foundationComponentMetadata.ts +9 -0
- package/foundation/foundationDescriptors.ts +195 -0
- package/foundation/foundationSamples.ts +56 -0
- package/foundation/foundationStrings.ts +72 -0
- package/foundation/index.ts +17 -0
- package/foundation/listModel.ts +190 -0
- package/foundation/tabModel.ts +41 -0
- package/foundation/templates/boardScreenTemplate.ts +28 -0
- package/foundation/templates/dashboardScreenTemplate.ts +37 -0
- package/foundation/templates/formScreenTemplate.ts +28 -0
- package/foundation/templates/index.ts +26 -0
- package/foundation/templates/listScreenTemplate.ts +37 -0
- package/foundation/templates/masterDetailScreenTemplate.ts +28 -0
- package/foundation/templates/recordPageTemplate.ts +36 -0
- package/foundation/templates/templateParts.ts +30 -0
- package/foundation/templates/wizardScreenTemplate.ts +33 -0
- package/foundation/templates/workspaceScreenTemplate.ts +27 -0
- package/foundation/urlState.ts +52 -0
- package/foundation/useCompactWidth.ts +23 -0
- package/foundation/wizardModel.ts +54 -0
- package/gallery/GalleryScreenPreview.tsx +64 -5
- package/gallery/applicationChrome.ts +6 -1
- package/gallery/composeScreen.ts +4 -2
- package/gallery/screens.ts +16 -1
- package/package.json +10 -8
- package/sampleData/foundation-en.json +512 -0
- package/shell/layout.css +513 -0
- package/thumbnails/BoardScreen.svg +15 -0
- package/thumbnails/DashboardScreen.svg +18 -0
- package/thumbnails/FormScreen.svg +14 -0
- package/thumbnails/ListScreen.svg +16 -0
- package/thumbnails/MasterDetailScreen.svg +22 -0
- package/thumbnails/RecordPage.svg +17 -0
- package/thumbnails/WizardScreen.svg +16 -0
- package/thumbnails/WorkspaceScreen.svg +12 -0
|
@@ -0,0 +1,37 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The dashboard: key figures across the top, charts and tables below, an aside for what needs attention. Every region is its own component with its
|
|
10
|
+
* own five states, so one failed query shows the other regions - the page never fails as a whole.
|
|
11
|
+
*/
|
|
12
|
+
export const dashboardScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'DashboardScreen',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'header' }, { name: 'kpis' }, { name: 'charts' }, { name: 'tables' }, { name: 'aside' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: column([slotLeaf('header'), slotLeaf('kpis'), grid([slotLeaf('charts', { span: 2 }), slotLeaf('aside')], 3, 16), slotLeaf('tables')], 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('kpis'), slotLeaf('charts'), slotLeaf('tables'), slotLeaf('aside')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
header: [component('dashboard-header', 'pageHeader', { title: 'Sales overview', subtitle: 'This quarter compared to last' })],
|
|
22
|
+
kpis: [
|
|
23
|
+
component('kpi-pipeline', 'statCard', { label: 'Open pipeline', value: fromQuery('Kpis', '0.value'), change: fromQuery('Kpis', '0.change'), format: 'currency', currency: 'USD', comparison: 'last quarter' }),
|
|
24
|
+
component('kpi-won', 'statCard', { label: 'Deals won', value: fromQuery('Kpis', '1.value'), change: fromQuery('Kpis', '1.change'), comparison: 'last quarter' }),
|
|
25
|
+
component('kpi-response', 'statCard', { label: 'Response time (hours)', value: fromQuery('Kpis', '2.value'), change: fromQuery('Kpis', '2.change'), goodDirection: 'down', comparison: 'last quarter' }),
|
|
26
|
+
],
|
|
27
|
+
charts: [
|
|
28
|
+
component('chart-revenue', 'chartWrapper', { title: 'Revenue by quarter', kind: 'line', categories: fromQuery('RevenueChart', '0.categories'), series: fromQuery('RevenueChart', '0.series') }),
|
|
29
|
+
component('chart-stages', 'chartWrapper', { title: 'Deals by stage', kind: 'funnel', categories: fromQuery('StageChart', '0.categories'), series: fromQuery('StageChart', '0.series') }),
|
|
30
|
+
],
|
|
31
|
+
tables: [component('dashboard-deals', 'dataList', { label: 'Closing soon', rows: fromQuery('Deals'), columns: fromQuery('DealColumns'), titleField: 'name', pageSize: 5 })],
|
|
32
|
+
aside: [component('dashboard-activity', 'activityFeed', { label: 'Recent activity', entries: fromQuery('Activity') })],
|
|
33
|
+
},
|
|
34
|
+
displayName: 'Dashboard',
|
|
35
|
+
description: 'Key figures, charts and tables, each region with its own loading, empty and error states.',
|
|
36
|
+
metadata: { ...foundationTemplateProvenance, type: 'Dashboard', category: 'Foundation / Dashboard', thumbnail: thumbnailOf('DashboardScreen') },
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The form: titled sections of fields in a one-to-three column grid, validated, with Save, Save and new and Cancel. One template serves create, edit, read-only
|
|
10
|
+
* and settings (a save for each section) by its mode, and warns before unsaved changes are lost.
|
|
11
|
+
*/
|
|
12
|
+
export const formScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'FormScreen',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'header' }, { name: 'body' }, { name: 'actions' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: column([slotLeaf('header'), slotLeaf('body'), slotLeaf('actions')], 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('body'), slotLeaf('actions')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
header: [component('form-header', 'pageHeader', { title: 'New account', subtitle: 'Fields marked * are required' })],
|
|
22
|
+
body: [component('form-body', 'formScreenBody', { mode: 'create', sections: fromQuery('RecordSections'), values: fromQuery('RecordValues', '0'), errors: fromQuery('FormErrors', '0') })],
|
|
23
|
+
actions: [],
|
|
24
|
+
},
|
|
25
|
+
displayName: 'Form',
|
|
26
|
+
description: 'Sections of validated fields with Save, Save and new, Cancel and an unsaved-changes guard.',
|
|
27
|
+
metadata: { ...foundationTemplateProvenance, type: 'Form', category: 'Foundation / Form', thumbnail: thumbnailOf('FormScreen') },
|
|
28
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { boardScreenTemplate } from './boardScreenTemplate';
|
|
5
|
+
import { dashboardScreenTemplate } from './dashboardScreenTemplate';
|
|
6
|
+
import { formScreenTemplate } from './formScreenTemplate';
|
|
7
|
+
import { listScreenTemplate } from './listScreenTemplate';
|
|
8
|
+
import { masterDetailScreenTemplate } from './masterDetailScreenTemplate';
|
|
9
|
+
import { recordPageTemplate } from './recordPageTemplate';
|
|
10
|
+
import { wizardScreenTemplate } from './wizardScreenTemplate';
|
|
11
|
+
import { workspaceScreenTemplate } from './workspaceScreenTemplate';
|
|
12
|
+
|
|
13
|
+
export * from './templateParts';
|
|
14
|
+
export * from './boardScreenTemplate';
|
|
15
|
+
export * from './dashboardScreenTemplate';
|
|
16
|
+
export * from './formScreenTemplate';
|
|
17
|
+
export * from './listScreenTemplate';
|
|
18
|
+
export * from './masterDetailScreenTemplate';
|
|
19
|
+
export * from './recordPageTemplate';
|
|
20
|
+
export * from './wizardScreenTemplate';
|
|
21
|
+
export * from './workspaceScreenTemplate';
|
|
22
|
+
|
|
23
|
+
/** The foundation screen templates, in the order the manifest lists them. The workspace comes first: it is the container the master/detail nests in. */
|
|
24
|
+
export const foundationScreenTemplates = [
|
|
25
|
+
workspaceScreenTemplate, dashboardScreenTemplate, listScreenTemplate, boardScreenTemplate, masterDetailScreenTemplate, recordPageTemplate, formScreenTemplate, wizardScreenTemplate,
|
|
26
|
+
];
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The collection screen: search, filters, a list that is a table, grouped, or cards, a bar that appears when rows are selected, and a footer for paging.
|
|
10
|
+
* Search, sort, page and selection live in the address, so a link shares the list as it is. On a phone the table becomes cards.
|
|
11
|
+
*/
|
|
12
|
+
export const listScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'ListScreen',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'header' }, { name: 'toolbar' }, { name: 'filters' }, { name: 'list' }, { name: 'bulkBar' }, { name: 'footer' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: column([slotLeaf('header'), row([slotLeaf('toolbar')], 8), slotLeaf('filters'), slotLeaf('bulkBar'), slotLeaf('list'), slotLeaf('footer')], 12),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('toolbar'), slotLeaf('filters'), slotLeaf('bulkBar'), slotLeaf('list'), slotLeaf('footer')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
header: [component('list-header', 'pageHeader', { title: 'Deals', subtitle: 'Every open and closed deal' })],
|
|
22
|
+
toolbar: [
|
|
23
|
+
component('list-search', 'globalSearch', { placeholder: 'Search deals', label: 'Search deals', urlKey: 'dealsq' }),
|
|
24
|
+
component('list-views', 'savedViewsMenu', { views: fromQuery('DealViews'), savePermission: 'enabled', managePermission: 'enabled' }),
|
|
25
|
+
component('list-columns', 'columnChooser', { columns: fromQuery('DealColumnChoices') }),
|
|
26
|
+
],
|
|
27
|
+
filters: [component('list-filter', 'filterBuilder', { fields: fromQuery('DealFilterFields') })],
|
|
28
|
+
list: [component('list-records', 'dataList', {
|
|
29
|
+
label: 'Deals', rows: fromQuery('Deals'), columns: fromQuery('DealColumns'), titleField: 'name', selectionMode: 'multiple', pageSize: 10, rowActions: ['Open', 'Archive'], urlKey: 'deals',
|
|
30
|
+
})],
|
|
31
|
+
bulkBar: [component('list-bulk', 'bulkBar', { selection: fromQuery('DealSelection'), actions: ['Assign', 'Archive'] })],
|
|
32
|
+
footer: [],
|
|
33
|
+
},
|
|
34
|
+
displayName: 'List',
|
|
35
|
+
description: 'A searchable, filterable collection that is a table, grouped or cards, with a bulk bar for selected rows.',
|
|
36
|
+
metadata: { ...foundationTemplateProvenance, type: 'List', category: 'Foundation / List', thumbnail: thumbnailOf('ListScreen') },
|
|
37
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The master/detail: a list beside the selected record. The selection lives in the address, so a link opens a record and the back button returns to the
|
|
10
|
+
* list; on a phone the two become a navigation stack - the list, then the record with a way back. It lives in a module workspace's `moduleContent`.
|
|
11
|
+
*/
|
|
12
|
+
export const masterDetailScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'MasterDetailScreen',
|
|
14
|
+
fitsSlot: 'WorkspaceScreen.moduleContent',
|
|
15
|
+
slots: [{ name: 'list' }, { name: 'details' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: grid([slotLeaf('list'), slotLeaf('details', { span: 2 })], 3, 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('list'), slotLeaf('details')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
list: [component('md-list', 'dataList', { label: 'Accounts', rows: fromQuery('Deals'), columns: fromQuery('DealColumns'), titleField: 'name', selectionMode: 'single', urlKey: 'md' })],
|
|
22
|
+
details: [component('md-detail', 'tabSet', { tabs: fromQuery('RecordTabs'), urlKey: 'mdtab', label: 'Account' })],
|
|
23
|
+
},
|
|
24
|
+
displayName: 'Master/detail with address selection',
|
|
25
|
+
description: 'A list beside the selected record that stacks into a navigation stack on a phone.',
|
|
26
|
+
outlets: [{ name: 'master-detail.detail', description: 'The selected record.', accepts: ['Detail'] }],
|
|
27
|
+
metadata: { ...foundationTemplateProvenance, type: 'Workspace', category: 'Foundation / Workspace', thumbnail: thumbnailOf('MasterDetailScreen') },
|
|
28
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The record page: a header, the key facts, then tabs (overview, related, files, history) with the record's main content, its related records and its
|
|
10
|
+
* activity, and an aside. The tabs are configurable by changing one property.
|
|
11
|
+
*/
|
|
12
|
+
export const recordPageTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'RecordPage',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'header' }, { name: 'summary' }, { name: 'tabs' }, { name: 'main' }, { name: 'related' }, { name: 'activity' }, { name: 'aside' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: column([slotLeaf('header'), slotLeaf('summary'), grid([column([slotLeaf('tabs'), slotLeaf('main'), slotLeaf('related'), slotLeaf('activity')], 16, { span: 2 }), slotLeaf('aside')], 3, 16)], 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('summary'), slotLeaf('tabs'), slotLeaf('main'), slotLeaf('related'), slotLeaf('activity'), slotLeaf('aside')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
header: [component('record-header', 'pageHeader', { title: fromQuery('RecordValues', '0.name'), subtitle: 'Account' })],
|
|
22
|
+
summary: [
|
|
23
|
+
component('record-value', 'statCard', { label: 'Lifetime value', value: fromQuery('Kpis', '0.value'), format: 'currency', currency: 'USD' }),
|
|
24
|
+
component('record-status', 'statusChip', { status: fromQuery('Deals', '0.status'), tone: 'info' }),
|
|
25
|
+
component('record-people', 'avatarStack', { people: fromQuery('People') }),
|
|
26
|
+
],
|
|
27
|
+
tabs: [component('record-tabs', 'tabSet', { tabs: fromQuery('RecordTabs'), urlKey: 'tab', label: 'Record sections' })],
|
|
28
|
+
main: [component('record-form', 'formScreenBody', { mode: 'readOnly', sections: fromQuery('RecordSections'), values: fromQuery('RecordValues', '0') })],
|
|
29
|
+
related: [component('record-related', 'dataList', { label: 'Related deals', rows: fromQuery('Deals'), columns: fromQuery('DealColumns'), titleField: 'name', pageSize: 5 })],
|
|
30
|
+
activity: [component('record-activity', 'activityFeed', { label: 'History', entries: fromQuery('Activity') })],
|
|
31
|
+
aside: [component('record-checklist', 'checklistSection', { sections: fromQuery('Checklist') })],
|
|
32
|
+
},
|
|
33
|
+
displayName: 'Record page',
|
|
34
|
+
description: 'One record: its key facts, tabbed sections, related records and history.',
|
|
35
|
+
metadata: { ...foundationTemplateProvenance, type: 'Detail', category: 'Foundation / Detail', thumbnail: thumbnailOf('RecordPage') },
|
|
36
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { BindingExpression, BindingSourceKind, ExternalComponent, SceneElement, TemplateMetadata } from '@cratis/scene.model';
|
|
5
|
+
import { externalComponent } from '../../gallery/elements';
|
|
6
|
+
import { defaultBlueprintTemplateProvenance } from '../../templateProvenance';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The provenance every foundation template carries. The foundation components draw themselves with no component library, so unlike the earlier
|
|
10
|
+
* gallery templates these require no PrimeReact or Cratis.Components: a host with only this blueprint renders them.
|
|
11
|
+
*/
|
|
12
|
+
export const foundationTemplateProvenance: Pick<TemplateMetadata, 'compatibility' | 'attribution' | 'license' | 'licenseUrl'> = {
|
|
13
|
+
compatibility: { scene: defaultBlueprintTemplateProvenance.compatibility!.scene },
|
|
14
|
+
attribution: { author: 'Cratis', url: 'https://github.com/Cratis/Scene/tree/main/Source/JavaScript/blueprint.default' },
|
|
15
|
+
license: 'MIT',
|
|
16
|
+
licenseUrl: 'https://github.com/Cratis/Scene/blob/main/LICENSE',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** The package-relative thumbnail path of a foundation template. */
|
|
20
|
+
export const thumbnailOf = (templateName: string): string => `thumbnails/${templateName}.svg`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A property taken from a query result - never a value. A template declares *where* its data comes from and the sample-data pack, or the real
|
|
24
|
+
* query, supplies the rows (spec SD1); `path` reaches into the result (`0.value` is the first row's `value`, an empty path is the whole result).
|
|
25
|
+
*/
|
|
26
|
+
export const fromQuery = (query: string, path = ''): BindingExpression => ({ kind: BindingSourceKind.QueryResult, query, path });
|
|
27
|
+
|
|
28
|
+
/** A component of this blueprint, by its bare name, with its properties and slot content. */
|
|
29
|
+
export const component = (id: string, componentName: string, properties: Record<string, unknown> = {}, slots: Record<string, SceneElement[]> = {}): ExternalComponent =>
|
|
30
|
+
externalComponent(id, componentName, properties, slots);
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The wizard: linear steps with a progress list, Back and Next, and a save-draft. The last step is the review. Where the person has got to survives a refresh,
|
|
10
|
+
* and a refresh can never land past a step that was not done. The content of each step is the slot named by its id.
|
|
11
|
+
*/
|
|
12
|
+
export const wizardScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'WizardScreen',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'header' }, { name: 'progress' }, { name: 'step' }, { name: 'actions' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: column([slotLeaf('header'), slotLeaf('progress'), slotLeaf('step'), slotLeaf('actions')], 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('progress'), slotLeaf('step'), slotLeaf('actions')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
header: [component('wizard-header', 'pageHeader', { title: 'New account', subtitle: 'Three short steps' })],
|
|
22
|
+
progress: [component('wizard-progress', 'wizardProgress', { steps: fromQuery('WizardSteps'), urlKey: 'step' }, {
|
|
23
|
+
details: [component('wizard-details', 'formScreenBody', { mode: 'create', sections: fromQuery('RecordSections'), values: fromQuery('RecordValues', '0') })],
|
|
24
|
+
people: [component('wizard-people', 'avatarStack', { people: fromQuery('People') })],
|
|
25
|
+
review: [component('wizard-review', 'formScreenBody', { mode: 'readOnly', sections: fromQuery('RecordSections'), values: fromQuery('RecordValues', '0') })],
|
|
26
|
+
})],
|
|
27
|
+
step: [],
|
|
28
|
+
actions: [],
|
|
29
|
+
},
|
|
30
|
+
displayName: 'Wizard',
|
|
31
|
+
description: 'Linear steps with progress, back and next, a saved draft and a review step.',
|
|
32
|
+
metadata: { ...foundationTemplateProvenance, type: 'Wizard', category: 'Foundation / Wizard', thumbnail: thumbnailOf('WizardScreen') },
|
|
33
|
+
};
|
|
@@ -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
|
+
import { ScreenTemplate, WidthSizeClass } from '@cratis/scene.model';
|
|
5
|
+
import { column, grid, row, slotLeaf } from '../../layouts/flowBuilders';
|
|
6
|
+
import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A module's own workspace: its sections beside the content of the section in force. The sections are the module's sub-navigation (a left rail that
|
|
10
|
+
* becomes tabs on a phone); the section in force is kept in the address, so a deep link or a refresh restores it. Feature screens go in `moduleContent`.
|
|
11
|
+
*/
|
|
12
|
+
export const workspaceScreenTemplate: ScreenTemplate = {
|
|
13
|
+
name: 'WorkspaceScreen',
|
|
14
|
+
fitsSlot: 'content',
|
|
15
|
+
slots: [{ name: 'moduleNav' }, { name: 'moduleContent' }],
|
|
16
|
+
arrangement: {
|
|
17
|
+
root: grid([slotLeaf('moduleNav'), slotLeaf('moduleContent', { span: 3 })], 4, 16),
|
|
18
|
+
overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('moduleNav'), slotLeaf('moduleContent')], 16) }],
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
moduleNav: [component('workspace-sections', 'subNavigation', { items: fromQuery('ModuleSections'), orientation: 'vertical', urlKey: 'section', label: 'Module sections' })],
|
|
22
|
+
moduleContent: [],
|
|
23
|
+
},
|
|
24
|
+
displayName: 'Module workspace',
|
|
25
|
+
description: 'A module\'s sections beside the content of the section that is open.',
|
|
26
|
+
metadata: { ...foundationTemplateProvenance, type: 'Workspace', category: 'Foundation / Workspace', thumbnail: thumbnailOf('WorkspaceScreen') },
|
|
27
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
/** Reads one query-string parameter of the current address, or undefined when it is absent or there is no address (server rendering). */
|
|
7
|
+
export function readUrlParameter(name: string): string | undefined {
|
|
8
|
+
if (typeof window === 'undefined') return undefined;
|
|
9
|
+
const value = new URLSearchParams(window.location.search).get(name);
|
|
10
|
+
return value === null ? undefined : value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Writes one query-string parameter to the address without adding a history entry; an undefined or empty value removes it. */
|
|
14
|
+
export function writeUrlParameter(name: string, value: string | undefined): void {
|
|
15
|
+
if (typeof window === 'undefined') return;
|
|
16
|
+
const parameters = new URLSearchParams(window.location.search);
|
|
17
|
+
if (value === undefined || value === '') parameters.delete(name);
|
|
18
|
+
else parameters.set(name, value);
|
|
19
|
+
|
|
20
|
+
const query = parameters.toString();
|
|
21
|
+
window.history.replaceState(window.history.state, '', `${window.location.pathname}${query.length > 0 ? `?${query}` : ''}${window.location.hash}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* State that lives in the address bar: a list's search, sort, page and filters, a master/detail's selection, a module's active section.
|
|
26
|
+
*
|
|
27
|
+
* It starts from the address when the address has a value (so a shared link, a refresh and the back button restore the screen as it was),
|
|
28
|
+
* otherwise from the fallback the template supplies. Changing it rewrites the address in place and does not add a history entry per keystroke.
|
|
29
|
+
* Several components on one screen use distinct `key`s; a template names them (`q`, `sort`, `page`, `selected`, `section`). Without a
|
|
30
|
+
* `urlKey` the state is plain component state, which is what a gallery card and a design surface want.
|
|
31
|
+
*
|
|
32
|
+
* @param key The query-string parameter, or undefined to keep the state local.
|
|
33
|
+
* @param fallback The value used when the address has none.
|
|
34
|
+
* @returns The value and its setter.
|
|
35
|
+
*/
|
|
36
|
+
export function useUrlState(key: string | undefined, fallback: string | undefined): [string | undefined, (value: string | undefined) => void] {
|
|
37
|
+
const [value, setValue] = useState<string | undefined>(() => (key === undefined ? fallback : readUrlParameter(key) ?? fallback));
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (key === undefined) return;
|
|
41
|
+
const restore = () => setValue(readUrlParameter(key) ?? fallback);
|
|
42
|
+
window.addEventListener('popstate', restore);
|
|
43
|
+
return () => window.removeEventListener('popstate', restore);
|
|
44
|
+
}, [key, fallback]);
|
|
45
|
+
|
|
46
|
+
const update = useCallback((next: string | undefined) => {
|
|
47
|
+
setValue(next);
|
|
48
|
+
if (key !== undefined) writeUrlParameter(key, next);
|
|
49
|
+
}, [key]);
|
|
50
|
+
|
|
51
|
+
return [value, update];
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { useEffect, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
/** The width below which a screen is a phone's: the collapse rules of Part B 1.3 (list to cards, master-detail to stack) apply under it. */
|
|
7
|
+
export const compactQuery = '(max-width: 40rem)';
|
|
8
|
+
|
|
9
|
+
/** Whether the viewport is phone-wide, kept up to date. Without `matchMedia` (a test environment, server rendering) it is false, so the full layout renders. */
|
|
10
|
+
export function useCompactWidth(): boolean {
|
|
11
|
+
const [compact, setCompact] = useState(() => typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(compactQuery).matches);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
|
15
|
+
const query = window.matchMedia(compactQuery);
|
|
16
|
+
const update = () => setCompact(query.matches);
|
|
17
|
+
update();
|
|
18
|
+
query.addEventListener('change', update);
|
|
19
|
+
return () => query.removeEventListener('change', update);
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
return compact;
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
/** One step of a wizard. */
|
|
5
|
+
export interface WizardStep {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
/** Whether the step's own fields are valid; a step that is not valid blocks Next. Defaults to true. */
|
|
9
|
+
valid: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Where a step stands for the person going through the wizard. */
|
|
13
|
+
export enum StepStatus {
|
|
14
|
+
Completed = 'completed',
|
|
15
|
+
Current = 'current',
|
|
16
|
+
Upcoming = 'upcoming',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Reads steps, dropping those without an id and repeated ids. */
|
|
20
|
+
export function readWizardSteps(value: unknown): WizardStep[] {
|
|
21
|
+
if (!Array.isArray(value)) return [];
|
|
22
|
+
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
return value.flatMap((entry): WizardStep[] => {
|
|
25
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) return [];
|
|
26
|
+
const { id, label, valid } = entry as Record<string, unknown>;
|
|
27
|
+
if (typeof id !== 'string' || id.length === 0 || seen.has(id)) return [];
|
|
28
|
+
seen.add(id);
|
|
29
|
+
return [{ id, label: typeof label === 'string' && label.length > 0 ? label : id, valid: valid !== false }];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The step to show: the requested one when it exists and every step before it is completed, else the furthest step that is reachable. A refresh can never land past a step that was not done. */
|
|
34
|
+
export function reachableStep(steps: WizardStep[], completed: ReadonlySet<string>, requested: string | undefined): string | undefined {
|
|
35
|
+
const wanted = steps.findIndex(step => step.id === requested);
|
|
36
|
+
let furthest = 0;
|
|
37
|
+
while (furthest < steps.length - 1 && completed.has(steps[furthest].id)) furthest++;
|
|
38
|
+
return steps[wanted >= 0 && wanted <= furthest ? wanted : furthest]?.id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The status of a step: the current one, a completed one (revisitable), or upcoming. */
|
|
42
|
+
export function statusOf(completed: ReadonlySet<string>, current: string | undefined, id: string): StepStatus {
|
|
43
|
+
if (id === current) return StepStatus.Current;
|
|
44
|
+
return completed.has(id) ? StepStatus.Completed : StepStatus.Upcoming;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Whether a step may be opened from the progress indicator: a completed one, the current one, or the next one when the current is done. */
|
|
48
|
+
export function canOpen(steps: WizardStep[], completed: ReadonlySet<string>, id: string): boolean {
|
|
49
|
+
const index = steps.findIndex(step => step.id === id);
|
|
50
|
+
return index === 0 || steps.slice(0, index).every(step => completed.has(step.id));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The step before or after another, or undefined at either end. */
|
|
54
|
+
export const neighbour = (steps: WizardStep[], id: string | undefined, direction: -1 | 1): WizardStep | undefined => steps[steps.findIndex(step => step.id === id) + direction];
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
|
-
import { Theme } from '@cratis/scene.model';
|
|
5
|
+
import { ExternalComponent, SampleDataPack, SceneElement, Theme } from '@cratis/scene.model';
|
|
6
6
|
import { ComponentRegistry, SceneElementView, coreComponents } from '@cratis/scene.react';
|
|
7
7
|
import { LayoutConfigProvider, LayoutConfigState } from '../configuration';
|
|
8
|
+
import { ComponentName } from '../ComponentName';
|
|
8
9
|
import { defaultBlueprintComponents } from '../defaultBlueprintComponents';
|
|
10
|
+
import { ComponentState, foundationSamples } from '../foundation';
|
|
11
|
+
import { defaultBlueprintName } from '../packageName';
|
|
9
12
|
import { LayoutThemeProvider, defaultBlueprintThemes } from '../themes';
|
|
10
13
|
import { composeScreenElement } from './composeScreen';
|
|
11
14
|
import { galleryComponentCatalog, galleryPreviewProfile } from './previewProfile';
|
|
@@ -38,8 +41,63 @@ export interface GalleryScreenPreviewProps {
|
|
|
38
41
|
|
|
39
42
|
/** The data state to show. Defaults to the seeded, populated records. */
|
|
40
43
|
state?: TemplatePreviewState;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The sample-data pack that stands in for the queries the templates bind to: each set's `shape` is a query name and its `rows` that query's result.
|
|
47
|
+
* Templates carry no rows of their own - they declare where the data comes from (spec SD1) - so a preview of a foundation template needs one.
|
|
48
|
+
*/
|
|
49
|
+
pack?: SampleDataPack;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A standard state (spec Part B 1.4) to force on every foundation component that owns the five states: loading, error, no-permission or partial.
|
|
53
|
+
* This is how a designer inspects each one on a real template - it only sets the `state` property the components already read, so there is no
|
|
54
|
+
* second code path. `error`, `permissionSubject` and `failedRegions` give the state its message.
|
|
55
|
+
*/
|
|
56
|
+
forceState?: ForcedComponentState;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The standard states a preview can force on a template's components. */
|
|
60
|
+
export interface ForcedComponentState {
|
|
61
|
+
state: ComponentState;
|
|
62
|
+
error?: string;
|
|
63
|
+
permissionSubject?: string;
|
|
64
|
+
failedRegions?: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Components that are chrome or controls rather than data-showing regions, and so do not own the five states. */
|
|
68
|
+
const withoutStates = new Set<string>([
|
|
69
|
+
ComponentName.EmptyState, ComponentName.TrendChip, ComponentName.StatusChip, ComponentName.BulkBar, ComponentName.GlobalSearch, ComponentName.NotificationsButton,
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/** Sets the forced state on every region that owns the five states, leaving chrome and controls as they are. */
|
|
73
|
+
export function forceComponentState(element: SceneElement, forced: ForcedComponentState): SceneElement {
|
|
74
|
+
const external = element as Partial<ExternalComponent>;
|
|
75
|
+
const children = (element as { children?: SceneElement[] }).children;
|
|
76
|
+
const slots = external.slots ? Object.fromEntries(Object.entries(external.slots).map(([slot, inner]) => [slot, inner.map(child => forceComponentState(child, forced))])) : undefined;
|
|
77
|
+
const bare = typeof external.componentName === 'string' ? external.componentName.substring(external.componentName.lastIndexOf(':') + 1) : undefined;
|
|
78
|
+
const owns = bare !== undefined && external.componentName!.includes(defaultBlueprintName) && (bare in foundationSamples) && foundationSamples[bare].stateful && !withoutStates.has(bare);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...element,
|
|
82
|
+
...(owns ? { properties: { ...external.properties, ...forced } } : {}),
|
|
83
|
+
...(slots ? { slots } : {}),
|
|
84
|
+
...(children ? { children: children.map(child => forceComponentState(child, forced)) } : {}),
|
|
85
|
+
} as SceneElement;
|
|
41
86
|
}
|
|
42
87
|
|
|
88
|
+
/**
|
|
89
|
+
* A pack as the query results the templates bind to: one entry for each set, keyed by the set's shape name.
|
|
90
|
+
*
|
|
91
|
+
* The state is a property of the *data*, not of the template: Empty answers every query with no rows; Loading and Error withhold the answer, which is
|
|
92
|
+
* what a query that has not come back or has failed looks like to a component, so each shows its own skeleton or error exactly as it will in an application.
|
|
93
|
+
* The elements themselves are untouched, which is why the same template previews in every state with no per-template code.
|
|
94
|
+
*/
|
|
95
|
+
export const queryResultsOf = (pack: SampleDataPack | undefined, state: TemplatePreviewState = TemplatePreviewState.Populated): Record<string, unknown> | undefined => {
|
|
96
|
+
if (pack === undefined) return undefined;
|
|
97
|
+
if (state === TemplatePreviewState.Empty) return Object.fromEntries(pack.sets.map(set => [set.shape, []]));
|
|
98
|
+
return Object.fromEntries(pack.sets.map(set => [set.shape, set.rows]));
|
|
99
|
+
};
|
|
100
|
+
|
|
43
101
|
/**
|
|
44
102
|
* Boots one gallery screen through the real engine.
|
|
45
103
|
*
|
|
@@ -53,20 +111,21 @@ export interface GalleryScreenPreviewProps {
|
|
|
53
111
|
* a preview of a blueprint against half a profile should look visibly incomplete rather than quietly
|
|
54
112
|
* wrong.
|
|
55
113
|
*/
|
|
56
|
-
export function GalleryScreenPreview({ screenName, initialConfig, themes = defaultBlueprintThemes, registry = galleryPreviewRegistry, state = TemplatePreviewState.Populated }: GalleryScreenPreviewProps) {
|
|
114
|
+
export function GalleryScreenPreview({ screenName, initialConfig, themes = defaultBlueprintThemes, registry = galleryPreviewRegistry, state = TemplatePreviewState.Populated, pack, forceState }: GalleryScreenPreviewProps) {
|
|
57
115
|
const element = useMemo(() => {
|
|
58
116
|
const screen = galleryScreen(screenName);
|
|
59
117
|
if (!screen) {
|
|
60
118
|
throw new Error(`The gallery has no screen named '${screenName}'.`);
|
|
61
119
|
}
|
|
62
120
|
|
|
63
|
-
|
|
64
|
-
|
|
121
|
+
const resolved = applyTemplatePreviewState(resolveElementComponentNames(composeScreenElement(screen), galleryPreviewProfile, galleryComponentCatalog), state);
|
|
122
|
+
return forceState ? forceComponentState(resolved, forceState) : resolved;
|
|
123
|
+
}, [screenName, state, forceState]);
|
|
65
124
|
|
|
66
125
|
return (
|
|
67
126
|
<LayoutConfigProvider initialConfig={initialConfig} storage={null}>
|
|
68
127
|
<LayoutThemeProvider themes={themes}>
|
|
69
|
-
<SceneElementView element={element} registry={registry} resolveBinding={() => undefined} />
|
|
128
|
+
<SceneElementView element={element} registry={registry} resolveBinding={() => undefined} queryResults={queryResultsOf(pack, state)} />
|
|
70
129
|
</LayoutThemeProvider>
|
|
71
130
|
</LayoutConfigProvider>
|
|
72
131
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { SceneElement } from '@cratis/scene.model';
|
|
4
|
+
import { BindingSourceKind, SceneElement } from '@cratis/scene.model';
|
|
5
5
|
import { ComponentName } from '../ComponentName';
|
|
6
6
|
import { SlotName } from '../layouts';
|
|
7
7
|
import { defaultBlueprintThemes } from '../themes';
|
|
@@ -71,6 +71,11 @@ export function applicationChrome(activeScreen: string, breadcrumb: BreadcrumbEn
|
|
|
71
71
|
{
|
|
72
72
|
logo: [logo('topbar-logo')],
|
|
73
73
|
end: [
|
|
74
|
+
// The topbar of Part B 2.1: logo, search, notifications, the environment, and the user. The search and the bell are the foundation
|
|
75
|
+
// components, so they work everywhere the shell does; the unread count is a query result, never a value.
|
|
76
|
+
externalComponent('topbar-search', ComponentName.GlobalSearch, { placeholder: 'Search', label: 'Search the application' }),
|
|
77
|
+
externalComponent('topbar-notifications', ComponentName.NotificationsButton, { count: { kind: BindingSourceKind.QueryResult, query: 'NotificationCount', path: '0.count' } }),
|
|
78
|
+
externalComponent('topbar-environment', ComponentName.StatusChip, { status: 'Production', tone: 'info' }),
|
|
74
79
|
externalComponent('topbar-user', ComponentName.UserMenu, {
|
|
75
80
|
name: 'Amelia Nyquist',
|
|
76
81
|
role: 'Owner',
|
package/gallery/composeScreen.ts
CHANGED
|
@@ -64,12 +64,14 @@ function placeInside(outer: ScreenTemplate, inner: ScreenTemplate): ScreenTempla
|
|
|
64
64
|
return outer;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
// A container-qualified name (`WorkspaceScreen.moduleContent`) says which container; the slot's own name is what the container's content is keyed by.
|
|
68
|
+
const slot = inner.fitsSlot.substring(inner.fitsSlot.lastIndexOf('.') + 1);
|
|
69
|
+
const innerContent = inner.slots.flatMap(innerSlot => inner.content?.[innerSlot.name] ?? []);
|
|
68
70
|
return {
|
|
69
71
|
...outer,
|
|
70
72
|
content: {
|
|
71
73
|
...outer.content,
|
|
72
|
-
[
|
|
74
|
+
[slot]: [...(outer.content?.[slot] ?? []), ...innerContent],
|
|
73
75
|
},
|
|
74
76
|
};
|
|
75
77
|
}
|
package/gallery/screens.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
|
import { Layout, Screen, ScreenTemplate } from '@cratis/scene.model';
|
|
5
|
+
import { foundationScreenTemplates } from '../foundation/templates';
|
|
5
6
|
import { LayoutName, appShellLayout, fullPageLayout } from '../layouts';
|
|
6
7
|
import { BreadcrumbEntry, applicationChrome, fullPageChrome } from './applicationChrome';
|
|
7
8
|
import { nestScreenTemplates, templateContentInLayout } from './composeScreen';
|
|
@@ -20,6 +21,7 @@ import { workspaceTemplates } from './workspaceTemplates';
|
|
|
20
21
|
* and what the bundle provides; `validatePackageBundle` proves the two agree.
|
|
21
22
|
*/
|
|
22
23
|
export const galleryScreenTemplates: ScreenTemplate[] = [
|
|
24
|
+
...foundationScreenTemplates,
|
|
23
25
|
...workspaceTemplates,
|
|
24
26
|
...supportTemplates,
|
|
25
27
|
...authTemplates,
|
|
@@ -43,6 +45,14 @@ const breadcrumbs: Record<string, BreadcrumbEntry[]> = {
|
|
|
43
45
|
UserManagement: [{ label: 'Administration', targetScreen: 'Dashboard' }, { label: 'Users' }],
|
|
44
46
|
Invoice: [{ label: 'Billing', targetScreen: 'Dashboard' }, { label: 'INV-2043' }],
|
|
45
47
|
Help: [{ label: 'Support', targetScreen: 'Dashboard' }, { label: 'Help' }],
|
|
48
|
+
DashboardScreen: [{ label: 'Dashboard' }],
|
|
49
|
+
ListScreen: [{ label: 'Sales', targetScreen: 'DashboardScreen' }, { label: 'Deals' }],
|
|
50
|
+
BoardScreen: [{ label: 'Sales', targetScreen: 'DashboardScreen' }, { label: 'Pipeline' }],
|
|
51
|
+
WorkspaceScreen: [{ label: 'Sales' }],
|
|
52
|
+
MasterDetailScreen: [{ label: 'Sales', targetScreen: 'WorkspaceScreen' }, { label: 'Accounts' }],
|
|
53
|
+
RecordPage: [{ label: 'Sales', targetScreen: 'DashboardScreen' }, { label: 'Accounts', targetScreen: 'ListScreen' }, { label: 'Account' }],
|
|
54
|
+
FormScreen: [{ label: 'Sales', targetScreen: 'DashboardScreen' }, { label: 'Accounts', targetScreen: 'ListScreen' }, { label: 'New account' }],
|
|
55
|
+
WizardScreen: [{ label: 'Sales', targetScreen: 'DashboardScreen' }, { label: 'New account' }],
|
|
46
56
|
ModuleWorkspace: [{ label: 'Operations' }],
|
|
47
57
|
FeatureSection: [{ label: 'Operations', targetScreen: 'ModuleWorkspace' }, { label: 'Adjustments' }],
|
|
48
58
|
SliceSection: [
|
|
@@ -85,7 +95,12 @@ function screenFor(template: ScreenTemplate): Screen {
|
|
|
85
95
|
*/
|
|
86
96
|
function containmentChainFor(template: ScreenTemplate): ScreenTemplate[] {
|
|
87
97
|
const depth = nestingChainTemplates.findIndex(candidate => candidate.name === template.name);
|
|
88
|
-
|
|
98
|
+
if (depth >= 0) return nestingChainTemplates.slice(0, depth + 1);
|
|
99
|
+
|
|
100
|
+
// A template that fits a container-qualified slot (`WorkspaceScreen.moduleContent`) renders inside that container, which is what the qualifier is for.
|
|
101
|
+
const [container] = (template.fitsSlot ?? '').split('.');
|
|
102
|
+
const outer = container ? foundationScreenTemplates.find(candidate => candidate.name === container) : undefined;
|
|
103
|
+
return outer ? [outer, template] : [template];
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
function mergeSlotContent(chrome: Record<string, Screen['slotContent'][string]>, content: Record<string, Screen['slotContent'][string]>): Screen['slotContent'] {
|