@cratis/scene.blueprint.default 4.40.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.
Files changed (129) hide show
  1. package/defaultBlueprint.ts +1 -0
  2. package/dist/cjs/defaultBlueprint.js +18 -1
  3. package/dist/cjs/defaultBlueprint.js.map +1 -1
  4. package/dist/cjs/defaultBlueprintComponentMetadata.js +9 -0
  5. package/dist/cjs/defaultBlueprintComponentMetadata.js.map +1 -1
  6. package/dist/cjs/defaultBlueprintComponents.js +9 -0
  7. package/dist/cjs/defaultBlueprintComponents.js.map +1 -1
  8. package/dist/cjs/foundation/templates/boardScreenTemplate.js +76 -0
  9. package/dist/cjs/foundation/templates/boardScreenTemplate.js.map +1 -0
  10. package/dist/cjs/foundation/templates/dashboardScreenTemplate.js +128 -0
  11. package/dist/cjs/foundation/templates/dashboardScreenTemplate.js.map +1 -0
  12. package/dist/cjs/foundation/templates/formScreenTemplate.js +71 -0
  13. package/dist/cjs/foundation/templates/formScreenTemplate.js.map +1 -0
  14. package/dist/cjs/foundation/templates/index.js +39 -0
  15. package/dist/cjs/foundation/templates/index.js.map +1 -0
  16. package/dist/cjs/foundation/templates/listScreenTemplate.js +124 -0
  17. package/dist/cjs/foundation/templates/listScreenTemplate.js.map +1 -0
  18. package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js +79 -0
  19. package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
  20. package/dist/cjs/foundation/templates/recordPageTemplate.js +137 -0
  21. package/dist/cjs/foundation/templates/recordPageTemplate.js.map +1 -0
  22. package/dist/cjs/foundation/templates/templateParts.js +38 -0
  23. package/dist/cjs/foundation/templates/templateParts.js.map +1 -0
  24. package/dist/cjs/foundation/templates/wizardScreenTemplate.js +95 -0
  25. package/dist/cjs/foundation/templates/wizardScreenTemplate.js.map +1 -0
  26. package/dist/cjs/foundation/templates/workspaceScreenTemplate.js +62 -0
  27. package/dist/cjs/foundation/templates/workspaceScreenTemplate.js.map +1 -0
  28. package/dist/cjs/gallery/GalleryScreenPreview.js +76 -4
  29. package/dist/cjs/gallery/GalleryScreenPreview.js.map +1 -1
  30. package/dist/cjs/gallery/applicationChrome.js +18 -1
  31. package/dist/cjs/gallery/applicationChrome.js.map +1 -1
  32. package/dist/cjs/gallery/composeScreen.js +5 -3
  33. package/dist/cjs/gallery/composeScreen.js.map +1 -1
  34. package/dist/cjs/gallery/screens.js +83 -2
  35. package/dist/cjs/gallery/screens.js.map +1 -1
  36. package/dist/cjs/index.js +25 -0
  37. package/dist/cjs/index.js.map +1 -1
  38. package/dist/esm/defaultBlueprint.d.ts.map +1 -1
  39. package/dist/esm/defaultBlueprint.js +18 -1
  40. package/dist/esm/defaultBlueprint.js.map +1 -1
  41. package/dist/esm/defaultBlueprintComponentMetadata.js +9 -0
  42. package/dist/esm/defaultBlueprintComponentMetadata.js.map +1 -1
  43. package/dist/esm/defaultBlueprintComponents.js +9 -0
  44. package/dist/esm/defaultBlueprintComponents.js.map +1 -1
  45. package/dist/esm/foundation/index.d.ts +1 -0
  46. package/dist/esm/foundation/index.d.ts.map +1 -1
  47. package/dist/esm/foundation/index.js +1 -0
  48. package/dist/esm/foundation/index.js.map +1 -1
  49. package/dist/esm/foundation/templates/boardScreenTemplate.d.ts +3 -0
  50. package/dist/esm/foundation/templates/boardScreenTemplate.d.ts.map +1 -0
  51. package/dist/esm/foundation/templates/boardScreenTemplate.js +74 -0
  52. package/dist/esm/foundation/templates/boardScreenTemplate.js.map +1 -0
  53. package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts +3 -0
  54. package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts.map +1 -0
  55. package/dist/esm/foundation/templates/dashboardScreenTemplate.js +126 -0
  56. package/dist/esm/foundation/templates/dashboardScreenTemplate.js.map +1 -0
  57. package/dist/esm/foundation/templates/formScreenTemplate.d.ts +3 -0
  58. package/dist/esm/foundation/templates/formScreenTemplate.d.ts.map +1 -0
  59. package/dist/esm/foundation/templates/formScreenTemplate.js +69 -0
  60. package/dist/esm/foundation/templates/formScreenTemplate.js.map +1 -0
  61. package/dist/esm/foundation/templates/index.d.ts +11 -0
  62. package/dist/esm/foundation/templates/index.d.ts.map +1 -0
  63. package/dist/esm/foundation/templates/index.js +25 -0
  64. package/dist/esm/foundation/templates/index.js.map +1 -0
  65. package/dist/esm/foundation/templates/listScreenTemplate.d.ts +3 -0
  66. package/dist/esm/foundation/templates/listScreenTemplate.d.ts.map +1 -0
  67. package/dist/esm/foundation/templates/listScreenTemplate.js +122 -0
  68. package/dist/esm/foundation/templates/listScreenTemplate.js.map +1 -0
  69. package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts +3 -0
  70. package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts.map +1 -0
  71. package/dist/esm/foundation/templates/masterDetailScreenTemplate.js +77 -0
  72. package/dist/esm/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
  73. package/dist/esm/foundation/templates/recordPageTemplate.d.ts +3 -0
  74. package/dist/esm/foundation/templates/recordPageTemplate.d.ts.map +1 -0
  75. package/dist/esm/foundation/templates/recordPageTemplate.js +135 -0
  76. package/dist/esm/foundation/templates/recordPageTemplate.js.map +1 -0
  77. package/dist/esm/foundation/templates/templateParts.d.ts +6 -0
  78. package/dist/esm/foundation/templates/templateParts.d.ts.map +1 -0
  79. package/dist/esm/foundation/templates/templateParts.js +33 -0
  80. package/dist/esm/foundation/templates/templateParts.js.map +1 -0
  81. package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts +3 -0
  82. package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts.map +1 -0
  83. package/dist/esm/foundation/templates/wizardScreenTemplate.js +93 -0
  84. package/dist/esm/foundation/templates/wizardScreenTemplate.js.map +1 -0
  85. package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts +3 -0
  86. package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts.map +1 -0
  87. package/dist/esm/foundation/templates/workspaceScreenTemplate.js +60 -0
  88. package/dist/esm/foundation/templates/workspaceScreenTemplate.js.map +1 -0
  89. package/dist/esm/gallery/GalleryScreenPreview.d.ts +13 -2
  90. package/dist/esm/gallery/GalleryScreenPreview.d.ts.map +1 -1
  91. package/dist/esm/gallery/GalleryScreenPreview.js +75 -5
  92. package/dist/esm/gallery/GalleryScreenPreview.js.map +1 -1
  93. package/dist/esm/gallery/applicationChrome.d.ts.map +1 -1
  94. package/dist/esm/gallery/applicationChrome.js +18 -1
  95. package/dist/esm/gallery/applicationChrome.js.map +1 -1
  96. package/dist/esm/gallery/composeScreen.d.ts.map +1 -1
  97. package/dist/esm/gallery/composeScreen.js +5 -3
  98. package/dist/esm/gallery/composeScreen.js.map +1 -1
  99. package/dist/esm/gallery/screens.d.ts.map +1 -1
  100. package/dist/esm/gallery/screens.js +83 -2
  101. package/dist/esm/gallery/screens.js.map +1 -1
  102. package/dist/esm/index.js +11 -1
  103. package/dist/esm/index.js.map +1 -1
  104. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  105. package/foundation/index.ts +1 -0
  106. package/foundation/templates/boardScreenTemplate.ts +28 -0
  107. package/foundation/templates/dashboardScreenTemplate.ts +37 -0
  108. package/foundation/templates/formScreenTemplate.ts +28 -0
  109. package/foundation/templates/index.ts +26 -0
  110. package/foundation/templates/listScreenTemplate.ts +37 -0
  111. package/foundation/templates/masterDetailScreenTemplate.ts +28 -0
  112. package/foundation/templates/recordPageTemplate.ts +36 -0
  113. package/foundation/templates/templateParts.ts +30 -0
  114. package/foundation/templates/wizardScreenTemplate.ts +33 -0
  115. package/foundation/templates/workspaceScreenTemplate.ts +27 -0
  116. package/gallery/GalleryScreenPreview.tsx +64 -5
  117. package/gallery/applicationChrome.ts +6 -1
  118. package/gallery/composeScreen.ts +4 -2
  119. package/gallery/screens.ts +16 -1
  120. package/package.json +10 -8
  121. package/sampleData/foundation-en.json +512 -0
  122. package/thumbnails/BoardScreen.svg +15 -0
  123. package/thumbnails/DashboardScreen.svg +18 -0
  124. package/thumbnails/FormScreen.svg +14 -0
  125. package/thumbnails/ListScreen.svg +16 -0
  126. package/thumbnails/MasterDetailScreen.svg +22 -0
  127. package/thumbnails/RecordPage.svg +17 -0
  128. package/thumbnails/WizardScreen.svg +16 -0
  129. package/thumbnails/WorkspaceScreen.svg +12 -0
