@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.
Files changed (92) hide show
  1. package/dist/CHANGELOG.md +8 -0
  2. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package-lock.json +4255 -227
  3. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/package.json +11 -2
  4. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/index.ts +19 -0
  5. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectDetailService.ts +125 -0
  6. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectInfoGraphQLService.ts +194 -0
  7. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/objectInfoService.ts +199 -0
  8. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/api/recordListGraphQLService.ts +365 -0
  9. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/FiltersPanel.tsx +375 -0
  10. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/LoadingFallback.tsx +61 -0
  11. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/SearchResultCard.tsx +131 -0
  12. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/alerts/status-alert.tsx +45 -0
  13. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailFields.tsx +55 -0
  14. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailForm.tsx +146 -0
  15. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailHeader.tsx +34 -0
  16. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/DetailLayoutSections.tsx +80 -0
  17. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/Section.tsx +108 -0
  18. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/SectionRow.tsx +20 -0
  19. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/UiApiDetailForm.tsx +140 -0
  20. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  21. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedAddress.tsx +29 -0
  22. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedEmail.tsx +17 -0
  23. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedPhone.tsx +24 -0
  24. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedText.tsx +11 -0
  25. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/FormattedUrl.tsx +29 -0
  26. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/detail/formatted/index.ts +6 -0
  27. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterField.tsx +54 -0
  28. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterInput.tsx +55 -0
  29. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/filters/FilterSelect.tsx +72 -0
  30. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/forms/filters-form.tsx +114 -0
  31. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/forms/submit-button.tsx +47 -0
  32. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/layout/card-layout.tsx +19 -0
  33. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/ResultCardFields.tsx +71 -0
  34. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchHeader.tsx +31 -0
  35. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchPagination.tsx +144 -0
  36. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/search/SearchResultsPanel.tsx +197 -0
  37. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/shared/GlobalSearchInput.tsx +114 -0
  38. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/alert.tsx +69 -0
  39. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/button.tsx +67 -0
  40. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/card.tsx +92 -0
  41. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/dialog.tsx +143 -0
  42. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/field.tsx +222 -0
  43. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/index.ts +84 -0
  44. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/input.tsx +19 -0
  45. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/label.tsx +19 -0
  46. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/pagination.tsx +112 -0
  47. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/select.tsx +183 -0
  48. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/separator.tsx +26 -0
  49. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/skeleton.tsx +14 -0
  50. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/spinner.tsx +15 -0
  51. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/table.tsx +87 -0
  52. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components/ui/tabs.tsx +78 -0
  53. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/components.json +18 -0
  54. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/constants.ts +39 -0
  55. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/features/global-search/index.ts +33 -0
  56. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/form.tsx +208 -0
  57. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/index.ts +22 -0
  58. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useObjectInfoBatch.ts +65 -0
  59. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useObjectSearchData.ts +380 -0
  60. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useRecordDetailLayout.ts +156 -0
  61. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/hooks/useRecordListGraphQL.ts +135 -0
  62. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/lib/utils.ts +6 -0
  63. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/DetailPage.tsx +109 -0
  64. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/GlobalSearch.tsx +229 -0
  65. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/pages/Home.tsx +11 -10
  66. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/routes.tsx +22 -0
  67. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/styles/global.css +122 -0
  68. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/filters/filters.ts +120 -0
  69. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/filters/picklist.ts +32 -0
  70. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/index.ts +4 -0
  71. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/objectInfo/objectInfo.ts +166 -0
  72. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/recordDetail/recordDetail.ts +61 -0
  73. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/types/search/searchResults.ts +229 -0
  74. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/apiUtils.ts +125 -0
  75. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/cacheUtils.ts +76 -0
  76. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/debounce.ts +89 -0
  77. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/fieldUtils.ts +354 -0
  78. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/fieldValueExtractor.ts +67 -0
  79. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/filterUtils.ts +32 -0
  80. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/formDataTransformUtils.ts +260 -0
  81. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/formUtils.ts +142 -0
  82. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLNodeFieldUtils.ts +186 -0
  83. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLObjectInfoAdapter.ts +319 -0
  84. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/graphQLRecordAdapter.ts +90 -0
  85. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/index.ts +59 -0
  86. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/layoutTransformUtils.ts +236 -0
  87. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/linkUtils.ts +14 -0
  88. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/paginationUtils.ts +49 -0
  89. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/recordUtils.ts +159 -0
  90. package/dist/force-app/main/default/webapplications/appreacttemplateb2e/src/utils/sanitizationUtils.ts +49 -0
  91. package/dist/package.json +1 -1
  92. 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
+ }
@@ -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 };
@@ -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 };
@@ -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 };
@@ -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
+ };