@schandlergarcia/sf-web-components 2.3.17 → 2.5.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 (94) hide show
  1. package/.a4drules/skills/command-center-builder/SKILL.md +3 -2
  2. package/.a4drules/skills/component-library/SKILL.md +50 -4
  3. package/.a4drules/skills/component-library/card-components.md +88 -0
  4. package/.a4drules/skills/component-library/when-to-use.md +1 -0
  5. package/CHANGELOG.md +40 -0
  6. package/CLAUDE.md +12 -13
  7. package/README.md +0 -15
  8. package/dist/components/library/cards/KanbanBoard.js +313 -0
  9. package/dist/components/library/cards/KanbanBoard.js.map +1 -0
  10. package/dist/components/library/index.js +60 -57
  11. package/dist/components/library/index.js.map +1 -1
  12. package/dist/components/workspace/ComponentRegistry.js +5 -2
  13. package/dist/components/workspace/ComponentRegistry.js.map +1 -1
  14. package/dist/index.js +84 -82
  15. package/dist/index.js.map +1 -1
  16. package/dist/styles/global.css +44 -57
  17. package/package.json +7 -2
  18. package/scripts/apply-brand.mjs +47 -30
  19. package/scripts/postinstall.mjs +1 -11
  20. package/src/components/library/cards/KanbanBoard.jsx +507 -0
  21. package/src/components/library/index.jsx +1 -0
  22. package/src/styles/global.css +44 -57
  23. package/brands/engine/PARTNER_HUB_PRD.md +0 -584
  24. package/brands/engine/agentApiConfig.ts +0 -36
  25. package/brands/engine/app/api/graphql-operations-types.ts +0 -11260
  26. package/brands/engine/app/api/graphqlClient.ts +0 -25
  27. package/brands/engine/app/api/partnerQueries.ts +0 -212
  28. package/brands/engine/app/appLayout.tsx +0 -5
  29. package/brands/engine/app/components/AgentPanel.tsx +0 -541
  30. package/brands/engine/app/components/AgentforceConversationClient.tsx +0 -201
  31. package/brands/engine/app/components/Data360Widget.tsx +0 -301
  32. package/brands/engine/app/components/__inherit_AgentforceConversationClient.tsx +0 -3
  33. package/brands/engine/app/components/alerts/status-alert.tsx +0 -49
  34. package/brands/engine/app/components/layouts/card-layout.tsx +0 -29
  35. package/brands/engine/app/components/workspace/CommandCenter.tsx +0 -16
  36. package/brands/engine/app/config/agentApi.ts +0 -36
  37. package/brands/engine/app/data/partner-hub-sample-data.js +0 -297
  38. package/brands/engine/app/features/object-search/__examples__/api/accountSearchService.ts +0 -46
  39. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +0 -19
  40. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +0 -19
  41. package/brands/engine/app/features/object-search/__examples__/api/query/getAccountDetail.graphql +0 -121
  42. package/brands/engine/app/features/object-search/__examples__/api/query/searchAccounts.graphql +0 -51
  43. package/brands/engine/app/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +0 -357
  44. package/brands/engine/app/features/object-search/__examples__/pages/AccountSearch.tsx +0 -312
  45. package/brands/engine/app/features/object-search/__examples__/pages/Home.tsx +0 -34
  46. package/brands/engine/app/features/object-search/api/objectSearchService.ts +0 -84
  47. package/brands/engine/app/features/object-search/components/ActiveFilters.tsx +0 -89
  48. package/brands/engine/app/features/object-search/components/FilterContext.tsx +0 -83
  49. package/brands/engine/app/features/object-search/components/ObjectBreadcrumb.tsx +0 -66
  50. package/brands/engine/app/features/object-search/components/PaginationControls.tsx +0 -109
  51. package/brands/engine/app/features/object-search/components/SearchBar.tsx +0 -41
  52. package/brands/engine/app/features/object-search/components/SortControl.tsx +0 -143
  53. package/brands/engine/app/features/object-search/components/filters/BooleanFilter.tsx +0 -78
  54. package/brands/engine/app/features/object-search/components/filters/DateFilter.tsx +0 -128
  55. package/brands/engine/app/features/object-search/components/filters/DateRangeFilter.tsx +0 -70
  56. package/brands/engine/app/features/object-search/components/filters/FilterFieldWrapper.tsx +0 -33
  57. package/brands/engine/app/features/object-search/components/filters/MultiSelectFilter.tsx +0 -97
  58. package/brands/engine/app/features/object-search/components/filters/NumericRangeFilter.tsx +0 -163
  59. package/brands/engine/app/features/object-search/components/filters/SearchFilter.tsx +0 -50
  60. package/brands/engine/app/features/object-search/components/filters/SelectFilter.tsx +0 -97
  61. package/brands/engine/app/features/object-search/components/filters/TextFilter.tsx +0 -91
  62. package/brands/engine/app/features/object-search/hooks/useAsyncData.ts +0 -54
  63. package/brands/engine/app/features/object-search/hooks/useCachedAsyncData.ts +0 -184
  64. package/brands/engine/app/features/object-search/hooks/useDebouncedCallback.ts +0 -34
  65. package/brands/engine/app/features/object-search/hooks/useObjectSearchParams.ts +0 -252
  66. package/brands/engine/app/features/object-search/utils/debounce.ts +0 -25
  67. package/brands/engine/app/features/object-search/utils/fieldUtils.ts +0 -29
  68. package/brands/engine/app/features/object-search/utils/filterUtils.ts +0 -404
  69. package/brands/engine/app/features/object-search/utils/sortUtils.ts +0 -38
  70. package/brands/engine/app/hooks/useEngineLiveData.ts +0 -49
  71. package/brands/engine/app/hooks/useEvaAgent.ts +0 -288
  72. package/brands/engine/app/hooks/usePartnerDashboardData.ts +0 -141
  73. package/brands/engine/app/navigationMenu.tsx +0 -80
  74. package/brands/engine/app/pages/AccountObjectDetailPage.tsx +0 -361
  75. package/brands/engine/app/pages/AccountSearch.tsx +0 -305
  76. package/brands/engine/app/pages/BlankDashboard.tsx +0 -15
  77. package/brands/engine/app/pages/DataTest.tsx +0 -78
  78. package/brands/engine/app/pages/Home.tsx +0 -5
  79. package/brands/engine/app/pages/NotFound.tsx +0 -19
  80. package/brands/engine/app/pages/PartnerHubDashboard.tsx +0 -2760
  81. package/brands/engine/app/pages/Search.tsx +0 -13
  82. package/brands/engine/app/router-utils.tsx +0 -35
  83. package/brands/engine/app/routes.tsx +0 -39
  84. package/brands/engine/app/styles/global.css +0 -269
  85. package/brands/engine/brand.css +0 -40
  86. package/brands/engine/engine-command-center-prd.md +0 -575
  87. package/brands/engine/engine-live-data.js +0 -135
  88. package/brands/engine/engine-sample-data.js +0 -378
  89. package/brands/engine/engine_logo.png +0 -0
  90. package/brands/engine/global.css +0 -269
  91. package/brands/engine/partner-hub-sample-data.js +0 -281
  92. package/brands/engine/schema.graphql +0 -292
  93. package/brands/engine/useEngineLiveData.ts +0 -49
  94. package/brands/engine/useEvaAgent.ts +0 -288