@@ -56,3 +56,4 @@ export * from './foundationDescriptors';
56
56
  export * from './foundationComponentMetadata';
57
57
  export * from './foundationSamples';
58
58
  export * from './FoundationStates';
59
+ export * from './templates';
@@ -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 board: columns mapped to a status or stage, cards that move between them - by dragging or by each card's own Move-to menu, so the board is as
10
+ * usable from a keyboard and a screen reader as with a pointer.
11
+ */
12
+ export const boardScreenTemplate: ScreenTemplate = {
13
+ name: 'BoardScreen',
14
+ fitsSlot: 'content',
15
+ slots: [{ name: 'header' }, { name: 'toolbar' }, { name: 'board' }],
16
+ arrangement: {
17
+ root: column([slotLeaf('header'), row([slotLeaf('toolbar')], 8), slotLeaf('board')], 12),
18
+ overrides: [{ width: WidthSizeClass.Compact, root: column([slotLeaf('header'), slotLeaf('toolbar'), slotLeaf('board')], 16) }],
19
+ },
20
+ content: {
21
+ header: [component('board-header', 'pageHeader', { title: 'Pipeline', subtitle: 'Drag a deal to a new stage' })],
22
+ toolbar: [component('board-search', 'globalSearch', { placeholder: 'Search the pipeline', label: 'Search the pipeline' })],
23
+ board: [component('board-columns', 'kanbanBoard', { columns: fromQuery('PipelineColumns'), cards: fromQuery('PipelineCards') })],
24
+ },
25
+ displayName: 'Board',
26
+ description: 'Columns mapped to a stage with cards that move by dragging or by menu.',
27
+ metadata: { ...foundationTemplateProvenance, type: 'Board', category: 'Foundation / Board', thumbnail: thumbnailOf('BoardScreen') },
28
+ };
@@ -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
+ };
@@ -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
- return applyTemplatePreviewState(resolveElementComponentNames(composeScreenElement(screen), galleryPreviewProfile, galleryComponentCatalog), state);
64
- }, [screenName, state]);
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',
@@ -64,12 +64,14 @@ function placeInside(outer: ScreenTemplate, inner: ScreenTemplate): ScreenTempla
64
64
  return outer;
