myoperator-mcp 0.2.364 → 0.2.366
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/index.js +141 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -710,6 +710,7 @@ const badgeVariants = cva(
|
|
|
710
710
|
variant: {
|
|
711
711
|
// Status-based variants (existing)
|
|
712
712
|
active: "bg-semantic-success-surface text-semantic-success-primary",
|
|
713
|
+
information: "bg-semantic-info-surface text-semantic-info-text",
|
|
713
714
|
warning: "bg-semantic-warning-surface text-semantic-warning-primary",
|
|
714
715
|
failed: "bg-semantic-error-surface text-semantic-error-primary",
|
|
715
716
|
disabled: "bg-semantic-bg-ui text-semantic-text-muted",
|
|
@@ -740,6 +741,7 @@ const badgeVariants = cva(
|
|
|
740
741
|
* @example
|
|
741
742
|
* \`\`\`tsx
|
|
742
743
|
* <Badge variant="active">Active</Badge>
|
|
744
|
+
* <Badge variant="information">Coming Soon</Badge>
|
|
743
745
|
* <Badge variant="warning">Warning</Badge>
|
|
744
746
|
* <Badge variant="failed">Failed</Badge>
|
|
745
747
|
* <Badge variant="disabled">Disabled</Badge>
|
|
@@ -8292,6 +8294,8 @@ export interface SearchFilterProps
|
|
|
8292
8294
|
searchPlaceholder?: string;
|
|
8293
8295
|
/** Search input behavior. Defaults to "numeric" for phone-number filtering. */
|
|
8294
8296
|
searchMode?: SearchFilterSearchMode;
|
|
8297
|
+
/** Minimum query length before internal option filtering runs. Below it, all options are shown. Defaults to 0. */
|
|
8298
|
+
minSearchLength?: number;
|
|
8295
8299
|
/** Message shown when filtering returns no options */
|
|
8296
8300
|
emptyMessage?: string;
|
|
8297
8301
|
/** Disables the whole filter */
|
|
@@ -8319,6 +8323,7 @@ const SearchFilter = React.forwardRef<HTMLDivElement, SearchFilterProps>(
|
|
|
8319
8323
|
onSearchChange,
|
|
8320
8324
|
searchPlaceholder = "Search...",
|
|
8321
8325
|
searchMode = "numeric",
|
|
8326
|
+
minSearchLength = 0,
|
|
8322
8327
|
emptyMessage = "No options found",
|
|
8323
8328
|
disabled = false,
|
|
8324
8329
|
searchDisabled = false,
|
|
@@ -8455,6 +8460,7 @@ const SearchFilter = React.forwardRef<HTMLDivElement, SearchFilterProps>(
|
|
|
8455
8460
|
}, [isOpen, refs.floating, setOpen]);
|
|
8456
8461
|
|
|
8457
8462
|
const filteredOptions = React.useMemo(() => {
|
|
8463
|
+
if (normalizedSearchQuery.length < minSearchLength) return options;
|
|
8458
8464
|
if (!normalizedSearchQuery) return options;
|
|
8459
8465
|
|
|
8460
8466
|
return options.filter((option) =>
|
|
@@ -8462,7 +8468,7 @@ const SearchFilter = React.forwardRef<HTMLDivElement, SearchFilterProps>(
|
|
|
8462
8468
|
normalizedSearchQuery
|
|
8463
8469
|
)
|
|
8464
8470
|
);
|
|
8465
|
-
}, [normalizedSearchQuery, options, searchMode]);
|
|
8471
|
+
}, [normalizedSearchQuery, options, searchMode, minSearchLength]);
|
|
8466
8472
|
|
|
8467
8473
|
const selectOption = React.useCallback(
|
|
8468
8474
|
(option: SearchFilterOption) => {
|
|
@@ -8670,6 +8676,7 @@ export { SearchFilter, searchFilterVariants };
|
|
|
8670
8676
|
import { Loader2, Search } from "lucide-react";
|
|
8671
8677
|
|
|
8672
8678
|
import { cn } from "@/lib/utils";
|
|
8679
|
+
import { Input } from "./input";
|
|
8673
8680
|
import {
|
|
8674
8681
|
Select,
|
|
8675
8682
|
SelectContent,
|
|
@@ -8728,6 +8735,12 @@ export interface SelectFieldProps {
|
|
|
8728
8735
|
searchable?: boolean;
|
|
8729
8736
|
/** Search placeholder text */
|
|
8730
8737
|
searchPlaceholder?: string;
|
|
8738
|
+
/**
|
|
8739
|
+
* Render a divider (top border) above each option group and display the
|
|
8740
|
+
* group labels in uppercase with letter-spacing \u2014 matches the Figma
|
|
8741
|
+
* "routing" dropdown style. Only affects grouped options.
|
|
8742
|
+
*/
|
|
8743
|
+
separateGroups?: boolean;
|
|
8731
8744
|
/**
|
|
8732
8745
|
* Controlled search value. When provided, internal search state is
|
|
8733
8746
|
* ignored and the consumer owns the value \u2014 typically used to drive
|
|
@@ -8849,6 +8862,7 @@ const SelectField = React.forwardRef(
|
|
|
8849
8862
|
options,
|
|
8850
8863
|
searchable,
|
|
8851
8864
|
searchPlaceholder = "Search...",
|
|
8865
|
+
separateGroups,
|
|
8852
8866
|
searchValue,
|
|
8853
8867
|
onSearchChange,
|
|
8854
8868
|
wrapperClassName,
|
|
@@ -8869,6 +8883,17 @@ const SelectField = React.forwardRef(
|
|
|
8869
8883
|
const isSearchControlled = searchValue !== undefined;
|
|
8870
8884
|
const effectiveSearchQuery = isSearchControlled ? searchValue : searchQuery;
|
|
8871
8885
|
|
|
8886
|
+
// Track the current selection ourselves so we always know which option is
|
|
8887
|
+
// selected \u2014 even in uncontrolled mode. This lets us keep the selected
|
|
8888
|
+
// option mounted while the search filter hides everything else (see the
|
|
8889
|
+
// filter below), which prevents Radix from blanking the trigger value and
|
|
8890
|
+
// stealing focus off the search input when the selected item unmounts.
|
|
8891
|
+
const isValueControlled = value !== undefined;
|
|
8892
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(
|
|
8893
|
+
defaultValue ?? ""
|
|
8894
|
+
);
|
|
8895
|
+
const selectedValue = isValueControlled ? value : uncontrolledValue;
|
|
8896
|
+
|
|
8872
8897
|
// Combined value change handler that also fires onSelect with full option object.
|
|
8873
8898
|
// When interceptValue returns false, onValueChange is skipped (only onSelect fires).
|
|
8874
8899
|
const handleValueChange = React.useCallback(
|
|
@@ -8877,6 +8902,9 @@ const SelectField = React.forwardRef(
|
|
|
8877
8902
|
|
|
8878
8903
|
if (!intercepted) {
|
|
8879
8904
|
onValueChange?.(newValue);
|
|
8905
|
+
if (!isValueControlled) {
|
|
8906
|
+
setUncontrolledValue(newValue);
|
|
8907
|
+
}
|
|
8880
8908
|
}
|
|
8881
8909
|
|
|
8882
8910
|
if (onSelect) {
|
|
@@ -8886,7 +8914,7 @@ const SelectField = React.forwardRef(
|
|
|
8886
8914
|
}
|
|
8887
8915
|
}
|
|
8888
8916
|
},
|
|
8889
|
-
[onValueChange, onSelect, interceptValue, options]
|
|
8917
|
+
[onValueChange, onSelect, interceptValue, options, isValueControlled]
|
|
8890
8918
|
);
|
|
8891
8919
|
|
|
8892
8920
|
// Support re-selection: fire onSelect when clicking the already-selected
|
|
@@ -8895,11 +8923,11 @@ const SelectField = React.forwardRef(
|
|
|
8895
8923
|
// no-op.
|
|
8896
8924
|
const handleItemClick = React.useCallback(
|
|
8897
8925
|
(option: SelectOption) => {
|
|
8898
|
-
if (option.value ===
|
|
8926
|
+
if (option.value === selectedValue) {
|
|
8899
8927
|
handleValueChange(option.value);
|
|
8900
8928
|
}
|
|
8901
8929
|
},
|
|
8902
|
-
[
|
|
8930
|
+
[selectedValue, handleValueChange]
|
|
8903
8931
|
);
|
|
8904
8932
|
|
|
8905
8933
|
// Derive state from props
|
|
@@ -8914,46 +8942,55 @@ const SelectField = React.forwardRef(
|
|
|
8914
8942
|
// Determine aria-describedby
|
|
8915
8943
|
const ariaDescribedBy = error ? errorId : helperText ? helperId : undefined;
|
|
8916
8944
|
|
|
8917
|
-
// Group options by group property
|
|
8945
|
+
// Group options by group property.
|
|
8946
|
+
//
|
|
8947
|
+
// When client-side filtering is active, non-matching options are hidden
|
|
8948
|
+
// (rendered with \`display:none\`) rather than removed from the tree. The
|
|
8949
|
+
// one exception we *must* keep mounted is the currently-selected option:
|
|
8950
|
+
// if Radix's selected item unmounts, the trigger loses its displayed value
|
|
8951
|
+
// and focus is pulled off the search input. \`matchCount\` tracks how many
|
|
8952
|
+
// options are actually visible so the "no results" / footer states stay
|
|
8953
|
+
// accurate.
|
|
8918
8954
|
const groupedOptions = React.useMemo(() => {
|
|
8919
|
-
const groups: Record<string, SelectOption[]> =
|
|
8920
|
-
|
|
8955
|
+
const groups: Record<string, (SelectOption & { hidden?: boolean })[]> =
|
|
8956
|
+
{};
|
|
8957
|
+
const ungrouped: (SelectOption & { hidden?: boolean })[] = [];
|
|
8958
|
+
let matchCount = 0;
|
|
8959
|
+
|
|
8960
|
+
const filtering =
|
|
8961
|
+
!!searchable && !isSearchControlled && !!searchQuery;
|
|
8962
|
+
const query = searchQuery.toLowerCase();
|
|
8921
8963
|
|
|
8922
8964
|
options.forEach((option) => {
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
searchQuery &&
|
|
8930
|
-
!option.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
8931
|
-
) {
|
|
8965
|
+
const isMatch =
|
|
8966
|
+
!filtering || option.label.toLowerCase().includes(query);
|
|
8967
|
+
const isSelected = option.value === selectedValue && !!selectedValue;
|
|
8968
|
+
|
|
8969
|
+
// Drop only options that neither match the query nor are selected.
|
|
8970
|
+
if (!isMatch && !isSelected) {
|
|
8932
8971
|
return;
|
|
8933
8972
|
}
|
|
8934
8973
|
|
|
8974
|
+
if (isMatch) matchCount++;
|
|
8975
|
+
const entry = { ...option, hidden: !isMatch };
|
|
8976
|
+
|
|
8935
8977
|
if (option.group) {
|
|
8936
8978
|
if (!groups[option.group]) {
|
|
8937
8979
|
groups[option.group] = [];
|
|
8938
8980
|
}
|
|
8939
|
-
groups[option.group].push(
|
|
8981
|
+
groups[option.group].push(entry);
|
|
8940
8982
|
} else {
|
|
8941
|
-
ungrouped.push(
|
|
8983
|
+
ungrouped.push(entry);
|
|
8942
8984
|
}
|
|
8943
8985
|
});
|
|
8944
8986
|
|
|
8945
|
-
return { groups, ungrouped };
|
|
8946
|
-
}, [options, searchable, isSearchControlled, searchQuery]);
|
|
8987
|
+
return { groups, ungrouped, matchCount };
|
|
8988
|
+
}, [options, searchable, isSearchControlled, searchQuery, selectedValue]);
|
|
8947
8989
|
|
|
8948
8990
|
const hasGroups = Object.keys(groupedOptions.groups).length > 0;
|
|
8949
8991
|
|
|
8950
|
-
//
|
|
8951
|
-
const totalRendered =
|
|
8952
|
-
groupedOptions.ungrouped.length +
|
|
8953
|
-
Object.values(groupedOptions.groups).reduce(
|
|
8954
|
-
(sum, items) => sum + items.length,
|
|
8955
|
-
0
|
|
8956
|
-
);
|
|
8992
|
+
// Number of *visible* options \u2014 drives the empty-state and footer rows.
|
|
8993
|
+
const totalRendered = groupedOptions.matchCount;
|
|
8957
8994
|
const showEndOfList = hasMore === false && totalRendered > 0 && !loadingMore;
|
|
8958
8995
|
|
|
8959
8996
|
// Handle search input change
|
|
@@ -9009,7 +9046,14 @@ const SelectField = React.forwardRef(
|
|
|
9009
9046
|
ref={ref}
|
|
9010
9047
|
id={selectId}
|
|
9011
9048
|
state={derivedState}
|
|
9012
|
-
className={cn(
|
|
9049
|
+
className={cn(
|
|
9050
|
+
loading && "pr-10",
|
|
9051
|
+
// Figma "routing" style uses a darker (text-muted) placeholder
|
|
9052
|
+
// instead of the default lighter placeholder token.
|
|
9053
|
+
separateGroups &&
|
|
9054
|
+
"[&>span[data-placeholder]]:text-semantic-text-muted",
|
|
9055
|
+
triggerClassName
|
|
9056
|
+
)}
|
|
9013
9057
|
aria-invalid={!!error}
|
|
9014
9058
|
aria-describedby={ariaDescribedBy}
|
|
9015
9059
|
>
|
|
@@ -9024,28 +9068,45 @@ const SelectField = React.forwardRef(
|
|
|
9024
9068
|
className={contentClassName}
|
|
9025
9069
|
>
|
|
9026
9070
|
{/* Search input */}
|
|
9027
|
-
{searchable &&
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9071
|
+
{searchable &&
|
|
9072
|
+
(separateGroups ? (
|
|
9073
|
+
// Figma "routing" style \u2014 a bordered, rounded search box that
|
|
9074
|
+
// reuses the shared Input component (no leading icon).
|
|
9075
|
+
<div className="px-1 pb-1.5">
|
|
9076
|
+
<Input
|
|
9077
|
+
type="text"
|
|
9078
|
+
placeholder={searchPlaceholder}
|
|
9079
|
+
value={effectiveSearchQuery}
|
|
9080
|
+
onChange={handleSearchChange}
|
|
9081
|
+
// Prevent closing dropdown when clicking input
|
|
9082
|
+
onClick={(e) => e.stopPropagation()}
|
|
9083
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
9084
|
+
/>
|
|
9085
|
+
</div>
|
|
9086
|
+
) : (
|
|
9087
|
+
<div className="flex items-center gap-2 px-3 pb-1.5 border-b border-solid border-semantic-border-layout">
|
|
9088
|
+
<Search className="size-4 text-semantic-text-muted shrink-0" />
|
|
9089
|
+
<input
|
|
9090
|
+
type="text"
|
|
9091
|
+
placeholder={searchPlaceholder}
|
|
9092
|
+
value={effectiveSearchQuery}
|
|
9093
|
+
onChange={handleSearchChange}
|
|
9094
|
+
className="w-full h-[42px] text-base text-semantic-text-primary bg-transparent placeholder:text-semantic-text-placeholder focus:outline-none"
|
|
9095
|
+
// Prevent closing dropdown when clicking input
|
|
9096
|
+
onClick={(e) => e.stopPropagation()}
|
|
9097
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
9098
|
+
/>
|
|
9099
|
+
</div>
|
|
9100
|
+
))}
|
|
9042
9101
|
|
|
9043
|
-
{/* Ungrouped options
|
|
9102
|
+
{/* Ungrouped options. Non-matching options stay mounted but hidden
|
|
9103
|
+
(only the selected option is ever kept while filtering). */}
|
|
9044
9104
|
{groupedOptions.ungrouped.map((option) => (
|
|
9045
9105
|
<SelectItem
|
|
9046
9106
|
key={option.value}
|
|
9047
9107
|
value={option.value}
|
|
9048
9108
|
disabled={option.disabled}
|
|
9109
|
+
className={cn(option.hidden && "hidden")}
|
|
9049
9110
|
onPointerUp={() => handleItemClick(option)}
|
|
9050
9111
|
>
|
|
9051
9112
|
{option.label}
|
|
@@ -9055,32 +9116,44 @@ const SelectField = React.forwardRef(
|
|
|
9055
9116
|
{/* Grouped options */}
|
|
9056
9117
|
{hasGroups &&
|
|
9057
9118
|
Object.entries(groupedOptions.groups).map(
|
|
9058
|
-
([groupName, groupOptions]) =>
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9119
|
+
([groupName, groupOptions]) => {
|
|
9120
|
+
// Hide the group label when every option in it is filtered
|
|
9121
|
+
// out (i.e. only a hidden selected item remains).
|
|
9122
|
+
const hasVisible = groupOptions.some((o) => !o.hidden);
|
|
9123
|
+
return (
|
|
9124
|
+
<SelectGroup key={groupName}>
|
|
9125
|
+
{hasVisible && (
|
|
9126
|
+
<SelectLabel
|
|
9127
|
+
className={cn(
|
|
9128
|
+
separateGroups &&
|
|
9129
|
+
"mt-1 border-t border-solid border-semantic-border-layout pt-2.5 uppercase tracking-[0.5px]"
|
|
9130
|
+
)}
|
|
9131
|
+
>
|
|
9132
|
+
{groupName}
|
|
9133
|
+
</SelectLabel>
|
|
9134
|
+
)}
|
|
9135
|
+
{groupOptions.map((option) => (
|
|
9136
|
+
<SelectItem
|
|
9137
|
+
key={option.value}
|
|
9138
|
+
value={option.value}
|
|
9139
|
+
disabled={option.disabled}
|
|
9140
|
+
className={cn(option.hidden && "hidden")}
|
|
9141
|
+
onPointerUp={() => handleItemClick(option)}
|
|
9142
|
+
>
|
|
9143
|
+
{option.label}
|
|
9144
|
+
</SelectItem>
|
|
9145
|
+
))}
|
|
9146
|
+
</SelectGroup>
|
|
9147
|
+
);
|
|
9148
|
+
}
|
|
9073
9149
|
)}
|
|
9074
9150
|
|
|
9075
|
-
{/* No results message */}
|
|
9076
|
-
{searchable &&
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
No results found
|
|
9082
|
-
</div>
|
|
9083
|
-
)}
|
|
9151
|
+
{/* No results message \u2014 based on the count of *visible* options. */}
|
|
9152
|
+
{searchable && effectiveSearchQuery && totalRendered === 0 && (
|
|
9153
|
+
<div className="py-6 text-center text-sm text-semantic-text-muted">
|
|
9154
|
+
No results found
|
|
9155
|
+
</div>
|
|
9156
|
+
)}
|
|
9084
9157
|
|
|
9085
9158
|
{/* Loading-more row (lazy-load) */}
|
|
9086
9159
|
{loadingMore && (
|
package/package.json
CHANGED