Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/website/screens/components/select/code/SelectCodePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,22 @@ const sections = [
<TableCode>false</TableCode>
</td>
</tr>
<tr>
<td>
<DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline">
<StatusBadge status="new" />
searchByStartsWith
</DxcFlex>
</td>
<td>
<TableCode>boolean</TableCode>
</td>
<td>
Defines the search mode when searchable is true. If true, matches options that start with the search text.
If false, matches options that contain the search text anywhere in their label.
</td>
<td>false</td>
</tr>
<tr>
<td>size</td>
<td>
Expand Down
28 changes: 28 additions & 0 deletions packages/lib/src/select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ const singleOptions = [
{ label: "Option 04", value: "4" },
];

const startsWithSingleOptions = [
{ label: "Option 01", value: "1" },
{ label: "This is option 02", value: "2" },
{ label: "Is option 03", value: "3" },
{ label: "And Option 04", value: "4" },
];

const single_options_virtualized = [
...Array.from({ length: 10000 }, (_, i) => ({
label: `Option ${String(i + 1).padStart(2, "0")}`,
Expand Down Expand Up @@ -593,6 +600,19 @@ const SearchableSelect = () => (
</ExampleContainer>
);

const startsWithSearchableSelect = () => (
<ExampleContainer expanded>
<Title title="Searchable contains select" theme="light" level={4} />
<DxcSelect
label="Select Label"
searchable
searchByStartsWith
options={startsWithSingleOptions}
placeholder="Choose an option"
/>
</ExampleContainer>
);

const SearchValue = () => (
<ExampleContainer expanded>
<Title title="Searchable select with value" theme="light" level={4} />
Expand Down Expand Up @@ -740,6 +760,14 @@ export const Searchable: Story = {
},
};

export const StartsWithSearchable: Story = {
render: startsWithSearchableSelect,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(await canvas.findByRole("combobox"), "t");
},
};

export const SearchableWithValue: Story = {
render: SearchValue,
play: async ({ canvasElement }) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/lib/src/select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const DxcSelect = forwardRef<RefType, SelectPropsType>(
options,
placeholder = "",
searchable = false,
searchByStartsWith = false,
size = "medium",
tabIndex = 0,
value,
Expand All @@ -217,7 +218,10 @@ const DxcSelect = forwardRef<RefType, SelectPropsType>(
const translatedLabels = useContext(HalstackLanguageContext);

const optionalItem = useMemo(() => ({ label: placeholder, value: "" }), [placeholder]);
const filteredOptions = useMemo(() => filterOptionsBySearchValue(options, searchValue), [options, searchValue]);
const filteredOptions = useMemo(
() => filterOptionsBySearchValue(options, searchValue, searchByStartsWith),
[options, searchValue, searchByStartsWith]
);
const lastOptionIndex = useMemo(
() => getLastOptionIndex(options, filteredOptions, searchable, optional, multiple, enableSelectAll),
[options, filteredOptions, searchable, optional, multiple, enableSelectAll]
Expand Down
6 changes: 6 additions & 0 deletions packages/lib/src/select/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ type CommonProps = {
* If true, enables search functionality.
*/
searchable?: boolean;
/**
* Defines the search mode when searchable is true.
* If true, matches options that start with the search text.
* If false, matches options that contain the search text anywhere in their label.
*/
searchByStartsWith?: boolean;
/**
* Size of the component.
*/
Expand Down
19 changes: 15 additions & 4 deletions packages/lib/src/select/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,31 @@ export const canOpenListbox = (options: Props["options"], disabled: boolean) =>
/**
* Filters the options by the search value.
*/
export const filterOptionsBySearchValue = (options: Props["options"], searchValue: string): Props["options"] =>
options.length > 0
export const filterOptionsBySearchValue = (
options: Props["options"],
searchValue: string,
searchByStartsWith: Props["searchByStartsWith"] = false
): Props["options"] => {
const matchesSearch = (label: string, search: string, searchByStartsWith: boolean) => {
const upperLabel = label.toUpperCase();
const upperSearch = search.toUpperCase();
return searchByStartsWith ? upperLabel.startsWith(upperSearch) : upperLabel.includes(upperSearch);
};

return options.length > 0
? isArrayOfGroupedOptions(options)
? options.map((optionGroup) => {
const group = {
label: optionGroup.label,
options: optionGroup.options.filter((option) =>
option.label.toUpperCase().includes(searchValue.toUpperCase())
matchesSearch(option.label, searchValue, searchByStartsWith)
),
};
return group;
})
: options.filter((option) => option.label.toUpperCase().includes(searchValue.toUpperCase()))
: options.filter((option) => matchesSearch(option.label, searchValue, searchByStartsWith))
: [];
};

/**
* Returns the index of the last option, depending on several conditions.
Expand Down