@salesforce/webapp-template-app-react-template-b2e-experimental 1.59.2 → 1.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.md +8 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package-lock.json +4255 -227
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package.json +11 -2
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/index.ts +19 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectDetailService.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectInfoGraphQLService.ts +194 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectInfoService.ts +199 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/recordListGraphQLService.ts +365 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/FiltersPanel.tsx +375 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/LoadingFallback.tsx +61 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/SearchResultCard.tsx +131 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/alerts/status-alert.tsx +45 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailFields.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailForm.tsx +146 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailHeader.tsx +34 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailLayoutSections.tsx +80 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/Section.tsx +108 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/SectionRow.tsx +20 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/UiApiDetailForm.tsx +140 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FieldValueDisplay.tsx +73 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedAddress.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedEmail.tsx +17 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedPhone.tsx +24 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedText.tsx +11 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedUrl.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/index.ts +6 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterField.tsx +54 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterInput.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterSelect.tsx +72 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/forms/filters-form.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/forms/submit-button.tsx +47 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/layout/card-layout.tsx +19 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/ResultCardFields.tsx +71 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchHeader.tsx +31 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchPagination.tsx +144 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchResultsPanel.tsx +197 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/shared/GlobalSearchInput.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/alert.tsx +69 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/button.tsx +67 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/card.tsx +92 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/dialog.tsx +143 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/field.tsx +222 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/index.ts +84 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/input.tsx +19 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/label.tsx +19 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/pagination.tsx +112 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/select.tsx +183 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/separator.tsx +26 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/skeleton.tsx +14 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/spinner.tsx +15 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/table.tsx +87 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/tabs.tsx +78 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components.json +18 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/constants.ts +39 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/global-search/index.ts +33 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/form.tsx +208 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/index.ts +22 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useObjectInfoBatch.ts +65 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useObjectSearchData.ts +380 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useRecordDetailLayout.ts +156 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useRecordListGraphQL.ts +135 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/lib/utils.ts +6 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/DetailPage.tsx +109 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/GlobalSearch.tsx +229 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/Home.tsx +11 -10
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/routes.tsx +22 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/styles/global.css +122 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/filters/filters.ts +120 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/filters/picklist.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/index.ts +4 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/objectInfo/objectInfo.ts +166 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/recordDetail/recordDetail.ts +61 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/search/searchResults.ts +229 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/apiUtils.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/cacheUtils.ts +76 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/debounce.ts +89 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/fieldUtils.ts +354 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/fieldValueExtractor.ts +67 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/filterUtils.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/formDataTransformUtils.ts +260 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/formUtils.ts +142 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLNodeFieldUtils.ts +186 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLObjectInfoAdapter.ts +319 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLRecordAdapter.ts +90 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/index.ts +59 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/layoutTransformUtils.ts +236 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/linkUtils.ts +14 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/paginationUtils.ts +49 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/recordUtils.ts +159 -0
- package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/sanitizationUtils.ts +49 -0
- package/dist/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchResultsPanel Component
|
|
3
|
+
*
|
|
4
|
+
* Displays the search results panel with loading, error, and empty states.
|
|
5
|
+
* Renders a list of SearchResultCard components and pagination controls.
|
|
6
|
+
*
|
|
7
|
+
* @param columns - Array of column definitions for displaying result data
|
|
8
|
+
* @param results - Array of search result records to display
|
|
9
|
+
* @param columnsLoading - Whether column metadata is currently loading
|
|
10
|
+
* @param resultsLoading - Whether search results are currently loading
|
|
11
|
+
* @param columnsError - Error message if column fetch failed
|
|
12
|
+
* @param resultsError - Error message if results fetch failed
|
|
13
|
+
* @param currentPageToken - Current pagination token
|
|
14
|
+
* @param pageSize - Number of results per page
|
|
15
|
+
* @param onPageChange - Callback when pagination changes; second arg optional: "next" | "prev" | "first" (cursor-stack pagination)
|
|
16
|
+
* @param onPageSizeChange - Callback when page size changes
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <SearchResultsPanel
|
|
21
|
+
* columns={columns}
|
|
22
|
+
* results={results}
|
|
23
|
+
* columnsLoading={false}
|
|
24
|
+
* resultsLoading={false}
|
|
25
|
+
* columnsError={null}
|
|
26
|
+
* resultsError={null}
|
|
27
|
+
* currentPageToken="0"
|
|
28
|
+
* pageSize={25}
|
|
29
|
+
* onPageChange={(token, direction) => handlePageChange(token, direction)}
|
|
30
|
+
* onPageSizeChange={(size) => setPageSize(size)}
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import { useMemo } from "react";
|
|
35
|
+
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
|
36
|
+
import { Skeleton } from "../ui/skeleton";
|
|
37
|
+
import { AlertCircle } from "lucide-react";
|
|
38
|
+
import {
|
|
39
|
+
Select,
|
|
40
|
+
SelectContent,
|
|
41
|
+
SelectItem,
|
|
42
|
+
SelectTrigger,
|
|
43
|
+
SelectValue,
|
|
44
|
+
} from "../ui/select";
|
|
45
|
+
import { Label } from "../ui/label";
|
|
46
|
+
import SearchResultCard from "../SearchResultCard";
|
|
47
|
+
import SearchPagination from "./SearchPagination";
|
|
48
|
+
import type { Column, SearchResultRecord } from "../../types/search/searchResults";
|
|
49
|
+
import { getSafeKey } from "../../utils/recordUtils";
|
|
50
|
+
|
|
51
|
+
interface SearchResultsPanelProps {
|
|
52
|
+
/** API name of the object being searched (e.g. for detail page navigation). */
|
|
53
|
+
objectApiName?: string;
|
|
54
|
+
columns: Column[];
|
|
55
|
+
results: SearchResultRecord[];
|
|
56
|
+
columnsLoading: boolean;
|
|
57
|
+
resultsLoading: boolean;
|
|
58
|
+
columnsError: string | null;
|
|
59
|
+
resultsError: string | null;
|
|
60
|
+
currentPageToken: string;
|
|
61
|
+
nextPageToken: string | null;
|
|
62
|
+
previousPageToken: string | null;
|
|
63
|
+
hasNextPage?: boolean;
|
|
64
|
+
hasPreviousPage?: boolean;
|
|
65
|
+
pageSize: number;
|
|
66
|
+
sortBy: string;
|
|
67
|
+
onPageChange: (newPageToken: string, direction?: "next" | "prev" | "first") => void;
|
|
68
|
+
onPageSizeChange: (newPageSize: number) => void;
|
|
69
|
+
onSortByChange: (newSortBy: string) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function SearchResultsPanel({
|
|
73
|
+
objectApiName,
|
|
74
|
+
columns,
|
|
75
|
+
results,
|
|
76
|
+
columnsLoading,
|
|
77
|
+
resultsLoading,
|
|
78
|
+
columnsError,
|
|
79
|
+
resultsError,
|
|
80
|
+
currentPageToken,
|
|
81
|
+
nextPageToken,
|
|
82
|
+
previousPageToken,
|
|
83
|
+
hasNextPage = false,
|
|
84
|
+
hasPreviousPage = false,
|
|
85
|
+
pageSize,
|
|
86
|
+
sortBy,
|
|
87
|
+
onPageChange,
|
|
88
|
+
onPageSizeChange,
|
|
89
|
+
onSortByChange,
|
|
90
|
+
}: SearchResultsPanelProps) {
|
|
91
|
+
const sortableColumns = useMemo(() => columns.filter(({ sortable }) => sortable), [columns]);
|
|
92
|
+
|
|
93
|
+
const validResults = useMemo(
|
|
94
|
+
() => results.filter((record) => record && record.record && record.record.id),
|
|
95
|
+
[results],
|
|
96
|
+
);
|
|
97
|
+
if (columnsError || resultsError) {
|
|
98
|
+
return (
|
|
99
|
+
<Alert variant="destructive" role="alert">
|
|
100
|
+
<AlertCircle className="h-4 w-4" aria-hidden="true" />
|
|
101
|
+
<AlertTitle>Error</AlertTitle>
|
|
102
|
+
<AlertDescription>
|
|
103
|
+
{columnsError || resultsError || "Failed to load search results"}
|
|
104
|
+
</AlertDescription>
|
|
105
|
+
</Alert>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (resultsLoading || columnsLoading) {
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
className="space-y-4"
|
|
113
|
+
role="status"
|
|
114
|
+
aria-live="polite"
|
|
115
|
+
aria-label="Loading search results"
|
|
116
|
+
>
|
|
117
|
+
<span className="sr-only">Loading search results</span>
|
|
118
|
+
{[1, 2, 3].map((i) => (
|
|
119
|
+
<div key={i} className="border rounded-lg p-6" aria-hidden="true">
|
|
120
|
+
<Skeleton className="h-6 w-3/4 mb-4" />
|
|
121
|
+
<Skeleton className="h-4 w-full mb-2" />
|
|
122
|
+
<Skeleton className="h-4 w-2/3" />
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (results.length === 0) {
|
|
130
|
+
return (
|
|
131
|
+
<div className="text-center py-12" role="status" aria-live="polite">
|
|
132
|
+
<p className="text-lg mb-2">No results found</p>
|
|
133
|
+
<p className="text-sm">Try adjusting your search query or filters</p>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<>
|
|
140
|
+
{sortableColumns.length > 0 && (
|
|
141
|
+
<div
|
|
142
|
+
className="mb-6 flex items-center gap-2 justify-end"
|
|
143
|
+
role="group"
|
|
144
|
+
aria-label="Sort options"
|
|
145
|
+
>
|
|
146
|
+
<Label htmlFor="sort-by-select" className="text-sm font-normal whitespace-nowrap">
|
|
147
|
+
Sort by:
|
|
148
|
+
</Label>
|
|
149
|
+
<Select value={sortBy || ""} onValueChange={onSortByChange}>
|
|
150
|
+
<SelectTrigger
|
|
151
|
+
id="sort-by-select"
|
|
152
|
+
className="w-[200px]"
|
|
153
|
+
aria-label="Sort search results by field"
|
|
154
|
+
>
|
|
155
|
+
<SelectValue placeholder="Select field..." />
|
|
156
|
+
</SelectTrigger>
|
|
157
|
+
<SelectContent>
|
|
158
|
+
<SelectItem value="relevance">Relevance</SelectItem>
|
|
159
|
+
{sortableColumns.map((column) => (
|
|
160
|
+
<SelectItem key={column.fieldApiName} value={column.fieldApiName}>
|
|
161
|
+
{column.label}
|
|
162
|
+
</SelectItem>
|
|
163
|
+
))}
|
|
164
|
+
</SelectContent>
|
|
165
|
+
</Select>
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
<div className="space-y-4 mb-6" role="list" aria-label="Search results list">
|
|
170
|
+
{validResults.map((record, index) => {
|
|
171
|
+
const recordId = record.record.id;
|
|
172
|
+
const safeKey = getSafeKey(recordId, index);
|
|
173
|
+
return (
|
|
174
|
+
<div key={safeKey} role="listitem">
|
|
175
|
+
<SearchResultCard
|
|
176
|
+
record={record.record}
|
|
177
|
+
columns={columns}
|
|
178
|
+
objectApiName={objectApiName}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
})}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<SearchPagination
|
|
186
|
+
currentPageToken={currentPageToken}
|
|
187
|
+
nextPageToken={nextPageToken}
|
|
188
|
+
previousPageToken={previousPageToken}
|
|
189
|
+
hasNextPage={hasNextPage}
|
|
190
|
+
hasPreviousPage={hasPreviousPage}
|
|
191
|
+
pageSize={pageSize}
|
|
192
|
+
onPageChange={onPageChange}
|
|
193
|
+
onPageSizeChange={onPageSizeChange}
|
|
194
|
+
/>
|
|
195
|
+
</>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GlobalSearchInput Component
|
|
3
|
+
*
|
|
4
|
+
* Search input with two actions: Search (navigate to results for query) and
|
|
5
|
+
* Browse All (navigate to same results UI with all records for the object).
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useCallback, useMemo, useId } from "react";
|
|
8
|
+
import type { KeyboardEvent, ChangeEvent } from "react";
|
|
9
|
+
import { useNavigate } from "react-router";
|
|
10
|
+
import { Card, CardContent } from "../ui/card";
|
|
11
|
+
import { Input } from "../ui/input";
|
|
12
|
+
import { Button } from "../ui/button";
|
|
13
|
+
import { Search } from "lucide-react";
|
|
14
|
+
import { OBJECT_API_NAMES } from "../../constants";
|
|
15
|
+
import { useObjectInfoBatch } from "../../hooks/useObjectInfoBatch";
|
|
16
|
+
|
|
17
|
+
const BROWSE_SEGMENT = "browse__all";
|
|
18
|
+
|
|
19
|
+
const FALLBACK_LABEL_PLURAL = "records";
|
|
20
|
+
|
|
21
|
+
export function GlobalSearchInput() {
|
|
22
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
23
|
+
const navigate = useNavigate();
|
|
24
|
+
const inputId = useId();
|
|
25
|
+
const searchButtonId = useId();
|
|
26
|
+
const browseButtonId = useId();
|
|
27
|
+
const inputDescriptionId = `${inputId}-description`;
|
|
28
|
+
const { objectInfos } = useObjectInfoBatch([...OBJECT_API_NAMES]);
|
|
29
|
+
const labelPlural = (objectInfos[0]?.labelPlural as string | undefined) ?? FALLBACK_LABEL_PLURAL;
|
|
30
|
+
|
|
31
|
+
const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
|
32
|
+
setSearchQuery(e.target.value);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const handleSearch = useCallback(() => {
|
|
36
|
+
const trimmed = searchQuery.trim();
|
|
37
|
+
if (trimmed) {
|
|
38
|
+
navigate(`/global-search/${encodeURIComponent(trimmed)}`);
|
|
39
|
+
}
|
|
40
|
+
}, [searchQuery, navigate]);
|
|
41
|
+
|
|
42
|
+
const handleBrowseAll = useCallback(() => {
|
|
43
|
+
navigate(`/global-search/${BROWSE_SEGMENT}`);
|
|
44
|
+
}, [navigate]);
|
|
45
|
+
|
|
46
|
+
const handleKeyDown = useCallback(
|
|
47
|
+
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
48
|
+
if (e.key === "Enter") {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
handleSearch();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[handleSearch],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const isSearchDisabled = useMemo(() => !searchQuery.trim(), [searchQuery]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="w-full max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
60
|
+
<Card className="w-full">
|
|
61
|
+
<CardContent className="pt-6">
|
|
62
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
63
|
+
<div className="flex-1 relative">
|
|
64
|
+
<Search
|
|
65
|
+
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-600"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
/>
|
|
68
|
+
<Input
|
|
69
|
+
id={inputId}
|
|
70
|
+
type="search"
|
|
71
|
+
placeholder={`Search for ${labelPlural}`}
|
|
72
|
+
value={searchQuery}
|
|
73
|
+
onChange={handleInputChange}
|
|
74
|
+
onKeyDown={handleKeyDown}
|
|
75
|
+
className="pl-10"
|
|
76
|
+
aria-label={`Search for ${labelPlural}`}
|
|
77
|
+
aria-describedby={inputDescriptionId}
|
|
78
|
+
/>
|
|
79
|
+
<p id={inputDescriptionId} className="sr-only">
|
|
80
|
+
Enter your search query and press Enter or click Search. Or click Browse All to see
|
|
81
|
+
all {labelPlural}.
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
|
86
|
+
<Button
|
|
87
|
+
id={searchButtonId}
|
|
88
|
+
onClick={handleSearch}
|
|
89
|
+
disabled={isSearchDisabled}
|
|
90
|
+
className="w-full sm:w-auto"
|
|
91
|
+
aria-label="Search"
|
|
92
|
+
aria-describedby={inputDescriptionId}
|
|
93
|
+
variant="default"
|
|
94
|
+
>
|
|
95
|
+
<Search className="h-4 w-4 mr-2" aria-hidden="true" />
|
|
96
|
+
Search
|
|
97
|
+
</Button>
|
|
98
|
+
<Button
|
|
99
|
+
id={browseButtonId}
|
|
100
|
+
variant="outline"
|
|
101
|
+
onClick={handleBrowseAll}
|
|
102
|
+
className="w-full sm:w-auto"
|
|
103
|
+
aria-label={`Browse all ${labelPlural}`}
|
|
104
|
+
aria-describedby={inputDescriptionId}
|
|
105
|
+
>
|
|
106
|
+
Browse All {labelPlural}
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</CardContent>
|
|
111
|
+
</Card>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/alert.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-card text-card-foreground",
|
|
12
|
+
destructive:
|
|
13
|
+
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "default",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
function Alert({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="alert"
|
|
30
|
+
role="alert"
|
|
31
|
+
className={cn(alertVariants({ variant }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="alert-title"
|
|
41
|
+
className={cn(
|
|
42
|
+
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="alert-description"
|
|
54
|
+
className={cn(
|
|
55
|
+
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
64
|
+
return (
|
|
65
|
+
<div data-slot="alert-action" className={cn("absolute top-2 right-2", className)} {...props} />
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction };
|
package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/button.tsx
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Slot } from "radix-ui";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
outline:
|
|
14
|
+
"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
15
|
+
secondary:
|
|
16
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
17
|
+
ghost:
|
|
18
|
+
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
default:
|
|
25
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
26
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
28
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
29
|
+
icon: "size-8",
|
|
30
|
+
"icon-xs":
|
|
31
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
32
|
+
"icon-sm":
|
|
33
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
34
|
+
"icon-lg": "size-9",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
size: "default",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function Button({
|
|
45
|
+
className,
|
|
46
|
+
variant = "default",
|
|
47
|
+
size = "default",
|
|
48
|
+
asChild = false,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<"button"> &
|
|
51
|
+
VariantProps<typeof buttonVariants> & {
|
|
52
|
+
asChild?: boolean;
|
|
53
|
+
}) {
|
|
54
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Comp
|
|
58
|
+
data-slot="button"
|
|
59
|
+
data-variant={variant}
|
|
60
|
+
data-size={size}
|
|
61
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
62
|
+
{...(props as any)}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { Button, buttonVariants };
|
package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/card.tsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
function Card({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
data-size={size}
|
|
14
|
+
className={cn(
|
|
15
|
+
"ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
data-slot="card-header"
|
|
27
|
+
className={cn(
|
|
28
|
+
"gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
data-slot="card-title"
|
|
40
|
+
className={cn(
|
|
41
|
+
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
data-slot="card-description"
|
|
53
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
data-slot="card-action"
|
|
63
|
+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="card-content"
|
|
73
|
+
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
data-slot="card-footer"
|
|
83
|
+
className={cn(
|
|
84
|
+
"bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
|
package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/dialog.tsx
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
import { Button } from "./button";
|
|
6
|
+
import { XIcon } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
9
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
13
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
17
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
21
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DialogOverlay({
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
28
|
+
return (
|
|
29
|
+
<DialogPrimitive.Overlay
|
|
30
|
+
data-slot="dialog-overlay"
|
|
31
|
+
className={cn(
|
|
32
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function DialogContent({
|
|
41
|
+
className,
|
|
42
|
+
children,
|
|
43
|
+
showCloseButton = true,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
46
|
+
showCloseButton?: boolean;
|
|
47
|
+
}) {
|
|
48
|
+
return (
|
|
49
|
+
<DialogPortal>
|
|
50
|
+
<DialogOverlay />
|
|
51
|
+
<DialogPrimitive.Content
|
|
52
|
+
data-slot="dialog-content"
|
|
53
|
+
className={cn(
|
|
54
|
+
"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
{showCloseButton && (
|
|
61
|
+
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
|
62
|
+
<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm">
|
|
63
|
+
<XIcon />
|
|
64
|
+
<span className="sr-only">Close</span>
|
|
65
|
+
</Button>
|
|
66
|
+
</DialogPrimitive.Close>
|
|
67
|
+
)}
|
|
68
|
+
</DialogPrimitive.Content>
|
|
69
|
+
</DialogPortal>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div data-slot="dialog-header" className={cn("gap-2 flex flex-col", className)} {...props} />
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function DialogFooter({
|
|
80
|
+
className,
|
|
81
|
+
showCloseButton = false,
|
|
82
|
+
children,
|
|
83
|
+
...props
|
|
84
|
+
}: React.ComponentProps<"div"> & {
|
|
85
|
+
showCloseButton?: boolean;
|
|
86
|
+
}) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="dialog-footer"
|
|
90
|
+
className={cn(
|
|
91
|
+
"bg-muted/50 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
>
|
|
96
|
+
{children}
|
|
97
|
+
{showCloseButton && (
|
|
98
|
+
<DialogPrimitive.Close asChild>
|
|
99
|
+
<Button variant="outline">Close</Button>
|
|
100
|
+
</DialogPrimitive.Close>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
107
|
+
return (
|
|
108
|
+
<DialogPrimitive.Title
|
|
109
|
+
data-slot="dialog-title"
|
|
110
|
+
className={cn("text-base leading-none font-medium", className)}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function DialogDescription({
|
|
117
|
+
className,
|
|
118
|
+
...props
|
|
119
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
120
|
+
return (
|
|
121
|
+
<DialogPrimitive.Description
|
|
122
|
+
data-slot="dialog-description"
|
|
123
|
+
className={cn(
|
|
124
|
+
"text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3",
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
Dialog,
|
|
134
|
+
DialogClose,
|
|
135
|
+
DialogContent,
|
|
136
|
+
DialogDescription,
|
|
137
|
+
DialogFooter,
|
|
138
|
+
DialogHeader,
|
|
139
|
+
DialogOverlay,
|
|
140
|
+
DialogPortal,
|
|
141
|
+
DialogTitle,
|
|
142
|
+
DialogTrigger,
|
|
143
|
+
};
|