@cratis/scene.blueprint.default 4.40.0 → 4.42.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 (251) hide show
  1. package/ComponentName.ts +9 -0
  2. package/defaultBlueprint.ts +1 -0
  3. package/defaultBlueprintComponents.ts +4 -1
  4. package/dist/cjs/ComponentName.js +3 -0
  5. package/dist/cjs/ComponentName.js.map +1 -1
  6. package/dist/cjs/defaultBlueprint.js +20 -1
  7. package/dist/cjs/defaultBlueprint.js.map +1 -1
  8. package/dist/cjs/defaultBlueprintComponentMetadata.js +11 -0
  9. package/dist/cjs/defaultBlueprintComponentMetadata.js.map +1 -1
  10. package/dist/cjs/defaultBlueprintComponents.js +17 -0
  11. package/dist/cjs/defaultBlueprintComponents.js.map +1 -1
  12. package/dist/cjs/foundation/ActionButton.js.map +1 -1
  13. package/dist/cjs/foundation/ActionButtonControl.js +46 -0
  14. package/dist/cjs/foundation/ActionButtonControl.js.map +1 -0
  15. package/dist/cjs/foundation/DialogFrame.js +196 -0
  16. package/dist/cjs/foundation/DialogFrame.js.map +1 -0
  17. package/dist/cjs/foundation/FormScreenBody.js +13 -3
  18. package/dist/cjs/foundation/FormScreenBody.js.map +1 -1
  19. package/dist/cjs/foundation/Notice.js +51 -0
  20. package/dist/cjs/foundation/Notice.js.map +1 -0
  21. package/dist/cjs/foundation/formModel.js +1 -0
  22. package/dist/cjs/foundation/formModel.js.map +1 -1
  23. package/dist/cjs/foundation/foundationComponentMetadata.js +22 -0
  24. package/dist/cjs/foundation/foundationComponentMetadata.js.map +1 -1
  25. package/dist/cjs/foundation/foundationDescriptors.js +89 -0
  26. package/dist/cjs/foundation/foundationDescriptors.js.map +1 -1
  27. package/dist/cjs/foundation/foundationSamples.js +29 -0
  28. package/dist/cjs/foundation/foundationSamples.js.map +1 -1
  29. package/dist/cjs/foundation/foundationStrings.js +3 -1
  30. package/dist/cjs/foundation/foundationStrings.js.map +1 -1
  31. package/dist/cjs/foundation/templates/boardScreenTemplate.js +76 -0
  32. package/dist/cjs/foundation/templates/boardScreenTemplate.js.map +1 -0
  33. package/dist/cjs/foundation/templates/dashboardScreenTemplate.js +128 -0
  34. package/dist/cjs/foundation/templates/dashboardScreenTemplate.js.map +1 -0
  35. package/dist/cjs/foundation/templates/dialogTemplates.js +254 -0
  36. package/dist/cjs/foundation/templates/dialogTemplates.js.map +1 -0
  37. package/dist/cjs/foundation/templates/formScreenTemplate.js +71 -0
  38. package/dist/cjs/foundation/templates/formScreenTemplate.js.map +1 -0
  39. package/dist/cjs/foundation/templates/index.js +57 -0
  40. package/dist/cjs/foundation/templates/index.js.map +1 -0
  41. package/dist/cjs/foundation/templates/listScreenTemplate.js +124 -0
  42. package/dist/cjs/foundation/templates/listScreenTemplate.js.map +1 -0
  43. package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js +79 -0
  44. package/dist/cjs/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
  45. package/dist/cjs/foundation/templates/recordPageTemplate.js +137 -0
  46. package/dist/cjs/foundation/templates/recordPageTemplate.js.map +1 -0
  47. package/dist/cjs/foundation/templates/stateScreenTemplates.js +252 -0
  48. package/dist/cjs/foundation/templates/stateScreenTemplates.js.map +1 -0
  49. package/dist/cjs/foundation/templates/templateParts.js +38 -0
  50. package/dist/cjs/foundation/templates/templateParts.js.map +1 -0
  51. package/dist/cjs/foundation/templates/wizardScreenTemplate.js +95 -0
  52. package/dist/cjs/foundation/templates/wizardScreenTemplate.js.map +1 -0
  53. package/dist/cjs/foundation/templates/workspaceScreenTemplate.js +62 -0
  54. package/dist/cjs/foundation/templates/workspaceScreenTemplate.js.map +1 -0
  55. package/dist/cjs/gallery/GalleryDialogPreview.js +62 -0
  56. package/dist/cjs/gallery/GalleryDialogPreview.js.map +1 -0
  57. package/dist/cjs/gallery/GalleryScreenPreview.js +78 -4
  58. package/dist/cjs/gallery/GalleryScreenPreview.js.map +1 -1
  59. package/dist/cjs/gallery/applicationChrome.js +18 -1
  60. package/dist/cjs/gallery/applicationChrome.js.map +1 -1
  61. package/dist/cjs/gallery/composeScreen.js +5 -3
  62. package/dist/cjs/gallery/composeScreen.js.map +1 -1
  63. package/dist/cjs/gallery/dialogTemplates.js +41 -36
  64. package/dist/cjs/gallery/dialogTemplates.js.map +1 -1
  65. package/dist/cjs/gallery/screens.js +87 -3
  66. package/dist/cjs/gallery/screens.js.map +1 -1
  67. package/dist/cjs/index.js +61 -5
  68. package/dist/cjs/index.js.map +1 -1
  69. package/dist/esm/ComponentName.d.ts +3 -0
  70. package/dist/esm/ComponentName.d.ts.map +1 -1
  71. package/dist/esm/ComponentName.js +3 -0
  72. package/dist/esm/ComponentName.js.map +1 -1
  73. package/dist/esm/defaultBlueprint.d.ts.map +1 -1
  74. package/dist/esm/defaultBlueprint.js +20 -1
  75. package/dist/esm/defaultBlueprint.js.map +1 -1
  76. package/dist/esm/defaultBlueprintComponentMetadata.js +11 -0
  77. package/dist/esm/defaultBlueprintComponentMetadata.js.map +1 -1
  78. package/dist/esm/defaultBlueprintComponents.d.ts.map +1 -1
  79. package/dist/esm/defaultBlueprintComponents.js +17 -0
  80. package/dist/esm/defaultBlueprintComponents.js.map +1 -1
  81. package/dist/esm/foundation/ActionButton.d.ts +1 -1
  82. package/dist/esm/foundation/ActionButton.d.ts.map +1 -1
  83. package/dist/esm/foundation/ActionButton.js.map +1 -1
  84. package/dist/esm/foundation/ActionButtonControl.d.ts +3 -0
  85. package/dist/esm/foundation/ActionButtonControl.d.ts.map +1 -0
  86. package/dist/esm/foundation/ActionButtonControl.js +44 -0
  87. package/dist/esm/foundation/ActionButtonControl.js.map +1 -0
  88. package/dist/esm/foundation/DialogFrame.d.ts +14 -0
  89. package/dist/esm/foundation/DialogFrame.d.ts.map +1 -0
  90. package/dist/esm/foundation/DialogFrame.js +190 -0
  91. package/dist/esm/foundation/DialogFrame.js.map +1 -0
  92. package/dist/esm/foundation/FormScreenBody.d.ts.map +1 -1
  93. package/dist/esm/foundation/FormScreenBody.js +13 -3
  94. package/dist/esm/foundation/FormScreenBody.js.map +1 -1
  95. package/dist/esm/foundation/Notice.d.ts +3 -0
  96. package/dist/esm/foundation/Notice.d.ts.map +1 -0
  97. package/dist/esm/foundation/Notice.js +49 -0
  98. package/dist/esm/foundation/Notice.js.map +1 -0
  99. package/dist/esm/foundation/formModel.d.ts +2 -1
  100. package/dist/esm/foundation/formModel.d.ts.map +1 -1
  101. package/dist/esm/foundation/formModel.js +1 -0
  102. package/dist/esm/foundation/formModel.js.map +1 -1
  103. package/dist/esm/foundation/foundationComponentMetadata.d.ts.map +1 -1
  104. package/dist/esm/foundation/foundationComponentMetadata.js +22 -0
  105. package/dist/esm/foundation/foundationComponentMetadata.js.map +1 -1
  106. package/dist/esm/foundation/foundationDescriptors.d.ts.map +1 -1
  107. package/dist/esm/foundation/foundationDescriptors.js +89 -0
  108. package/dist/esm/foundation/foundationDescriptors.js.map +1 -1
  109. package/dist/esm/foundation/foundationSamples.d.ts.map +1 -1
  110. package/dist/esm/foundation/foundationSamples.js +29 -0
  111. package/dist/esm/foundation/foundationSamples.js.map +1 -1
  112. package/dist/esm/foundation/foundationStrings.d.ts +2 -0
  113. package/dist/esm/foundation/foundationStrings.d.ts.map +1 -1
  114. package/dist/esm/foundation/foundationStrings.js +3 -1
  115. package/dist/esm/foundation/foundationStrings.js.map +1 -1
  116. package/dist/esm/foundation/index.d.ts +4 -0
  117. package/dist/esm/foundation/index.d.ts.map +1 -1
  118. package/dist/esm/foundation/index.js +4 -0
  119. package/dist/esm/foundation/index.js.map +1 -1
  120. package/dist/esm/foundation/templates/boardScreenTemplate.d.ts +3 -0
  121. package/dist/esm/foundation/templates/boardScreenTemplate.d.ts.map +1 -0
  122. package/dist/esm/foundation/templates/boardScreenTemplate.js +74 -0
  123. package/dist/esm/foundation/templates/boardScreenTemplate.js.map +1 -0
  124. package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts +3 -0
  125. package/dist/esm/foundation/templates/dashboardScreenTemplate.d.ts.map +1 -0
  126. package/dist/esm/foundation/templates/dashboardScreenTemplate.js +126 -0
  127. package/dist/esm/foundation/templates/dashboardScreenTemplate.js.map +1 -0
  128. package/dist/esm/foundation/templates/dialogTemplates.d.ts +20 -0
  129. package/dist/esm/foundation/templates/dialogTemplates.d.ts.map +1 -0
  130. package/dist/esm/foundation/templates/dialogTemplates.js +243 -0
  131. package/dist/esm/foundation/templates/dialogTemplates.js.map +1 -0
  132. package/dist/esm/foundation/templates/formScreenTemplate.d.ts +3 -0
  133. package/dist/esm/foundation/templates/formScreenTemplate.d.ts.map +1 -0
  134. package/dist/esm/foundation/templates/formScreenTemplate.js +69 -0
  135. package/dist/esm/foundation/templates/formScreenTemplate.js.map +1 -0
  136. package/dist/esm/foundation/templates/index.d.ts +13 -0
  137. package/dist/esm/foundation/templates/index.d.ts.map +1 -0
  138. package/dist/esm/foundation/templates/index.js +27 -0
  139. package/dist/esm/foundation/templates/index.js.map +1 -0
  140. package/dist/esm/foundation/templates/listScreenTemplate.d.ts +3 -0
  141. package/dist/esm/foundation/templates/listScreenTemplate.d.ts.map +1 -0
  142. package/dist/esm/foundation/templates/listScreenTemplate.js +122 -0
  143. package/dist/esm/foundation/templates/listScreenTemplate.js.map +1 -0
  144. package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts +3 -0
  145. package/dist/esm/foundation/templates/masterDetailScreenTemplate.d.ts.map +1 -0
  146. package/dist/esm/foundation/templates/masterDetailScreenTemplate.js +77 -0
  147. package/dist/esm/foundation/templates/masterDetailScreenTemplate.js.map +1 -0
  148. package/dist/esm/foundation/templates/recordPageTemplate.d.ts +3 -0
  149. package/dist/esm/foundation/templates/recordPageTemplate.d.ts.map +1 -0
  150. package/dist/esm/foundation/templates/recordPageTemplate.js +135 -0
  151. package/dist/esm/foundation/templates/recordPageTemplate.js.map +1 -0
  152. package/dist/esm/foundation/templates/stateScreenTemplates.d.ts +8 -0
  153. package/dist/esm/foundation/templates/stateScreenTemplates.d.ts.map +1 -0
  154. package/dist/esm/foundation/templates/stateScreenTemplates.js +245 -0
  155. package/dist/esm/foundation/templates/stateScreenTemplates.js.map +1 -0
  156. package/dist/esm/foundation/templates/templateParts.d.ts +6 -0
  157. package/dist/esm/foundation/templates/templateParts.d.ts.map +1 -0
  158. package/dist/esm/foundation/templates/templateParts.js +33 -0
  159. package/dist/esm/foundation/templates/templateParts.js.map +1 -0
  160. package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts +3 -0
  161. package/dist/esm/foundation/templates/wizardScreenTemplate.d.ts.map +1 -0
  162. package/dist/esm/foundation/templates/wizardScreenTemplate.js +93 -0
  163. package/dist/esm/foundation/templates/wizardScreenTemplate.js.map +1 -0
  164. package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts +3 -0
  165. package/dist/esm/foundation/templates/workspaceScreenTemplate.d.ts.map +1 -0
  166. package/dist/esm/foundation/templates/workspaceScreenTemplate.js +60 -0
  167. package/dist/esm/foundation/templates/workspaceScreenTemplate.js.map +1 -0
  168. package/dist/esm/gallery/FoundationDialogs.stories.d.ts +35 -0
  169. package/dist/esm/gallery/FoundationDialogs.stories.d.ts.map +1 -0
  170. package/dist/esm/gallery/FoundationDialogs.stories.js +38 -0
  171. package/dist/esm/gallery/FoundationDialogs.stories.js.map +1 -0
  172. package/dist/esm/gallery/GalleryDialogPreview.d.ts +17 -0
  173. package/dist/esm/gallery/GalleryDialogPreview.d.ts.map +1 -0
  174. package/dist/esm/gallery/GalleryDialogPreview.js +59 -0
  175. package/dist/esm/gallery/GalleryDialogPreview.js.map +1 -0
  176. package/dist/esm/gallery/GalleryScreenPreview.d.ts +13 -2
  177. package/dist/esm/gallery/GalleryScreenPreview.d.ts.map +1 -1
  178. package/dist/esm/gallery/GalleryScreenPreview.js +77 -5
  179. package/dist/esm/gallery/GalleryScreenPreview.js.map +1 -1
  180. package/dist/esm/gallery/applicationChrome.d.ts.map +1 -1
  181. package/dist/esm/gallery/applicationChrome.js +18 -1
  182. package/dist/esm/gallery/applicationChrome.js.map +1 -1
  183. package/dist/esm/gallery/composeScreen.d.ts.map +1 -1
  184. package/dist/esm/gallery/composeScreen.js +5 -3
  185. package/dist/esm/gallery/composeScreen.js.map +1 -1
  186. package/dist/esm/gallery/dialogTemplates.d.ts.map +1 -1
  187. package/dist/esm/gallery/dialogTemplates.js +42 -37
  188. package/dist/esm/gallery/dialogTemplates.js.map +1 -1
  189. package/dist/esm/gallery/index.d.ts +1 -0
  190. package/dist/esm/gallery/index.d.ts.map +1 -1
  191. package/dist/esm/gallery/index.js +1 -0
  192. package/dist/esm/gallery/index.js.map +1 -1
  193. package/dist/esm/gallery/screens.d.ts.map +1 -1
  194. package/dist/esm/gallery/screens.js +87 -3
  195. package/dist/esm/gallery/screens.js.map +1 -1
  196. package/dist/esm/index.js +17 -1
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  199. package/foundation/ActionButton.tsx +1 -1
  200. package/foundation/ActionButtonControl.tsx +37 -0
  201. package/foundation/DialogFrame.tsx +138 -0
  202. package/foundation/FormScreenBody.tsx +6 -3
  203. package/foundation/Notice.tsx +33 -0
  204. package/foundation/formModel.ts +1 -0
  205. package/foundation/foundationComponentMetadata.ts +3 -0
  206. package/foundation/foundationDescriptors.ts +41 -0
  207. package/foundation/foundationSamples.ts +3 -0
  208. package/foundation/foundationStrings.ts +2 -0
  209. package/foundation/index.ts +4 -0
  210. package/foundation/templates/boardScreenTemplate.ts +28 -0
  211. package/foundation/templates/dashboardScreenTemplate.ts +37 -0
  212. package/foundation/templates/dialogTemplates.ts +114 -0
  213. package/foundation/templates/formScreenTemplate.ts +28 -0
  214. package/foundation/templates/index.ts +28 -0
  215. package/foundation/templates/listScreenTemplate.ts +37 -0
  216. package/foundation/templates/masterDetailScreenTemplate.ts +28 -0
  217. package/foundation/templates/recordPageTemplate.ts +36 -0
  218. package/foundation/templates/stateScreenTemplates.ts +107 -0
  219. package/foundation/templates/templateParts.ts +30 -0
  220. package/foundation/templates/wizardScreenTemplate.ts +33 -0
  221. package/foundation/templates/workspaceScreenTemplate.ts +27 -0
  222. package/gallery/FoundationDialogs.stories.tsx +74 -0
  223. package/gallery/GalleryDialogPreview.tsx +68 -0
  224. package/gallery/GalleryScreenPreview.tsx +64 -5
  225. package/gallery/applicationChrome.ts +6 -1
  226. package/gallery/composeScreen.ts +4 -2
  227. package/gallery/dialogTemplates.ts +22 -17
  228. package/gallery/index.ts +1 -0
  229. package/gallery/screens.ts +18 -2
  230. package/package.json +10 -8
  231. package/sampleData/foundation-en.json +661 -0
  232. package/shell/layout.css +219 -0
  233. package/thumbnails/BoardScreen.svg +15 -0
  234. package/thumbnails/ConfirmDialog.svg +12 -0
  235. package/thumbnails/DashboardScreen.svg +18 -0
  236. package/thumbnails/DetailDialog.svg +11 -0
  237. package/thumbnails/ErrorScreen.svg +11 -0
  238. package/thumbnails/FilterDialog.svg +16 -0
  239. package/thumbnails/ForbiddenScreen.svg +10 -0
  240. package/thumbnails/FormDialog.svg +12 -0
  241. package/thumbnails/FormScreen.svg +14 -0
  242. package/thumbnails/ListScreen.svg +16 -0
  243. package/thumbnails/LoginScreen.svg +12 -0
  244. package/thumbnails/LookupDialog.svg +13 -0
  245. package/thumbnails/MasterDetailScreen.svg +22 -0
  246. package/thumbnails/NotFoundScreen.svg +9 -0
  247. package/thumbnails/QuickCreateDialog.svg +12 -0
  248. package/thumbnails/RecordPage.svg +17 -0
  249. package/thumbnails/SideSheet.svg +13 -0
  250. package/thumbnails/WizardScreen.svg +16 -0
  251. 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 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,107 @@
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, TemplateMetadata } from '@cratis/scene.model';
5
+ import { externalComponent, panel } from '../../gallery/elements';
6
+ import { actionButton } from './dialogTemplates';
7
+ import { column, slotLeaf } from '../../layouts/flowBuilders';
8
+ import { component, foundationTemplateProvenance, fromQuery, thumbnailOf } from './templateParts';
9
+
10
+ const metadataOf = (name: string, type: 'Auth' | 'State'): TemplateMetadata => ({ ...foundationTemplateProvenance, type, category: `Foundation / ${type}`, thumbnail: thumbnailOf(name) });
11
+
12
+ /** The way back every state screen offers: home, which is always somewhere the person is allowed to be. */
13
+ const backToDashboard = (id: string) => actionButton(id, { label: 'Back to the dashboard', emphasis: 'secondary', targetScreen: 'DashboardScreen' });
14
+
15
+ /**
16
+ * The sign-in screen: the logo, the sign-in form, a slot for the identity providers the application offers (`providers` - buttons, each starting its
17
+ * provider's flow, which the application fills), an error area that is a live region and says why a sign-in failed, and a link to help. The form is
18
+ * made of the foundation's form components, so it validates and announces like every other form.
19
+ */
20
+ export const loginScreenTemplate: ScreenTemplate = {
21
+ name: 'LoginScreen',
22
+ fitsSlot: 'content',
23
+ slots: [{ name: 'logo' }, { name: 'error' }, { name: 'form' }, { name: 'providers' }, { name: 'help' }],
24
+ arrangement: { root: column([slotLeaf('logo'), slotLeaf('error'), slotLeaf('form'), slotLeaf('providers'), slotLeaf('help')], 16) },
25
+ content: {
26
+ logo: [externalComponent('login-logo', 'logo', { label: 'Contoso', initials: 'C' })],
27
+ error: [component('login-error', 'notice', { text: fromQuery('SignInError', '0.message'), tone: 'danger' })],
28
+ form: [component('login-form', 'formScreenBody', {
29
+ mode: 'create', saveAndNew: false, strings: { formSave: 'Sign in', formCancel: 'Clear' },
30
+ sections: fromQuery('SignInSections'), values: fromQuery('SignInValues', '0'), errors: fromQuery('FormErrors', '0'),
31
+ })],
32
+ providers: [panel('login-providers', [actionButton('login-microsoft', { label: 'Continue with Microsoft', emphasis: 'secondary' }), actionButton('login-github', { label: 'Continue with GitHub', emphasis: 'secondary' })])],
33
+ help: [actionButton('login-help', { label: 'Need help signing in?', emphasis: 'quiet', targetScreen: 'ForbiddenScreen' })],
34
+ },
35
+ displayName: 'Sign in',
36
+ description: 'A sign-in form with a slot for identity providers, an error area and a help link.',
37
+ metadata: metadataOf('LoginScreen', 'Auth'),
38
+ };
39
+
40
+ /** The wrong address: what happened in words, a search, and the way home. */
41
+ export const notFoundScreenTemplate: ScreenTemplate = {
42
+ name: 'NotFoundScreen',
43
+ fitsSlot: 'content',
44
+ slots: [{ name: 'message' }, { name: 'search' }, { name: 'actions' }],
45
+ arrangement: { root: column([slotLeaf('message'), slotLeaf('search'), slotLeaf('actions')], 16) },
46
+ content: {
47
+ message: [component('not-found-message', 'emptyState', { icon: 'file', headline: 'That page is not here', hint: 'The link may be old, or the thing it pointed at may have been removed.' })],
48
+ search: [component('not-found-search', 'globalSearch', { label: 'Search the application', placeholder: 'Search instead' })],
49
+ actions: [backToDashboard('not-found-home')],
50
+ },
51
+ displayName: 'Not found',
52
+ description: 'A wrong address explained, with a search and a link home.',
53
+ metadata: metadataOf('NotFoundScreen', 'State'),
54
+ };
55
+
56
+ /**
57
+ * The refusal: signed in, and still not allowed. It says so and offers to ask for access, and it shows nothing of what was refused - the template binds
58
+ * no data at all, so there is nothing to leak.
59
+ */
60
+ export const forbiddenScreenTemplate: ScreenTemplate = {
61
+ name: 'ForbiddenScreen',
62
+ fitsSlot: 'content',
63
+ slots: [{ name: 'message' }, { name: 'actions' }],
64
+ arrangement: { root: column([slotLeaf('message'), slotLeaf('actions')], 16) },
65
+ content: {
66
+ message: [component('forbidden-message', 'emptyState', {
67
+ icon: 'lock', headline: 'You do not have access to this', hint: 'Your account is signed in, but it is not allowed here. An administrator can change that.', actionLabel: 'Request access',
68
+ })],
69
+ actions: [backToDashboard('forbidden-home')],
70
+ },
71
+ displayName: 'Forbidden',
72
+ description: 'A refusal that offers to request access and shows none of what was refused.',
73
+ metadata: metadataOf('ForbiddenScreen', 'State'),
74
+ };
75
+
76
+ /** The failure on our side: a plain message, a retry, and the support reference id a person can quote. */
77
+ export const errorScreenTemplate: ScreenTemplate = {
78
+ name: 'ErrorScreen',
79
+ fitsSlot: 'content',
80
+ slots: [{ name: 'message' }, { name: 'reference' }, { name: 'actions' }],
81
+ arrangement: { root: column([slotLeaf('message'), slotLeaf('reference'), slotLeaf('actions')], 16) },
82
+ content: {
83
+ message: [component('error-message', 'emptyState', {
84
+ icon: 'alert', headline: 'Something went wrong on our side', hint: 'The team has been told. Nothing you were working on was lost.', actionLabel: 'Try again',
85
+ })],
86
+ reference: [panel('error-reference', [
87
+ externalComponent('error-reference-label', 'text', { text: 'Support reference' }),
88
+ externalComponent('error-reference-id', 'text', { text: fromQuery('SupportReference', '0.id') }),
89
+ ])],
90
+ actions: [backToDashboard('error-home')],
91
+ },
92
+ displayName: 'Error',
93
+ description: 'A generic failure with a retry and the support reference id to quote.',
94
+ metadata: metadataOf('ErrorScreen', 'State'),
95
+ };
96
+
97
+ /** The four auth and state screens of Part B 2.11. The legacy `Login`, `NotFound`, `AccessDenied` and `Error` remain as they were. */
98
+ export const stateScreenTemplates: ScreenTemplate[] = [loginScreenTemplate, notFoundScreenTemplate, forbiddenScreenTemplate, errorScreenTemplate];
99
+
100
+ /** The earlier template each spec-named one stands beside: the old name keeps working, and is the alias of the new. */
101
+ export const foundationTemplateAliases: Record<string, string> = {
102
+ LoginScreen: 'Login',
103
+ NotFoundScreen: 'NotFound',
104
+ ForbiddenScreen: 'AccessDenied',
105
+ ErrorScreen: 'Error',
106
+ QuickCreateDialog: 'FormDialog',
107
+ };
@@ -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,74 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { Meta, StoryObj } from '@storybook/react';
5
+ import { SampleDataPack } from '@cratis/scene.model';
6
+ import { corePackage, mergePackageRegistries } from '@cratis/scene.react';
7
+ import { primeReactPackage } from '@cratis/scene.primereact';
8
+ import { defaultBlueprint } from '../defaultBlueprint';
9
+ import { ComponentState } from '../foundation';
10
+ import { GalleryDialogPreview } from './GalleryDialogPreview';
11
+ import { GalleryScreenPreview } from './GalleryScreenPreview';
12
+ import { TemplatePreviewState } from './TemplatePreviewState';
13
+
14
+ const registry = mergePackageRegistries([corePackage, primeReactPackage, defaultBlueprint]);
15
+ /** The foundation sample-data pack, fetched from where the package ships it (`sampleData/`), as a host's loader would. */
16
+ const packUrl = new URL('../sampleData/foundation-en.json', import.meta.url).href;
17
+ const loadPack = async () => ({ pack: await (await fetch(packUrl)).json() as SampleDataPack });
18
+
19
+ /**
20
+ * The foundation dialogs and the auth and state screens (spec Part B 2.10 and 2.11) in each state, filled from the foundation sample-data pack: the data in the
21
+ * pack, an empty answer, and each of the standard states forced on the components that own them. A dialog opens in the box a host draws, which its frame takes on:
22
+ * focus handling, placement and its name.
23
+ */
24
+ const meta = {
25
+ title: 'Blueprint/Foundation dialogs',
26
+ component: GalleryDialogPreview,
27
+ parameters: { layout: 'fullscreen' },
28
+ loaders: [loadPack],
29
+ render: (args, { loaded }) => <GalleryDialogPreview {...args} pack={loaded.pack as SampleDataPack} />,
30
+ args: { registry, dialogName: 'ConfirmDialog' },
31
+ tags: ['autodocs'],
32
+ } satisfies Meta<typeof GalleryDialogPreview>;
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof meta>;
37
+
38
+ /** A destructive confirmation: the consequences listed, the confirming button naming what it does. */
39
+ export const Confirm: Story = {};
40
+
41
+ /** The short create form, six fields at most. */
42
+ export const QuickCreate: Story = { args: { dialogName: 'QuickCreateDialog' } };
43
+
44
+ /** The lookup with the pack's accounts. */
45
+ export const Lookup: Story = { args: { dialogName: 'LookupDialog' } };
46
+
47
+ /** The lookup when the query returns nothing: it says so in words. */
48
+ export const LookupEmpty: Story = { args: { dialogName: 'LookupDialog', state: TemplatePreviewState.Empty } };
49
+
50
+ /** The lookup while its query has not answered. */
51
+ export const LookupLoading: Story = { args: { dialogName: 'LookupDialog', forceState: { state: ComponentState.Loading } } };
52
+
53
+ /** The lookup when its query failed: a message and a retry. */
54
+ export const LookupError: Story = { args: { dialogName: 'LookupDialog', forceState: { state: ComponentState.Error, error: 'The server did not answer' } } };
55
+
56
+ /** The lookup for a user who may not see the accounts: what is missing, and none of the data. */
57
+ export const LookupNoPermission: Story = { args: { dialogName: 'LookupDialog', forceState: { state: ComponentState.NoPermission, permissionSubject: 'the accounts' } } };
58
+
59
+ /** The lookup when part of it failed: the chrome stays, and the failed part is named. */
60
+ export const LookupPartial: Story = { args: { dialogName: 'LookupDialog', forceState: { state: ComponentState.Partial, failedRegions: ['Accounts'] } } };
61
+
62
+ /** A drawer from the side hosting a form. */
63
+ export const SideSheet: Story = { args: { dialogName: 'SideSheet' } };
64
+
65
+ /** The advanced filter builder; on a phone it fills the page. */
66
+ export const Filter: Story = { args: { dialogName: 'FilterDialog' } };
67
+
68
+ /** Every state screen, as a gallery screen: sign in, not found, forbidden and error. */
69
+ export const StateScreens: StoryObj<typeof GalleryScreenPreview> = {
70
+ loaders: [loadPack],
71
+ render: (args, { loaded }) => <GalleryScreenPreview {...args} registry={registry} pack={loaded.pack as SampleDataPack} />,
72
+ args: { screenName: 'LoginScreen' },
73
+ argTypes: { screenName: { control: 'select', options: ['LoginScreen', 'NotFoundScreen', 'ForbiddenScreen', 'ErrorScreen'] } },
74
+ };
@@ -0,0 +1,68 @@
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 { useMemo } from 'react';
5
+ import { DialogTemplate, SampleDataPack, Theme } from '@cratis/scene.model';
6
+ import { ComponentRegistry, SceneElementView } from '@cratis/scene.react';
7
+ import { LayoutConfigProvider, LayoutConfigState } from '../configuration';
8
+ import { previewComponents, previewTemplateComponent } from '../preview-host/PreviewTemplate';
9
+ import { LayoutThemeProvider, defaultBlueprintThemes } from '../themes';
10
+ import { externalComponent } from './elements';
11
+ import { ForcedComponentState, forceComponentState, galleryPreviewRegistry, queryResultsOf } from './GalleryScreenPreview';
12
+ import { galleryDialogTemplates } from './dialogTemplates';
13
+ import { galleryComponentCatalog, galleryPreviewProfile } from './previewProfile';
14
+ import { resolveElementComponentNames } from './resolveElementNames';
15
+ import { TemplatePreviewState } from './TemplatePreviewState';
16
+
17
+ /** One gallery dialog template by name, for a story or a host that opens a specific one. */
18
+ export const galleryDialogTemplate = (name: string): DialogTemplate | undefined => galleryDialogTemplates.find(template => template.name === name);
19
+
20
+ export interface GalleryDialogPreviewProps {
21
+ /** The name of the gallery dialog template to open. */
22
+ dialogName: string;
23
+
24
+ /** Configuration to start from. */
25
+ initialConfig?: Partial<LayoutConfigState>;
26
+
27
+ /** The themes the preview may switch between. Defaults to the two this blueprint ships. */
28
+ themes?: Theme[];
29
+
30
+ /** The component registry to render against. Defaults to `core` plus this blueprint's own components. */
31
+ registry?: ComponentRegistry;
32
+
33
+ /** The data state to show. Defaults to the seeded, populated records. */
34
+ state?: TemplatePreviewState;
35
+
36
+ /** The sample-data pack that stands in for the queries the template binds to. */
37
+ pack?: SampleDataPack;
38
+
39
+ /** A standard state (spec Part B 1.4) to force on every foundation component of the dialog that owns the five states. */
40
+ forceState?: ForcedComponentState;
41
+ }
42
+
43
+ /**
44
+ * Opens one gallery dialog template the way a host does: inside a `role="dialog" aria-modal="true"` element, with the template's content in the wrapper
45
+ * a screen's template gets. Nothing here is a preview-only path - the dialog is a real {@link DialogTemplate} rendered by the real `SceneElementView`,
46
+ * and the dialog's own frame gives the host element its focus handling, placement and labelling exactly as it does in an application.
47
+ */
48
+ export function GalleryDialogPreview({ dialogName, initialConfig, themes = defaultBlueprintThemes, registry = galleryPreviewRegistry, state = TemplatePreviewState.Populated, pack, forceState }: GalleryDialogPreviewProps) {
49
+ const template = galleryDialogTemplate(dialogName);
50
+ if (!template) throw new Error(`The gallery has no dialog template named '${dialogName}'.`);
51
+
52
+ const element = useMemo(() => {
53
+ const composed = externalComponent(`dialog-${template.name}`, previewTemplateComponent, {}, template.content ?? {});
54
+ const resolved = resolveElementComponentNames(composed, galleryPreviewProfile, galleryComponentCatalog);
55
+ return forceState ? forceComponentState(resolved, forceState) : resolved;
56
+ }, [template, forceState]);
57
+ const full = useMemo(() => ({ ...registry, ...previewComponents }), [registry]);
58
+
59
+ return (
60
+ <LayoutConfigProvider initialConfig={initialConfig} storage={null}>
61
+ <LayoutThemeProvider themes={themes}>
62
+ <div role='dialog' aria-modal='true' aria-label={template.displayName ?? template.name} data-scene-dialog={template.name}>
63
+ <SceneElementView element={element} registry={full} resolveBinding={() => undefined} queryResults={queryResultsOf(pack, state)} />
64
+ </div>
65
+ </LayoutThemeProvider>
66
+ </LayoutConfigProvider>
67
+ );
68
+ }
@@ -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
  }