@salesforce/ui-bundle-template-app-react-sample-b2x 11.68.0 → 11.68.2

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 (38) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/uiBundles/propertyrentalapp/package.json +4 -4
  3. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/api/objectSearchService.ts +2 -8
  4. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useObjectSearchParams.ts +1 -19
  5. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/debounce.ts +1 -12
  6. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/filterUtils.ts +8 -123
  7. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/sortUtils.ts +1 -17
  8. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearch.tsx +3 -6
  9. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/routes.tsx +0 -10
  10. package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.tsbuildinfo +1 -1
  11. package/dist/package-lock.json +2 -2
  12. package/dist/package.json +1 -1
  13. package/package.json +4 -1
  14. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/accountSearchService.ts +0 -46
  15. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +0 -19
  16. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +0 -19
  17. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +0 -121
  18. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +0 -51
  19. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +0 -355
  20. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +0 -304
  21. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/Home.tsx +0 -34
  22. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ActiveFilters.tsx +0 -89
  23. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/FilterContext.tsx +0 -83
  24. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +0 -66
  25. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SearchBar.tsx +0 -41
  26. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SortControl.tsx +0 -143
  27. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/BooleanFilter.tsx +0 -78
  28. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateFilter.tsx +0 -128
  29. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +0 -70
  30. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +0 -43
  31. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +0 -105
  32. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +0 -171
  33. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SearchFilter.tsx +0 -52
  34. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SelectFilter.tsx +0 -97
  35. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/TextFilter.tsx +0 -93
  36. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useAsyncData.ts +0 -63
  37. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useDebouncedCallback.ts +0 -34
  38. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/fieldUtils.ts +0 -29
