@salesforce/webapp-template-app-react-template-b2x-experimental 1.109.4 → 1.109.6

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 (122) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/package.json +6 -5
  3. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/api/graphql-operations-types.ts +11260 -0
  4. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/components/ui/sonner.tsx +20 -0
  5. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  6. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  7. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  8. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  9. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  10. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  11. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/pages/AccountSearch.tsx +275 -0
  12. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/__examples__/pages/Home.tsx +34 -0
  13. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/api/objectSearchService.ts +84 -0
  14. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/ActiveFilters.tsx +89 -0
  15. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/FilterPanel.tsx +127 -0
  16. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  17. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/PaginationControls.tsx +151 -0
  18. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/SearchBar.tsx +41 -0
  19. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/SortControl.tsx +143 -0
  20. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/BooleanFilter.tsx +94 -0
  21. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/DateFilter.tsx +138 -0
  22. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/DateRangeFilter.tsx +78 -0
  23. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/MultiSelectFilter.tsx +106 -0
  24. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/NumericRangeFilter.tsx +102 -0
  25. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/SearchFilter.tsx +40 -0
  26. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
  27. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/components/filters/TextFilter.tsx +77 -0
  28. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/hooks/useAsyncData.ts +53 -0
  29. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/hooks/useCachedAsyncData.ts +183 -0
  30. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/hooks/useObjectSearchParams.ts +225 -0
  31. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/utils/debounce.ts +22 -0
  32. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/utils/fieldUtils.ts +29 -0
  33. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/utils/filterUtils.ts +372 -0
  34. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/object-search/utils/sortUtils.ts +38 -0
  35. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/pages/Home.tsx +10 -11
  36. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/routes.tsx +8 -20
  37. package/dist/package-lock.json +2 -2
  38. package/dist/package.json +1 -1
  39. package/package.json +1 -1
  40. package/dist/.a4drules/skills/designing-webapp-ui-ux/SKILL.md +0 -271
  41. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/charts.csv +0 -26
  42. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/colors.csv +0 -97
  43. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/icons.csv +0 -101
  44. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/landing.csv +0 -31
  45. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/products.csv +0 -97
  46. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/react-performance.csv +0 -45
  47. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/stacks/html-tailwind.csv +0 -56
  48. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/stacks/react.csv +0 -54
  49. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/stacks/shadcn.csv +0 -61
  50. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/styles.csv +0 -68
  51. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/typography.csv +0 -58
  52. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/ui-reasoning.csv +0 -101
  53. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/ux-guidelines.csv +0 -100
  54. package/dist/.a4drules/skills/designing-webapp-ui-ux/data/web-interface.csv +0 -31
  55. package/dist/.a4drules/skills/designing-webapp-ui-ux/scripts/core.js +0 -255
  56. package/dist/.a4drules/skills/designing-webapp-ui-ux/scripts/design_system.js +0 -861
  57. package/dist/.a4drules/skills/designing-webapp-ui-ux/scripts/search.js +0 -98
  58. package/dist/.a4drules/skills/integrating-unsplash-images/SKILL.md +0 -71
  59. package/dist/.a4drules/skills/integrating-unsplash-images/implementation/usage.md +0 -159
  60. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/api/objectDetailService.ts +0 -102
  61. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/api/objectInfoGraphQLService.ts +0 -137
  62. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/api/objectInfoService.ts +0 -95
  63. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/api/recordListGraphQLService.ts +0 -364
  64. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/DetailFields.tsx +0 -55
  65. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/DetailForm.tsx +0 -146
  66. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/DetailHeader.tsx +0 -34
  67. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/DetailLayoutSections.tsx +0 -80
  68. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/Section.tsx +0 -108
  69. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/SectionRow.tsx +0 -20
  70. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/UiApiDetailForm.tsx +0 -140
  71. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +0 -73
  72. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +0 -29
  73. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +0 -17
  74. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +0 -24
  75. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FormattedText.tsx +0 -11
  76. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +0 -29
  77. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/filters/FilterField.tsx +0 -54
  78. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/filters/FilterInput.tsx +0 -55
  79. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/filters/FilterSelect.tsx +0 -72
  80. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/filters/FiltersPanel.tsx +0 -380
  81. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/forms/filters-form.tsx +0 -114
  82. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/forms/submit-button.tsx +0 -47
  83. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/GlobalSearchInput.tsx +0 -114
  84. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/ResultCardFields.tsx +0 -71
  85. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/SearchHeader.tsx +0 -31
  86. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/SearchPagination.tsx +0 -144
  87. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/SearchResultCard.tsx +0 -138
  88. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/search/SearchResultsPanel.tsx +0 -197
  89. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/components/shared/LoadingFallback.tsx +0 -61
  90. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/constants.ts +0 -39
  91. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/filters/FilterInput.tsx +0 -55
  92. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/filters/FilterSelect.tsx +0 -72
  93. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/hooks/form.tsx +0 -209
  94. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/hooks/useObjectInfoBatch.ts +0 -72
  95. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/hooks/useObjectSearchData.ts +0 -174
  96. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/hooks/useRecordDetailLayout.ts +0 -137
  97. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/hooks/useRecordListGraphQL.ts +0 -135
  98. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/pages/DetailPage.tsx +0 -109
  99. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/pages/GlobalSearch.tsx +0 -235
  100. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/filters/filters.ts +0 -121
  101. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/filters/picklist.ts +0 -6
  102. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/objectInfo/objectInfo.ts +0 -49
  103. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/recordDetail/recordDetail.ts +0 -61
  104. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/schema.d.ts +0 -200
  105. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/types/search/searchResults.ts +0 -229
  106. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/apiUtils.ts +0 -59
  107. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/cacheUtils.ts +0 -76
  108. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/debounce.ts +0 -90
  109. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/fieldUtils.ts +0 -354
  110. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/fieldValueExtractor.ts +0 -67
  111. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/filterUtils.ts +0 -32
  112. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/formDataTransformUtils.ts +0 -260
  113. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/formUtils.ts +0 -142
  114. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/graphQLNodeFieldUtils.ts +0 -186
  115. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +0 -77
  116. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/graphQLRecordAdapter.ts +0 -90
  117. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/layoutTransformUtils.ts +0 -236
  118. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/linkUtils.ts +0 -14
  119. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/paginationUtils.ts +0 -49
  120. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/recordUtils.ts +0 -159
  121. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/features/global-search/utils/sanitizationUtils.ts +0 -50
  122. package/dist/force-app/main/default/webapplications/appreacttemplateb2x/src/index.ts +0 -120