@@ -1,78 +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 BooleanFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
16
- field: string;
17
- label: string;
18
- helpText?: string;
19
- }
20
-
21
- export function BooleanFilter({ field, label, helpText, className, ...props }: BooleanFilterProps) {
22
- const { value, onChange } = useFilterField(field);
23
- return (
24
- <FilterFieldWrapper
25
- label={label}
26
- htmlFor={`filter-${field}`}
27
- helpText={helpText}
28
- className={className}
29
- {...props}
30
- >
31
- <BooleanFilterSelect field={field} label={label} value={value} onChange={onChange} />
32
- </FilterFieldWrapper>
33
- );
34
- }
35
-
36
- interface BooleanFilterSelectProps {
37
- field: string;
38
- label: string;
39
- value: ActiveFilterValue | undefined;
40
- onChange: (value: ActiveFilterValue | undefined) => void;
41
- triggerProps?: React.ComponentProps<typeof SelectTrigger>;
42
- contentProps?: React.ComponentProps<typeof SelectContent>;
43
- }
44
-
45
- export function BooleanFilterSelect({
46
- field,
47
- label,
48
- value,
49
- onChange,
50
- triggerProps,
51
- contentProps,
52
- }: BooleanFilterSelectProps) {
53
- return (
54
- <Select
55
- value={value?.value ?? ALL_VALUE}
56
- onValueChange={(v) => {
57
- if (v === ALL_VALUE) {
58
- onChange(undefined);
59
- } else {
60
- onChange({ field, label, type: "boolean", value: v });
61
- }
62
- }}
63
- >
64
- <SelectTrigger
65
- id={`filter-${field}`}
66
- {...triggerProps}
67
- className={cn("w-full", triggerProps?.className)}
68
- >
69
- <SelectValue />
70
- </SelectTrigger>
71
- <SelectContent {...contentProps}>
72
- <SelectItem value={ALL_VALUE}>All</SelectItem>
73
- <SelectItem value="true">Yes</SelectItem>
74
- <SelectItem value="false">No</SelectItem>
75
- </SelectContent>
76
- </Select>
77
- );
78
- }
@@ -1,128 +0,0 @@
1
- import { useState } from "react";
2
- import { parseISO } from "date-fns";
3
- import {
4
- DatePicker,
5
- DatePickerTrigger,
6
- DatePickerContent,
7
- DatePickerCalendar,
8
- } from "../../../../components/ui/datePicker";
9
-
10
- import { useFilterField } from "../FilterContext";
11
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
12
- import type { FilterFieldType } from "../../utils/filterUtils";
13
- import {
14
- Select,
15
- SelectContent,
16
- SelectItem,
17
- SelectTrigger,
18
- SelectValue,
19
- } from "../../../../components/ui/select";
20
-
21
- type DateOperator = "gt" | "lt";
22
-
23
- const OPERATOR_OPTIONS: { value: DateOperator; label: string }[] = [
24
- { value: "gt", label: "After" },
25
- { value: "lt", label: "Before" },
26
- ];
27
-
28
- function operatorToField(op: DateOperator): "min" | "max" {
29
- return op === "gt" ? "min" : "max";
30
- }
31
-
32
- interface DateFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
33
- field: string;
34
- label: string;
35
- helpText?: string;
36
- filterType?: FilterFieldType;
37
- }
38
-
39
- export function DateFilter({
40
- field,
41
- label,
42
- helpText,
43
- filterType = "date",
44
- className,
45
- ...props
46
- }: DateFilterProps) {
47
- const { value, onChange } = useFilterField(field);
48
-
49
- const initialOp: DateOperator = value?.min ? "gt" : "lt";
50
- const [operator, setOperator] = useState<DateOperator>(initialOp);
51
-
52
- const currentDate = toDate(value?.min ?? value?.max);
53
-
54
- function handleOperatorChange(op: DateOperator) {
55
- setOperator(op);
56
- if (currentDate) {
57
- emitChange(op, currentDate);
58
- }
59
- }
60
-
61
- function handleDateChange(date: Date | undefined) {
62
- if (!date) {
63
- onChange(undefined);
64
- } else {
65
- emitChange(operator, date);
66
- }
67
- }
68
-
69
- function emitChange(op: DateOperator, date: Date) {
70
- const dateStr = toDateString(date);
71
- const f = operatorToField(op);
72
- onChange({
73
- field,
74
- label,
75
- type: filterType,
76
- value: op,
77
- min: f === "min" ? dateStr : undefined,
78
- max: f === "max" ? dateStr : undefined,
79
- });
80
- }
81
-
82
- return (
83
- <FilterFieldWrapper label={label} helpText={helpText} className={className} {...props}>
84
- <div className="flex gap-2">
85
- <Select value={operator} onValueChange={(v) => handleOperatorChange(v as DateOperator)}>
86
- <SelectTrigger className="w-full flex-1">
87
- <SelectValue />
88
- </SelectTrigger>
89
- <SelectContent>
90
- {OPERATOR_OPTIONS.map((opt) => (
91
- <SelectItem key={opt.value} value={opt.value}>
92
- {opt.label}
93
- </SelectItem>
94
- ))}
95
- </SelectContent>
96
- </Select>
97
- <DatePicker>
98
- <DatePickerTrigger
99
- className="w-full flex-2"
100
- date={currentDate}
101
- dateFormat="MMM do, yyyy"
102
- placeholder="Pick a date"
103
- aria-label={label}
104
- />
105
- <DatePickerContent>
106
- <DatePickerCalendar
107
- mode="single"
108
- captionLayout="dropdown"
109
- selected={currentDate}
110
- onSelect={handleDateChange}
111
- />
112
- </DatePickerContent>
113
- </DatePicker>
114
- </div>
115
- </FilterFieldWrapper>
116
- );
117
- }
118
-
119
- export function toDate(value: string | undefined): Date | undefined {
120
- if (!value) return undefined;
121
- const parsed = parseISO(value);
122
- return isNaN(parsed.getTime()) ? undefined : parsed;
123
- }
124
-
125
- export function toDateString(date: Date | undefined): string {
126
- if (!date) return "";
127
- return date.toISOString().split("T")[0];
128
- }
@@ -1,70 +0,0 @@
1
- import type { DateRange } from "react-day-picker";
2
- import {
3
- DatePicker,
4
- DatePickerRangeTrigger,
5
- DatePickerContent,
6
- DatePickerCalendar,
7
- } from "../../../../components/ui/datePicker";
8
-
9
- import { useFilterField } from "../FilterContext";
10
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
11
- import type { FilterFieldType } from "../../utils/filterUtils";
12
- import { toDate, toDateString } from "./DateFilter";
13
-
14
- interface DateRangeFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
15
- field: string;
16
- label: string;
17
- helpText?: string;
18
- filterType?: FilterFieldType;
19
- }
20
-
21
- export function DateRangeFilter({
22
- field,
23
- label,
24
- helpText,
25
- filterType = "daterange",
26
- className,
27
- ...props
28
- }: DateRangeFilterProps) {
29
- const { value, onChange } = useFilterField(field);
30
-
31
- const dateRange: DateRange | undefined =
32
- value?.min || value?.max ? { from: toDate(value?.min), to: toDate(value?.max) } : undefined;
33
-
34
- function handleRangeSelect(range: DateRange | undefined) {
35
- if (!range?.from && !range?.to) {
36
- onChange(undefined);
37
- } else {
38
- onChange({
39
- field,
40
- label,
41
- type: filterType,
42
- min: toDateString(range?.from),
43
- max: toDateString(range?.to),
44
- });
45
- }
46
- }
47
-
48
- return (
49
- <FilterFieldWrapper label={label} helpText={helpText} className={className} {...props}>
50
- <DatePicker>
51
- <DatePickerRangeTrigger
52
- className="w-full"
53
- dateRange={dateRange}
54
- placeholder="Pick a date range"
55
- aria-label={label}
56
- />
57
- <DatePickerContent align="start">
58
- <DatePickerCalendar
59
- mode="range"
60
- captionLayout="dropdown"
61
- defaultMonth={dateRange?.from}
62
- selected={dateRange}
63
- onSelect={handleRangeSelect}
64
- numberOfMonths={2}
65
- />
66
- </DatePickerContent>
67
- </DatePicker>
68
- </FilterFieldWrapper>
69
- );
70
- }
@@ -1,33 +0,0 @@
1
- import { Label } from "../../../../components/ui/label";
2
- import { cn } from "../../../../lib/utils";
3
-
4
- interface FilterFieldWrapperProps extends React.ComponentProps<"div"> {
5
- label: string;
6
- htmlFor?: string;
7
- helpText?: string;
8
- error?: string;
9
- }
10
-
11
- export function FilterFieldWrapper({
12
- label,
13
- htmlFor,
14
- helpText,
15
- error,
16
- className,
17
- children,
18
- ...props
19
- }: FilterFieldWrapperProps) {
20
- return (
21
- <div className={cn("space-y-1", className)} {...props}>
22
- <Label htmlFor={htmlFor}>{label}</Label>
23
- {children}
24
- <div className="min-h-4">
25
- {error ? (
26
- <p className="text-xs text-destructive">{error}</p>
27
- ) : (
28
- helpText && <p className="text-xs text-muted-foreground">{helpText}</p>
29
- )}
30
- </div>
31
- </div>
32
- );
33
- }
@@ -1,97 +0,0 @@
1
- import {
2
- Popover,
3
- PopoverContent,
4
- PopoverTrigger,
5
- } from "../../../../components/ui/popover";
6
- import { Checkbox } from "../../../../components/ui/checkbox";
7
- import { Button } from "../../../../components/ui/button";
8
- import { cn } from "../../../../lib/utils";
9
- import { Label } from "../../../../components/ui/label";
10
- import { ChevronDown } from "lucide-react";
11
- import { useFilterField } from "../FilterContext";
12
- import { FilterFieldWrapper } from "./FilterFieldWrapper";
13
-
14
- interface MultiSelectFilterProps extends Omit<React.ComponentProps<"div">, "onChange"> {
15
- field: string;
16
- label: string;
17
- options: Array<{ value: string; label: string }>;
18
- helpText?: string;
19
- }
20
-
21
- export function MultiSelectFilter({
22
- field,
23
- label,
24
- options,
25
- helpText,
26
- className,
27
- ...props
28
- }: MultiSelectFilterProps) {
29
- const { value, onChange } = useFilterField(field);
30
- const selected = value?.value ? value.value.split(",") : [];
31
-
32
- const triggerLabel =
33
- selected.length === 0
34
- ? `Select ${label.toLowerCase()}`
35
- : selected.length === 1
36
- ? (options.find((o) => o.value === selected[0])?.label ?? selected[0])
37
- : `${selected.length} selected`;
38
-
39
- function handleToggle(optionValue: string) {
40
- const next = selected.includes(optionValue)
41
- ? selected.filter((v) => v !== optionValue)
42
- : [...selected, optionValue];
43
-
44
- if (next.length === 0) {
45
- onChange(undefined);
46
- } else {
47
- onChange({
48
- field,
49
- label,
50
- type: "multipicklist",
51
- value: next.join(","),
52
- });
53
- }
54
- }
55
-
56
- return (
57
- <FilterFieldWrapper label={label} helpText={helpText} className={className} {...props}>
58
- <Popover>
59
- <PopoverTrigger asChild>
60
- <Button
61
- variant="outline"
62
- role="combobox"
63
- className={cn(
64
- "w-full justify-between font-normal",
65
- selected.length === 0 && "text-muted-foreground",
66
- )}
67
- >
68
- <span className="truncate">{triggerLabel}</span>
69
- <ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
70
- </Button>
71
- </PopoverTrigger>
72
- <PopoverContent className="p-2" align="start">
73
- <div className="max-h-48 overflow-y-auto space-y-1">
74
- {options.map((opt) => {
75
- const id = `filter-${field}-${opt.value}`;
76
- return (
77
- <div
78
- key={opt.value}
79
- className="flex items-center gap-2 rounded px-1 py-0.5 hover:bg-accent"
80
- >
81
- <Checkbox
82
- id={id}
83
- checked={selected.includes(opt.value)}
84
- onCheckedChange={() => handleToggle(opt.value)}
85
- />
86
- <Label htmlFor={id} className="text-sm font-normal cursor-pointer w-full">
87
- {opt.label}
88
- </Label>
89
- </div>
90
- );
91
- })}
92
- </div>
93
- </PopoverContent>
94
- </Popover>
95
- </FilterFieldWrapper>
96
- );
97
- }
@@ -1,163 +0,0 @@
1
- import { useEffect, 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 [localMin, setLocalMin] = useState(value?.min ?? "");
66
- const [localMax, setLocalMax] = useState(value?.max ?? "");
67
-
68
- const externalMin = value?.min ?? "";
69
- const externalMax = value?.max ?? "";
70
- useEffect(() => {
71
- setLocalMin(externalMin);
72
- }, [externalMin]);
73
- useEffect(() => {
74
- setLocalMax(externalMax);
75
- }, [externalMax]);
76
-
77
- const isOutOfBounds = (v: string) => {
78
- if (v === "") return false;
79
- const n = Number(v);
80
- return (boundMin != null && n < boundMin) || (boundMax != null && n > boundMax);
81
- };
82
- const minOutOfBounds = isOutOfBounds(localMin);
83
- const maxOutOfBounds = isOutOfBounds(localMax);
84
- const isRangeInverted = localMin !== "" && localMax !== "" && Number(localMin) > Number(localMax);
85
- const hasError = minOutOfBounds || maxOutOfBounds || isRangeInverted;
86
-
87
- const debouncedOnChange = useDebouncedCallback((min: string, max: string) => {
88
- if (!min && !max) {
89
- onChange(undefined);
90
- return;
91
- }
92
- const minNum = min !== "" ? Number(min) : null;
93
- const maxNum = max !== "" ? Number(max) : null;
94
- if (minNum != null && maxNum != null && minNum > maxNum) return;
95
- if (
96
- minNum != null &&
97
- ((boundMin != null && minNum < boundMin) || (boundMax != null && minNum > boundMax))
98
- )
99
- return;
100
- if (
101
- maxNum != null &&
102
- ((boundMin != null && maxNum < boundMin) || (boundMax != null && maxNum > boundMax))
103
- )
104
- return;
105
- onChange({ field, label, type: "numeric" as const, min, max });
106
- });
107
-
108
- const boundsLabel =
109
- boundMin != null && boundMax != null
110
- ? `${boundMin}–${boundMax}`
111
- : boundMin != null
112
- ? `${boundMin} or more`
113
- : boundMax != null
114
- ? `${boundMax} or less`
115
- : null;
116
-
117
- const errorMessage = isRangeInverted
118
- ? "Min must not exceed max"
119
- : (minOutOfBounds || maxOutOfBounds) && boundsLabel
120
- ? `Value must be between ${boundsLabel}`
121
- : undefined;
122
-
123
- return (
124
- <FilterFieldWrapper
125
- label={label}
126
- helpText={helpText}
127
- error={errorMessage}
128
- className={className}
129
- {...props}
130
- >
131
- <div className="flex gap-2">
132
- <Input
133
- type="number"
134
- placeholder="Min"
135
- value={localMin}
136
- min={boundMin}
137
- max={boundMax}
138
- onChange={(e) => {
139
- const v = e.target.value;
140
- setLocalMin(v);
141
- debouncedOnChange(v, localMax);
142
- }}
143
- aria-label={`${label} minimum`}
144
- aria-invalid={hasError || undefined}
145
- />
146
- <Input
147
- type="number"
148
- placeholder="Max"
149
- value={localMax}
150
- min={boundMin}
151
- max={boundMax}
152
- onChange={(e) => {
153
- const v = e.target.value;
154
- setLocalMax(v);
155
- debouncedOnChange(localMin, v);
156
- }}
157
- aria-label={`${label} maximum`}
158
- aria-invalid={hasError || undefined}
159
- />
160
- </div>
161
- </FilterFieldWrapper>
162
- );
163
- }
@@ -1,50 +0,0 @@
1
- import { useEffect, 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
- useEffect(() => {
26
- setLocalValue(externalValue);
27
- }, [externalValue]);
28
-
29
- const debouncedOnChange = useDebouncedCallback((v: string) => {
30
- if (v) {
31
- onChange({ field, label, type: "search", value: v });
32
- } else {
33
- onChange(undefined);
34
- }
35
- });
36
-
37
- return (
38
- <FilterFieldWrapper label={label} htmlFor={`filter-${field}`} className={className} {...props}>
39
- <SearchBar
40
- value={localValue}
41
- handleChange={(v) => {
42
- setLocalValue(v);
43
- debouncedOnChange(v);
44
- }}
45
- placeholder={placeholder}
46
- inputProps={{ id: `filter-${field}` }}
47
- />
48
- </FilterFieldWrapper>
49
- );
50
- }