65
65
  }
66
66
 
67
- const innerContent = inner.slots.flatMap(slot => inner.content?.[slot.name] ?? []);
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
- [inner.fitsSlot]: [...(outer.content?.[inner.fitsSlot] ?? []), ...innerContent],
74
+ [slot]: [...(outer.content?.[slot] ?? []), ...innerContent],
73
75
  },
74
76
  };
75
77
  }
@@ -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
- return depth < 0 ? [template] : nestingChainTemplates.slice(0, depth + 1);
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'] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene.blueprint.default",
3
- "version": "4.40.0",
3
+ "version": "4.41.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
5
  "keywords": [
6
6
  "cratis",
@@ -23,6 +23,8 @@
23
23
  },
24
24
  "files": [
25
25
  "dist",
26
+ "sampleData",
27
+ "thumbnails",
26
28
  "**/*.ts",
27
29
  "**/*.tsx",
28
30
  "shell/layout.css",
@@ -55,15 +57,15 @@
55
57
  "build-storybook": "storybook build"
56
58
  },
57
59
  "dependencies": {
58
- "@cratis/scene.engine": "4.40.0",
59
- "@cratis/scene.model": "4.40.0",
60
- "@cratis/scene.react": "4.40.0"
60
+ "@cratis/scene.engine": "4.41.0",
61
+ "@cratis/scene.model": "4.41.0",
62
+ "@cratis/scene.react": "4.41.0"
61
63
  },
62
64
  "devDependencies": {
63
- "@cratis/scene.engine": "4.40.0",
64
- "@cratis/scene.model": "4.40.0",
65
- "@cratis/scene.primereact": "4.40.0",
66
- "@cratis/scene.react": "4.40.0",
65
+ "@cratis/scene.engine": "4.41.0",
66
+ "@cratis/scene.model": "4.41.0",
67
+ "@cratis/scene.primereact": "4.41.0",
68
+ "@cratis/scene.react": "4.41.0",
67
69
  "@primereact/core": "11.2.0",
68
70
  "@primereact/headless": "11.2.0",
69
71
  "@primeuix/themes": "^3.0.1",