@@ -1,171 +0,0 @@
1
- import { useId, useState } from "react";
2
- import { Input } from "../../../../components/ui/input";
3
-
4
- import { useFilterField } from "../FilterContext";
5
- import { useDebouncedCallback } from "../../hooks/useDebouncedCallback";
6
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
7
- import type { ActiveFilterValue } from "../../utils/filterUtils";
8
-
9
- interface NumericRangeFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
10
- field: string;
11
- label: string;
12
- helpText?: string;
13
- min?: number;
14
- max?: number;
15
- }
16
-
17
- export function NumericRangeFilter({
18
- field,
19
- label,
20
- helpText,
21
- min,
22
- max,
23
- className,
24
- ...props
25
- }: NumericRangeFilterProps) {
26
- const { value, onChange } = useFilterField(field);
27
- return (
28
- <NumericRangeFilterInputs
29
- field={field}
30
- label={label}
31
- helpText={helpText}
32
- value={value}
33
- onChange={onChange}
34
- min={min}
35
- max={max}
36
- className={className}
37
- {...props}
38
- />
39
- );
40
- }
41
-
42
- interface NumericRangeFilterInputsProps extends Omit<React.ComponentProps<"div">, "onChange"> {
43
- field: string;
44
- label: string;
45
- helpText?: string;
46
- value: ActiveFilterValue | undefined;
47
- onChange: (value: ActiveFilterValue | undefined) => void;
48
- min?: number;
49
- max?: number;
50
- minInputProps?: React.ComponentProps<typeof Input>;
51
- maxInputProps?: React.ComponentProps<typeof Input>;
52
- }
53
-
54
- export function NumericRangeFilterInputs({
55
- field,
56
- label,
57
- helpText,
58
- value,
59
- onChange,
60
- min: boundMin,
61
- max: boundMax,
62
- className,
63
- ...props
64
- }: NumericRangeFilterInputsProps) {
65
- const errorId = useId();
66
- const [localMin, setLocalMin] = useState(value?.min ?? "");
67
- const [localMax, setLocalMax] = useState(value?.max ?? "");
68
-
69
- const externalMin = value?.min ?? "";
70
- const externalMax = value?.max ?? "";
71
- const [prevExternalMin, setPrevExternalMin] = useState(externalMin);
72
- const [prevExternalMax, setPrevExternalMax] = useState(externalMax);
73
- if (prevExternalMin !== externalMin) {
74
- setPrevExternalMin(externalMin);
75
- setLocalMin(externalMin);
76
- }
77
- if (prevExternalMax !== externalMax) {
78
- setPrevExternalMax(externalMax);
79
- setLocalMax(externalMax);
80
- }
81
-
82
- const isOutOfBounds = (v: string) => {
83
- if (v === "") return false;
84
- const n = Number(v);
85
- return (boundMin != null && n < boundMin) || (boundMax != null && n > boundMax);
86
- };
87
- const minOutOfBounds = isOutOfBounds(localMin);
88
- const maxOutOfBounds = isOutOfBounds(localMax);
89
- const isRangeInverted = localMin !== "" && localMax !== "" && Number(localMin) > Number(localMax);
90
- const hasError = minOutOfBounds || maxOutOfBounds || isRangeInverted;
91
-
92
- const debouncedOnChange = useDebouncedCallback((min: string, max: string) => {
93
- if (!min && !max) {
94
- onChange(undefined);
95
- return;
96
- }
97
- const minNum = min !== "" ? Number(min) : null;
98
- const maxNum = max !== "" ? Number(max) : null;
99
- if (minNum != null && maxNum != null && minNum > maxNum) return;
100
- if (
101
- minNum != null &&
102
- ((boundMin != null && minNum < boundMin) || (boundMax != null && minNum > boundMax))
103
- )
104
- return;
105
- if (
106
- maxNum != null &&
107
- ((boundMin != null && maxNum < boundMin) || (boundMax != null && maxNum > boundMax))
108
- )
109
- return;
110
- onChange({ field, label, type: "numeric" as const, min, max });
111
- });
112
-
113
- const boundsLabel =
114
- boundMin != null && boundMax != null
115
- ? `${boundMin}–${boundMax}`
116
- : boundMin != null
117
- ? `${boundMin} or more`
118
- : boundMax != null
119
- ? `${boundMax} or less`
120
- : null;
121
-
122
- const errorMessage = isRangeInverted
123
- ? "Min must not exceed max"
124
- : (minOutOfBounds || maxOutOfBounds) && boundsLabel
125
- ? `Value must be between ${boundsLabel}`
126
- : undefined;
127
-
128
- return (
129
- <FilterFieldWrapper
130
- label={label}
131
- helpText={helpText}
132
- error={errorMessage}
133
- errorId={errorId}
134
- className={className}
135
- {...props}
136
- >
137
- <div className="flex gap-2">
138
- <Input
139
- type="number"
140
- placeholder="Min"
141
- value={localMin}
142
- min={boundMin}
143
- max={boundMax}
144
- onChange={(e) => {
145
- const v = e.target.value;
146
- setLocalMin(v);
147
- debouncedOnChange(v, localMax);
148
- }}
149
- aria-label={`${label} minimum`}
150
- aria-invalid={hasError || undefined}
151
- aria-describedby={hasError ? errorId : undefined}
152
- />
153
- <Input
154
- type="number"
155
- placeholder="Max"
156
- value={localMax}
157
- min={boundMin}
158
- max={boundMax}
159
- onChange={(e) => {
160
- const v = e.target.value;
161
- setLocalMax(v);
162
- debouncedOnChange(localMin, v);
163
- }}
164
- aria-label={`${label} maximum`}
165
- aria-invalid={hasError || undefined}
166
- aria-describedby={hasError ? errorId : undefined}
167
- />
168
- </div>
169
- </FilterFieldWrapper>
170
- );
171
- }
@@ -1,52 +0,0 @@
1
- import { useState } from "react";
2
-
3
- import { SearchBar } from "../SearchBar";
4
- import { useFilterField } from "../FilterContext";
5
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
6
- import { useDebouncedCallback } from "../../hooks/useDebouncedCallback";
7
-
8
- interface SearchFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
9
- field: string;
10
- label: string;
11
- placeholder?: string;
12
- }
13
-
14
- export function SearchFilter({
15
- field,
16
- label,
17
- placeholder,
18
- className,
19
- ...props
20
- }: SearchFilterProps) {
21
- const { value, onChange } = useFilterField(field);
22
- const [localValue, setLocalValue] = useState(value?.value ?? "");
23
-
24
- const externalValue = value?.value ?? "";
25
- const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
26
- if (prevExternalValue !== externalValue) {
27
- setPrevExternalValue(externalValue);
28
- setLocalValue(externalValue);
29
- }
30
-
31
- const debouncedOnChange = useDebouncedCallback((v: string) => {
32
- if (v) {
33
- onChange({ field, label, type: "search", value: v });
34
- } else {
35
- onChange(undefined);
36
- }
37
- });
38
-
39
- return (
40
- <FilterFieldWrapper label={label} htmlFor={`filter-${field}`} className={className} {...props}>
41
- <SearchBar
42
- value={localValue}
43
- handleChange={(v) => {
44
- setLocalValue(v);
45
- debouncedOnChange(v);
46
- }}
47
- placeholder={placeholder}
48
- inputProps={{ id: `filter-${field}` }}
49
- />
50
- </FilterFieldWrapper>
51
- );
52
- }
@@ -1,97 +0,0 @@
1
- import {
2
- Select,
3
- SelectContent,
4
- SelectItem,
5
- SelectTrigger,
6
- SelectValue,
7
- } from "../../../../components/ui/select";
8
- import { cn } from "../../../../lib/utils";
9
- import { useFilterField } from "../FilterContext";
10
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
11
- import type { ActiveFilterValue } from "../../utils/filterUtils";
12
-
13
- const ALL_VALUE = "__all__";
14
-
15
- interface SelectFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
16
- field: string;
17
- label: string;
18
- options: Array<{ value: string; label: string }>;
19
- helpText?: string;
20
- }
21
-
22
- export function SelectFilter({
23
- field,
24
- label,
25
- options,
26
- helpText,
27
- className,
28
- ...props
29
- }: SelectFilterProps) {
30
- const { value, onChange } = useFilterField(field);
31
- return (
32
- <FilterFieldWrapper
33
- label={label}
34
- htmlFor={`filter-${field}`}
35
- helpText={helpText}
36
- className={className}
37
- {...props}
38
- >
39
- <SelectFilterControl
40
- field={field}
41
- label={label}
42
- options={options}
43
- value={value}
44
- onChange={onChange}
45
- />
46
- </FilterFieldWrapper>
47
- );
48
- }
49
-
50
- interface SelectFilterControlProps {
51
- field: string;
52
- label: string;
53
- options: Array<{ value: string; label: string }>;
54
- value: ActiveFilterValue | undefined;
55
- onChange: (value: ActiveFilterValue | undefined) => void;
56
- triggerProps?: React.ComponentProps<typeof SelectTrigger>;
57
- contentProps?: React.ComponentProps<typeof SelectContent>;
58
- }
59
-
60
- export function SelectFilterControl({
61
- field,
62
- label,
63
- options,
64
- value,
65
- onChange,
66
- triggerProps,
67
- contentProps,
68
- }: SelectFilterControlProps) {
69
- return (
70
- <Select
71
- value={value?.value ?? ALL_VALUE}
72
- onValueChange={(v) => {
73
- if (v === ALL_VALUE) {
74
- onChange(undefined);
75
- } else {
76
- onChange({ field, label, type: "picklist", value: v });
77
- }
78
- }}
79
- >
80
- <SelectTrigger
81
- id={`filter-${field}`}
82
- {...triggerProps}
83
- className={cn("w-full", triggerProps?.className)}
84
- >
85
- <SelectValue placeholder={`Select ${label.toLowerCase()}`} />
86
- </SelectTrigger>
87
- <SelectContent {...contentProps}>
88
- <SelectItem value={ALL_VALUE}>All</SelectItem>
89
- {options.map((opt) => (
90
- <SelectItem key={opt.value} value={opt.value}>
91
- {opt.label}
92
- </SelectItem>
93
- ))}
94
- </SelectContent>
95
- </Select>
96
- );
97
- }
@@ -1,93 +0,0 @@
1
- import { useState } from "react";
2
- import { Input } from "../../../../components/ui/input";
3
- import { cn } from "../../../../lib/utils";
4
- import { useFilterField } from "../FilterContext";
5
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
6
- import { useDebouncedCallback } from "../../hooks/useDebouncedCallback";
7
- import type { ActiveFilterValue } from "../../utils/filterUtils";
8
-
9
- interface TextFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
10
- field: string;
11
- label: string;
12
- placeholder?: string;
13
- helpText?: string;
14
- }
15
-
16
- export function TextFilter({
17
- field,
18
- label,
19
- placeholder,
20
- helpText,
21
- className,
22
- ...props
23
- }: TextFilterProps) {
24
- const { value, onChange } = useFilterField(field);
25
- return (
26
- <FilterFieldWrapper
27
- label={label}
28
- htmlFor={`filter-${field}`}
29
- helpText={helpText}
30
- className={className}
31
- {...props}
32
- >
33
- <TextFilterInput
34
- field={field}
35
- label={label}
36
- placeholder={placeholder}
37
- value={value}
38
- onChange={onChange}
39
- />
40
- </FilterFieldWrapper>
41
- );
42
- }
43
-
44
- interface TextFilterInputProps extends Omit<
45
- React.ComponentProps<typeof Input>,
46
- "onChange" | "value"
47
- > {
48
- field: string;
49
- label: string;
50
- value: ActiveFilterValue | undefined;
51
- onChange: (value: ActiveFilterValue | undefined) => void;
52
- }
53
-
54
- export function TextFilterInput({
55
- field,
56
- label,
57
- value,
58
- onChange,
59
- className,
60
- ...props
61
- }: TextFilterInputProps) {
62
- const [localValue, setLocalValue] = useState(value?.value ?? "");
63
-
64
- const externalValue = value?.value ?? "";
65
- const [prevExternalValue, setPrevExternalValue] = useState(externalValue);
66
- if (prevExternalValue !== externalValue) {
67
- setPrevExternalValue(externalValue);
68
- setLocalValue(externalValue);
69
- }
70
-
71
- const debouncedOnChange = useDebouncedCallback((v: string) => {
72
- if (v) {
73
- onChange({ field, label, type: "text", value: v });
74
- } else {
75
- onChange(undefined);
76
- }
77
- });
78
-
79
- return (
80
- <Input
81
- id={`filter-${field}`}
82
- type="text"
83
- placeholder={props.placeholder ?? `Filter by ${label.toLowerCase()}...`}
84
- value={localValue}
85
- onChange={(e) => {
86
- setLocalValue(e.target.value);
87
- debouncedOnChange(e.target.value);
88
- }}
89
- className={cn(className)}
90
- {...props}
91
- />
92
- );
93
- }
@@ -1,63 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
-
3
- interface UseAsyncDataResult<T> {
4
- data: T | null;
5
- loading: boolean;
6
- error: string | null;
7
- }
8
-
9
- /**
10
- * Runs an async fetcher on mount and whenever `deps` change.
11
- * Returns the loading/error/data state. Does not cache — every call
12
- * to the fetcher hits the source directly.
13
- *
14
- * A cleanup flag prevents state updates if the component unmounts
15
- * or deps change before the fetch completes (avoids React warnings
16
- * and stale updates from out-of-order responses).
17
- */
18
- export function useAsyncData<T>(
19
- fetcher: () => Promise<T>,
20
- deps: React.DependencyList,
21
- ): UseAsyncDataResult<T> {
22
- const [data, setData] = useState<T | null>(null);
23
- const [loading, setLoading] = useState(true);
24
- const [error, setError] = useState<string | null>(null);
25
- const [generation, setGeneration] = useState(0);
26
-
27
- const fetcherRef = useRef(fetcher);
28
- useEffect(() => {
29
- fetcherRef.current = fetcher;
30
- });
31
-
32
- // Detect dep changes during render to reset loading state and bump generation
33
- const [prevDeps, setPrevDeps] = useState(deps);
34
- if (deps.length !== prevDeps.length || deps.some((d, i) => d !== prevDeps[i])) {
35
- setPrevDeps(deps);
36
- setGeneration((g) => g + 1);
37
- if (!loading) setLoading(true);
38
- if (error !== null) setError(null);
39
- }
40
-
41
- useEffect(() => {
42
- let cancelled = false;
43
-
44
- fetcherRef
45
- .current()
46
- .then((result) => {
47
- if (!cancelled) setData(result);
48
- })
49
- .catch((err) => {
50
- console.error(err);
51
- if (!cancelled) setError(err instanceof Error ? err.message : "An error occurred");
52
- })
53
- .finally(() => {
54
- if (!cancelled) setLoading(false);
55
- });
56
-
57
- return () => {
58
- cancelled = true;
59
- };
60
- }, [generation]);
61
-
62
- return { data, loading, error };
63
- }
@@ -1,34 +0,0 @@
1
- import { useCallback, useEffect, useRef } from "react";
2
- import { debounce, FILTER_DEBOUNCE_MS } from "../utils/debounce";
3
-
4
- /**
5
- * Returns a stable debounced wrapper around the provided callback.
6
- *
7
- * The wrapper always invokes the *latest* version of `fn` (via a ref),
8
- * so the debounce timer is never reset when `fn` changes — only when
9
- * the caller invokes the returned function again.
10
- *
11
- * @param fn - The callback to debounce.
12
- * @param delay - Debounce delay in ms. Defaults to `FILTER_DEBOUNCE_MS`.
13
- */
14
- export function useDebouncedCallback<T extends (...args: any[]) => void>(
15
- fn: T,
16
- delay: number = FILTER_DEBOUNCE_MS,
17
- ): (...args: Parameters<T>) => void {
18
- const fnRef = useRef(fn);
19
- const debouncedRef = useRef<((...args: any[]) => void) | null>(null);
20
-
21
- useEffect(() => {
22
- fnRef.current = fn;
23
- });
24
-
25
- useEffect(() => {
26
- debouncedRef.current = debounce((...args: any[]) => {
27
- fnRef.current(...(args as Parameters<T>));
28
- }, delay);
29
- }, [delay]);
30
-
31
- return useCallback((...args: Parameters<T>) => {
32
- debouncedRef.current?.(...args);
33
- }, []);
34
- }
@@ -1,29 +0,0 @@
1
- export function fieldValue(
2
- field: { displayValue?: string | null; value?: unknown } | null | undefined,
3
- ): string | null {
4
- if (field?.displayValue != null) return field.displayValue;
5
- if (field?.value != null) return String(field.value);
6
- return null;
7
- }
8
-
9
- export function getAddressFieldLines(address: {
10
- street?: string | null;
11
- city?: string | null;
12
- state?: string | null;
13
- postalCode?: string | null;
14
- country?: string | null;
15
- }) {
16
- const cityStateZip = [address.city, address.state].filter(Boolean).join(", ");
17
- const cityStateZipLine = [cityStateZip, address.postalCode].filter(Boolean).join(" ");
18
- const lines = [address.street, cityStateZipLine, address.country].filter(Boolean);
19
- if (lines.length === 0) return null;
20
- return lines;
21
- }
22
-
23
- export function formatDateTimeField(
24
- value?: string | null,
25
- ...args: Parameters<Date["toLocaleString"]>
26
- ) {
27
- if (!value) return null;
28
- return new Date(value).toLocaleString(...args);
29
- }