@@ -1,98 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, searchStack } from './core.js';
4
- import { generateDesignSystem } from './design_system.js';
5
-
6
- // ============ ARG PARSER ============
7
-
8
- function parseArgs(argv) {
9
- const args = { query: null, maxResults: MAX_RESULTS, format: 'ascii' };
10
- let i = 2;
11
- while (i < argv.length) {
12
- const a = argv[i];
13
- if (a === '--domain' || a === '-d') { args.domain = argv[++i]; }
14
- else if (a === '--stack' || a === '-s') { args.stack = argv[++i]; }
15
- else if (a === '--max-results' || a === '-n') { args.maxResults = parseInt(argv[++i], 10); }
16
- else if (a === '--json') { args.json = true; }
17
- else if (a === '--design-system' || a === '-ds') { args.designSystem = true; }
18
- else if (a === '--project-name' || a === '-p') { args.projectName = argv[++i]; }
19
- else if (a === '--format' || a === '-f') { args.format = argv[++i]; }
20
- else if (a === '--persist') { args.persist = true; }
21
- else if (a === '--page') { args.page = argv[++i]; }
22
- else if (a === '--output-dir' || a === '-o') { args.outputDir = argv[++i]; }
23
- else if (!a.startsWith('-') && !args.query) { args.query = a; }
24
- i++;
25
- }
26
- return args;
27
- }
28
-
29
- // ============ FORMATTER ============
30
-
31
- function formatOutput(result) {
32
- if (result.error) return `Error: ${result.error}`;
33
-
34
- const lines = [];
35
- if (result.stack) {
36
- lines.push('## UI Pro Max Stack Guidelines');
37
- lines.push(`**Stack:** ${result.stack} | **Query:** ${result.query}`);
38
- } else {
39
- lines.push('## UI Pro Max Search Results');
40
- lines.push(`**Domain:** ${result.domain} | **Query:** ${result.query}`);
41
- }
42
- lines.push(`**Source:** ${result.file} | **Found:** ${result.count} results\n`);
43
-
44
- (result.results || []).forEach((row, i) => {
45
- lines.push(`### Result ${i + 1}`);
46
- for (const [key, value] of Object.entries(row)) {
47
- const v = String(value);
48
- lines.push(`- **${key}:** ${v.length > 300 ? v.slice(0, 300) + '...' : v}`);
49
- }
50
- lines.push('');
51
- });
52
-
53
- return lines.join('\n');
54
- }
55
-
56
- // ============ MAIN ============
57
-
58
- const args = parseArgs(process.argv);
59
-
60
- if (!args.query) {
61
- console.error('Usage: node search.js "<query>" [--domain <d>] [--stack <s>] [--design-system] [-p "Name"]');
62
- console.error(`Domains: ${Object.keys(CSV_CONFIG).join(', ')}`);
63
- console.error(`Stacks: ${AVAILABLE_STACKS.join(', ')}`);
64
- process.exit(1);
65
- }
66
-
67
- if (args.designSystem) {
68
- const result = generateDesignSystem(
69
- args.query,
70
- args.projectName,
71
- args.format,
72
- !!args.persist,
73
- args.page || null,
74
- args.outputDir || null
75
- );
76
- console.log(result);
77
-
78
- if (args.persist) {
79
- const slug = args.projectName ? args.projectName.toLowerCase().replace(/\s+/g, '-') : 'default';
80
- console.log('\n' + '='.repeat(60));
81
- console.log(`✅ Design system persisted to design-system/${slug}/`);
82
- console.log(` 📄 design-system/${slug}/MASTER.md (Global Source of Truth)`);
83
- if (args.page) {
84
- const pageSlug = args.page.toLowerCase().replace(/\s+/g, '-');
85
- console.log(` 📄 design-system/${slug}/pages/${pageSlug}.md (Page Overrides)`);
86
- }
87
- console.log('');
88
- console.log(`📖 Usage: When building a page, check design-system/${slug}/pages/[page].md first.`);
89
- console.log(` If exists, its rules override MASTER.md. Otherwise, use MASTER.md.`);
90
- console.log('='.repeat(60));
91
- }
92
- } else if (args.stack) {
93
- const result = searchStack(args.query, args.stack, args.maxResults);
94
- console.log(args.json ? JSON.stringify(result, null, 2) : formatOutput(result));
95
- } else {
96
- const result = search(args.query, args.domain, args.maxResults);
97
- console.log(args.json ? JSON.stringify(result, null, 2) : formatOutput(result));
98
- }
@@ -1,71 +0,0 @@
1
- ---
2
- name: integrating-unsplash-images
3
- description: Adds high-quality Unsplash images to React pages. Use when the user asks to add a hero image, background image, placeholder image, stock photo, decorative image, or any Unsplash-sourced imagery to the web application.
4
- ---
5
-
6
- # Unsplash Images
7
-
8
- ## When to Use
9
-
10
- Use this skill when:
11
- - Adding hero banners, section backgrounds, or decorative images
12
- - The user asks for stock photography or placeholder images
13
- - A page needs visual appeal without custom assets
14
-
15
- ---
16
-
17
- ## Step 1 — Determine image purpose
18
-
19
- Identify what the image is for:
20
-
21
- - **Hero / banner** — full-width background behind text or a CTA
22
- - **Section accent** — smaller image alongside content (stats, testimonials, features)
23
- - **Card thumbnail** — image inside a card component
24
- - **Background texture** — subtle decorative background
25
-
26
- If unclear, ask:
27
-
28
- > "Where should the image appear — as a hero banner, a section accent, or a card thumbnail?"
29
-
30
- ---
31
-
32
- ## Step 2 — Select the right Unsplash URL format
33
-
34
- Read `implementation/usage.md` for the full reference. Key rules:
35
-
36
- 1. **Always use `images.unsplash.com/photo-{id}` format** with explicit `w` and `q` parameters.
37
- 2. **Never use `source.unsplash.com`** — it is deprecated and returns 404s.
38
- 3. **Pin every image by its photo ID** — never rely on random or search endpoints.
39
-
40
- ---
41
-
42
- ## Step 3 — Validate image URLs
43
-
44
- Before using any Unsplash URL in code:
45
-
46
- 1. Open the URL in a browser or fetch it to confirm it returns a 200 status and a valid image.
47
- 2. If the URL returns a 404, redirect loop, or broken image, **discard it** and pick a different photo ID.
48
- 3. Never ship a URL you have not personally verified.
49
-
50
- ---
51
-
52
- ## Step 4 — Implementation
53
-
54
- Read `implementation/usage.md` and follow the instructions there.
55
-
56
- ---
57
-
58
- ## Verification
59
-
60
- Before completing:
61
-
62
- 1. Confirm every Unsplash URL loads a valid image (no 404, no redirect loops).
63
- 2. Confirm `alt` text is set appropriately (empty `alt=""` for decorative, descriptive for meaningful).
64
- 3. Run from the web app directory:
65
-
66
- ```bash
67
- cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
68
- ```
69
-
70
- - **Lint:** MUST result in 0 errors.
71
- - **Build:** MUST succeed.
@@ -1,159 +0,0 @@
1
- # Unsplash Images — Implementation Guide
2
-
3
- ## URL Format
4
-
5
- Always use the direct `images.unsplash.com` format with explicit sizing:
6
-
7
- ```
8
- https://images.unsplash.com/photo-{PHOTO_ID}?w={WIDTH}&q={QUALITY}
9
- ```
10
-
11
- | Parameter | Purpose | Recommended values |
12
- |-----------|---------|--------------------|
13
- | `w` | Pixel width served by the CDN | `600` card, `800` section, `1200` hero, `1920` full-bleed |
14
- | `q` | JPEG quality 1–100 | `80`–`85` (good balance of quality vs size) |
15
-
16
- ### Deprecated / broken formats — do NOT use
17
-
18
- | Format | Why it fails |
19
- |--------|-------------|
20
- | `source.unsplash.com/random` | Deprecated; returns 404 |
21
- | `source.unsplash.com/{WIDTH}x{HEIGHT}` | Deprecated; returns 404 |
22
- | `source.unsplash.com/featured/?{query}` | Deprecated; returns 404 |
23
-
24
- ---
25
-
26
- ## How to find a valid photo ID
27
-
28
- 1. Go to [unsplash.com](https://unsplash.com) and search for the subject (e.g. "modern apartment").
29
- 2. Open a photo. The URL will look like `unsplash.com/photos/{slug}-{PHOTO_ID}` or `unsplash.com/photos/{PHOTO_ID}`.
30
- 3. The `PHOTO_ID` is the last hyphen-separated segment (e.g. `photo-1600596542815-ffad4c1539a9`).
31
- 4. Build your URL: `https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85`
32
- 5. **Test the URL** in a browser before committing.
33
-
34
- ---
35
-
36
- ## Declaring image constants
37
-
38
- Define all Unsplash URLs as named constants at the top of the file. Never inline URLs in JSX.
39
-
40
- ```tsx
41
- const HERO_IMAGE = "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85";
42
- const SECTION_IMAGE = "https://images.unsplash.com/photo-1514565131-fce0801e5785?w=800&q=85";
43
- ```
44
-
45
- ---
46
-
47
- ## Rendering images in JSX
48
-
49
- ### Hero / banner (decorative — empty alt)
50
-
51
- ```tsx
52
- <div className="relative w-full overflow-hidden rounded-2xl">
53
- <div className="relative aspect-[21/9] min-h-[280px] w-full md:aspect-[3/1]">
54
- <img
55
- src={HERO_IMAGE}
56
- alt=""
57
- className="h-full w-full object-cover"
58
- loading="eager"
59
- fetchPriority="high"
60
- />
61
- <div className="absolute inset-0 bg-black/40" />
62
- <div className="absolute inset-0 flex flex-col items-center justify-center px-4">
63
- {/* overlay content */}
64
- </div>
65
- </div>
66
- </div>
67
- ```
68
-
69
- Key points:
70
- - `alt=""` for decorative images (screen readers skip them).
71
- - `loading="eager"` and `fetchPriority="high"` for above-the-fold heroes.
72
- - `object-cover` prevents stretching.
73
- - Semi-transparent overlay (`bg-black/40`) ensures text readability.
74
-
75
- ### Section accent (meaningful — descriptive alt)
76
-
77
- ```tsx
78
- <div className="relative min-h-[240px] overflow-hidden rounded-2xl">
79
- <img src={SECTION_IMAGE} alt="City skyline at sunset" className="h-full w-full object-cover" />
80
- <div className="absolute inset-0 bg-gradient-to-r from-black/20 to-transparent" />
81
- </div>
82
- ```
83
-
84
- ### Card thumbnail
85
-
86
- ```tsx
87
- <div className="aspect-[4/3] overflow-hidden bg-muted">
88
- <img
89
- src={imageUrl}
90
- alt=""
91
- className="h-full w-full object-cover transition-transform hover:scale-105"
92
- loading="lazy"
93
- />
94
- </div>
95
- ```
96
-
97
- Key points:
98
- - `loading="lazy"` for below-the-fold images.
99
- - `hover:scale-105` subtle zoom on hover.
100
- - `bg-muted` fallback color while loading.
101
-
102
- ---
103
-
104
- ## Fallback when no image is available
105
-
106
- Always provide a placeholder when the image URL may be null:
107
-
108
- ```tsx
109
- {imageUrl ? (
110
- <img src={imageUrl} alt="" className="h-full w-full object-cover" />
111
- ) : (
112
- <div className="flex h-full items-center justify-center text-muted-foreground">
113
- No image
114
- </div>
115
- )}
116
- ```
117
-
118
- ---
119
-
120
- ## Content Security Policy (CSP)
121
-
122
- If the application enforces CSP headers, add `images.unsplash.com` to `img-src`:
123
-
124
- ```
125
- img-src 'self' https://images.unsplash.com;
126
- ```
127
-
128
- Other commonly needed origins for stock images:
129
-
130
- | Origin | Purpose |
131
- |--------|---------|
132
- | `images.unsplash.com` | Unsplash photos |
133
- | `images.pexels.com` | Pexels photos |
134
- | `fonts.googleapis.com` | Google Fonts CSS |
135
- | `fonts.gstatic.com` | Google Fonts files |
136
-
137
- ---
138
-
139
- ## Accessibility checklist
140
-
141
- - [ ] Decorative images have `alt=""`
142
- - [ ] Meaningful images have descriptive `alt` text
143
- - [ ] Hero images use `loading="eager"` and `fetchPriority="high"`
144
- - [ ] Below-fold images use `loading="lazy"`
145
- - [ ] Text over images has sufficient contrast (use overlay like `bg-black/40`)
146
- - [ ] All URLs verified to return a valid image (no 404s)
147
-
148
- ---
149
-
150
- ## Common mistakes
151
-
152
- | Mistake | Fix |
153
- |---------|-----|
154
- | Using `source.unsplash.com` | Replace with `images.unsplash.com/photo-{id}?w=…&q=…` |
155
- | Hardcoding an unverified URL | Open URL in browser first; replace if broken |
156
- | Missing `w` parameter | Always set width — CDN returns full-res (5000px+) otherwise |
157
- | `loading="lazy"` on hero | Use `loading="eager"` for above-the-fold images |
158
- | No fallback for nullable URLs | Wrap in conditional with placeholder div |
159
- | Inline URLs in JSX | Extract to named constants at file top |
@@ -1,102 +0,0 @@
1
- /**
2
- * Record detail service: layout (REST), object metadata (GraphQL), single record (GraphQL).
3
- *
4
- * getRecordDetail orchestrates layout + objectInfoBatch + getRecordByIdGraphQL for the detail page.
5
- * Layout is still REST (uiApiClient); record and object info are GraphQL-backed.
6
- *
7
- * @module api/objectDetailService
8
- */
9
-
10
- import { uiApiClient } from "@salesforce/webapp-experimental/api";
11
- import type { LayoutResponse } from "../types/recordDetail/recordDetail";
12
- import { LayoutResponseSchema } from "../types/recordDetail/recordDetail";
13
- import { fetchAndValidate, safeEncodePath } from "../utils/apiUtils";
14
- import { objectInfoService } from "./objectInfoService";
15
- import type { ObjectInfoResult } from "../types/objectInfo/objectInfo";
16
- import { getRecordByIdGraphQL, type GraphQLRecordNode } from "./recordListGraphQLService";
17
- import type { Column } from "../types/search/searchResults";
18
- import { calculateFieldsToFetch } from "../utils/recordUtils";
19
-
20
- /** Fallback when record type is unknown. Prefer recordTypeId from the record (e.g. from search or record response) when available. */
21
- const DEFAULT_RECORD_TYPE_ID = "012000000000000AAA";
22
-
23
- /**
24
- * Returns field API names to request for a record from the given layout and object metadata.
25
- * Used to derive GraphQL columns from layout (detail view). Delegates to recordUtils.calculateFieldsToFetch.
26
- *
27
- * @param objectMetadata - Object info (fields, relationshipName, etc.).
28
- * @param layout - Layout response (sections, layoutItems, layoutComponents).
29
- * @returns Array of field API names (e.g. ["Name", "OwnerId", "Owner", "CreatedDate"]).
30
- */
31
- export function extractFieldsFromLayout(
32
- objectMetadata: ObjectInfoResult,
33
- layout: LayoutResponse,
34
- ): string[] {
35
- const [optionalFields] = calculateFieldsToFetch(objectMetadata, layout, false);
36
- return optionalFields;
37
- }
38
-
39
- export async function getLayout(
40
- objectApiName: string,
41
- recordTypeId: string = DEFAULT_RECORD_TYPE_ID,
42
- ): Promise<LayoutResponse> {
43
- const params = new URLSearchParams({
44
- layoutType: "Full",
45
- mode: "View",
46
- recordTypeId,
47
- });
48
- return fetchAndValidate(
49
- () => uiApiClient.get(`/layout/${safeEncodePath(objectApiName)}?${params.toString()}`),
50
- {
51
- schema: LayoutResponseSchema,
52
- errorContext: `layout for ${objectApiName}`,
53
- },
54
- );
55
- }
56
-
57
- export interface RecordDetailResult {
58
- layout: LayoutResponse;
59
- record: GraphQLRecordNode;
60
- objectMetadata: ObjectInfoResult;
61
- }
62
-
63
- /**
64
- * Converts layout-derived optionalFields (field API names) to Column[] for GraphQL node selection.
65
- * Uses unqualified names (no entity prefix) so the GraphQL query matches UI API shape.
66
- * Other Column fields (label, searchable, sortable) are only required by the type; GraphQL selection uses fieldApiName only.
67
- */
68
- function optionalFieldsToColumns(optionalFields: string[]): Column[] {
69
- return optionalFields.map((fieldApiName) => ({
70
- fieldApiName,
71
- label: "",
72
- searchable: false,
73
- sortable: false,
74
- }));
75
- }
76
-
77
- export async function getRecordDetail(
78
- objectApiName: string,
79
- recordId: string,
80
- recordTypeId: string = DEFAULT_RECORD_TYPE_ID,
81
- ): Promise<RecordDetailResult> {
82
- const layout = await getLayout(objectApiName, recordTypeId);
83
- const objectMetadata = await objectInfoService.getObjectInfoBatch(objectApiName);
84
- const firstResult = objectMetadata?.results?.[0]?.result;
85
- if (!firstResult) {
86
- throw new Error(`Object metadata not found for ${objectApiName}`);
87
- }
88
- // Layout-driven optionalFields (fields shown on the detail layout), not list columns
89
- const [optionalFields] = calculateFieldsToFetch(firstResult, layout, false);
90
- const columns = optionalFieldsToColumns(optionalFields);
91
- const record = await getRecordByIdGraphQL(objectApiName, recordId, columns);
92
- if (!record) {
93
- throw new Error(`Record not found: ${recordId}`);
94
- }
95
- return { layout, record, objectMetadata: firstResult };
96
- }
97
-
98
- export const objectDetailService = {
99
- extractFieldsFromLayout,
100
- getLayout,
101
- getRecordDetail,
102
- };
@@ -1,137 +0,0 @@
1
- /**
2
- * Object metadata GraphQL service (uiapi.objectInfos).
3
- *
4
- * Single endpoint for object describe and picklist values. Used by objectInfoService
5
- * to implement getObjectInfoBatch and getPicklistValues. Not used directly by UI.
6
- *
7
- * @module api/objectInfoGraphQLService
8
- */
9
-
10
- import { createDataSDK, gql } from "@salesforce/sdk-data";
11
- import type {
12
- GetObjectInfosQuery,
13
- GetObjectInfosQueryVariables,
14
- GetPicklistValuesQuery,
15
- GetPicklistValuesQueryVariables,
16
- ObjectInfoInput,
17
- } from "../types/schema";
18
-
19
- /**
20
- * Builds objectInfos query (metadata only). Uses apiNames only — do not pass objectInfoInputs.
21
- */
22
- const OBJECT_INFOS_QUERY = gql`
23
- query GetObjectInfos($apiNames: [String!]!) {
24
- uiapi {
25
- objectInfos(apiNames: $apiNames) {
26
- ApiName
27
- label
28
- labelPlural
29
- nameFields
30
- defaultRecordTypeId
31
- keyPrefix
32
- layoutable
33
- queryable
34
- searchable
35
- updateable
36
- deletable
37
- createable
38
- custom
39
- mruEnabled
40
- feedEnabled
41
- fields {
42
- ApiName
43
- label
44
- dataType
45
- relationshipName
46
- reference
47
- compound
48
- compoundFieldName
49
- compoundComponentName
50
- controllingFields
51
- controllerName
52
- referenceToInfos {
53
- ApiName
54
- nameFields
55
- }
56
- }
57
- recordTypeInfos {
58
- recordTypeId
59
- name
60
- master
61
- available
62
- defaultRecordTypeMapping
63
- }
64
- themeInfo {
65
- color
66
- iconUrl
67
- }
68
- childRelationships {
69
- relationshipName
70
- fieldName
71
- childObjectApiName
72
- }
73
- dependentFields {
74
- controllingField
75
- }
76
- }
77
- }
78
- }
79
- `;
80
-
81
- /**
82
- * Builds objectInfos query with picklist values (API v65.0+).
83
- * Schema requires objectInfos to be called with either apiNames or objectInfoInputs, not both.
84
- * This query uses objectInfoInputs only.
85
- * Optimized to only fetch fields used by extractPicklistValuesFromGraphQLObjectInfo.
86
- */
87
- const PICKLIST_VALUES_QUERY = gql`
88
- query GetPicklistValues($objectInfoInputs: [ObjectInfoInput!]!) {
89
- uiapi {
90
- objectInfos(objectInfoInputs: $objectInfoInputs) {
91
- ApiName
92
- fields {
93
- ApiName
94
- ... on PicklistField {
95
- picklistValuesByRecordTypeIDs {
96
- recordTypeID
97
- defaultValue {
98
- value
99
- }
100
- picklistValues {
101
- label
102
- value
103
- validFor
104
- }
105
- }
106
- }
107
- }
108
- }
109
- }
110
- }
111
- `;
112
-
113
- export async function queryForObjectInfos(apiNames: string[]): Promise<GetObjectInfosQuery> {
114
- return runQuery<GetObjectInfosQuery, GetObjectInfosQueryVariables>(OBJECT_INFOS_QUERY, {
115
- apiNames,
116
- });
117
- }
118
-
119
- export async function queryForPicklistValues(
120
- objectInfoInputs: ObjectInfoInput[],
121
- ): Promise<GetPicklistValuesQuery> {
122
- return runQuery<GetPicklistValuesQuery, GetPicklistValuesQueryVariables>(PICKLIST_VALUES_QUERY, {
123
- objectInfoInputs,
124
- });
125
- }
126
-
127
- async function runQuery<Q, V>(query: string, variables: V): Promise<Q> {
128
- const data = await createDataSDK();
129
- const response = await data.graphql?.<Q, V>(query, variables);
130
-
131
- if (response?.errors?.length) {
132
- const errorMessages = response.errors.map((e) => e.message).join("; ");
133
- throw new Error(`GraphQL Error: ${errorMessages}`);
134
- }
135
-
136
- return response?.data ?? ({} as Q);
137
- }
@@ -1,95 +0,0 @@
1
- import { uiApiClient } from "@salesforce/webapp-experimental/api";
2
- import type { Filter } from "../types/filters/filters";
3
- import { FilterArraySchema } from "../types/filters/filters";
4
- import type { PicklistValue } from "../types/filters/picklist";
5
- import type { ObjectInfoBatchResponse } from "../types/objectInfo/objectInfo";
6
- import { fetchAndValidate, safeEncodePath } from "../utils/apiUtils";
7
- import { queryForObjectInfos, queryForPicklistValues } from "./objectInfoGraphQLService";
8
- import {
9
- graphQLObjectInfosToBatchResponse,
10
- extractPicklistValuesFromGraphQLObjectInfo,
11
- } from "../utils/graphQLObjectInfoAdapter";
12
-
13
- /**
14
- * Object info and search service.
15
- *
16
- * - getObjectInfoBatch / getPicklistValues: GraphQL (objectInfoGraphQLService).
17
- * - getObjectListFilters: REST (search-info).
18
- * Hooks use this service; components do not call it directly.
19
- *
20
- * @module api/objectInfoService
21
- */
22
-
23
- /** Cache key: sorted, comma-joined object API names. */
24
- function getObjectInfoBatchCacheKey(objectApiNames: string): string {
25
- const names = objectApiNames
26
- .split(",")
27
- .map((s) => s.trim())
28
- .filter(Boolean);
29
- return [...names].sort().join(",");
30
- }
31
-
32
- const objectInfoBatchCache = new Map<string, ObjectInfoBatchResponse>();
33
- const objectInfoBatchInFlight = new Map<string, Promise<ObjectInfoBatchResponse>>();
34
-
35
- export async function getObjectInfoBatch(objectApiNames: string): Promise<ObjectInfoBatchResponse> {
36
- const names = objectApiNames
37
- .split(",")
38
- .map((s) => s.trim())
39
- .filter(Boolean);
40
- if (names.length === 0) {
41
- return { results: [] };
42
- }
43
- const key = getObjectInfoBatchCacheKey(objectApiNames);
44
- const cached = objectInfoBatchCache.get(key);
45
- if (cached) return Promise.resolve(cached);
46
- const inFlight = objectInfoBatchInFlight.get(key);
47
- if (inFlight) return inFlight;
48
- const promise = (async () => {
49
- try {
50
- const response = await queryForObjectInfos(names);
51
- const nodes = response?.uiapi?.objectInfos ?? [];
52
- const result = graphQLObjectInfosToBatchResponse(nodes, names);
53
- objectInfoBatchCache.set(key, result);
54
- return result;
55
- } finally {
56
- objectInfoBatchInFlight.delete(key);
57
- }
58
- })();
59
- objectInfoBatchInFlight.set(key, promise);
60
- return promise;
61
- }
62
-
63
- export async function getObjectListFilters(objectApiName: string): Promise<Filter[]> {
64
- return fetchAndValidate(
65
- () => uiApiClient.get(`/search-info/${safeEncodePath(objectApiName)}/filters`),
66
- {
67
- schema: FilterArraySchema,
68
- errorContext: `filters for ${objectApiName}`,
69
- extractData: (data: unknown) => {
70
- if (!data) return [];
71
- return Array.isArray(data) ? data : (data as { filters?: unknown }).filters || [];
72
- },
73
- },
74
- );
75
- }
76
-
77
- export async function getPicklistValues(
78
- objectApiName: string,
79
- fieldName: string,
80
- recordTypeId: string = "012000000000000AAA",
81
- ): Promise<PicklistValue[]> {
82
- const response = await queryForPicklistValues([
83
- { apiName: objectApiName, fieldNames: [fieldName] },
84
- ]);
85
- const nodes = response?.uiapi?.objectInfos ?? [];
86
- const node = nodes[0];
87
- if (!node) return [];
88
- return extractPicklistValuesFromGraphQLObjectInfo(node, fieldName, recordTypeId);
89
- }
90
-
91
- export const objectInfoService = {
92
- getObjectInfoBatch,
93
- getObjectListFilters,
94
- getPicklistValues,
95
- };