✨(react) add select mono option custom render
We want to be able to render the options in a customized manner.
This commit is contained in:
@@ -62,6 +62,14 @@
|
|||||||
font-size: var(--c--components--forms-select--font-size);
|
font-size: var(--c--components--forms-select--font-size);
|
||||||
background-color: var(--c--components--forms-select--background-color);
|
background-color: var(--c--components--forms-select--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
&--hidden {
|
||||||
|
// Using display: none makes impossible to focus the input.
|
||||||
|
position: absolute;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
import React, { forwardRef, PropsWithChildren } from "react";
|
import React, { forwardRef, PropsWithChildren, ReactNode } from "react";
|
||||||
import { SelectMulti } from ":/components/Forms/Select/multi";
|
import { SelectMulti } from ":/components/Forms/Select/multi";
|
||||||
import { Option, SelectMono } from ":/components/Forms/Select/mono";
|
import { SelectMono } from ":/components/Forms/Select/mono";
|
||||||
import { FieldProps } from ":/components/Forms/Field";
|
import { FieldProps } from ":/components/Forms/Field";
|
||||||
|
|
||||||
export * from ":/components/Forms/Select/mono";
|
export * from ":/components/Forms/Select/mono";
|
||||||
export * from ":/components/Forms/Select/multi";
|
export * from ":/components/Forms/Select/multi";
|
||||||
|
|
||||||
|
export type OptionWithRender = {
|
||||||
|
disabled?: boolean;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
render: () => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Option =
|
||||||
|
| {
|
||||||
|
disabled?: boolean;
|
||||||
|
value?: string;
|
||||||
|
label: string;
|
||||||
|
render?: undefined;
|
||||||
|
}
|
||||||
|
| OptionWithRender;
|
||||||
|
|
||||||
export interface SelectHandle {
|
export interface SelectHandle {
|
||||||
blur: () => void;
|
blur: () => void;
|
||||||
}
|
}
|
||||||
@@ -28,6 +44,7 @@ export type SelectProps = PropsWithChildren &
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
|
showLabelWhenSelected?: boolean;
|
||||||
};
|
};
|
||||||
export const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
|
export const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
|
||||||
if (props.defaultValue && props.value) {
|
if (props.defaultValue && props.value) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { useCunningham } from ":/components/Provider";
|
|||||||
import { Field } from ":/components/Forms/Field";
|
import { Field } from ":/components/Forms/Field";
|
||||||
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
import { LabelledBox } from ":/components/Forms/LabelledBox";
|
||||||
import { Button } from ":/components/Button";
|
import { Button } from ":/components/Button";
|
||||||
import { Option } from ":/components/Forms/Select/mono";
|
import { Option, SelectProps } from ":/components/Forms/Select";
|
||||||
import { SelectProps } from ":/components/Forms/Select";
|
import { isOptionWithRender } from ":/components/Forms/Select/utils";
|
||||||
|
|
||||||
export function getOptionsFilter(inputValue?: string) {
|
export function getOptionsFilter(inputValue?: string) {
|
||||||
return (option: Option) => {
|
return (option: Option) => {
|
||||||
@@ -22,10 +22,20 @@ export const optionToString = (option: Option | null) => {
|
|||||||
return option ? option.label : "";
|
return option ? option.label : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns underlying value of option.
|
||||||
|
*/
|
||||||
export const optionToValue = (option: Option) => {
|
export const optionToValue = (option: Option) => {
|
||||||
return option.value ?? option.label;
|
return option.value ?? option.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const renderOption = (option: Option) => {
|
||||||
|
if (isOptionWithRender(option)) {
|
||||||
|
return option.render();
|
||||||
|
}
|
||||||
|
return option.label;
|
||||||
|
};
|
||||||
|
|
||||||
export interface SubProps extends SelectProps {
|
export interface SubProps extends SelectProps {
|
||||||
defaultSelectedItem?: Option;
|
defaultSelectedItem?: Option;
|
||||||
downshiftProps: {
|
downshiftProps: {
|
||||||
@@ -159,12 +169,12 @@ export const SelectMonoAux = ({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames("c__select__menu", {
|
className={classNames("c__select__menu", {
|
||||||
"c__select__menu--opened": downshiftReturn.isOpen,
|
"c__select__menu--opened": downshiftReturn.isOpen || false,
|
||||||
})}
|
})}
|
||||||
{...downshiftReturn.getMenuProps()}
|
{...downshiftReturn.getMenuProps()}
|
||||||
>
|
>
|
||||||
<ul>
|
<ul>
|
||||||
{downshiftReturn.isOpen && (
|
{(downshiftReturn.isOpen || false) && (
|
||||||
<>
|
<>
|
||||||
{options.map((item, index) => {
|
{options.map((item, index) => {
|
||||||
const isActive = index === downshiftReturn.highlightedIndex;
|
const isActive = index === downshiftReturn.highlightedIndex;
|
||||||
@@ -182,7 +192,7 @@ export const SelectMonoAux = ({
|
|||||||
index,
|
index,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span>{item.label}</span>
|
<span>{renderOption(item)}</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useCombobox } from "downshift";
|
import { useCombobox } from "downshift";
|
||||||
|
import classNames from "classnames";
|
||||||
import { useCunningham } from ":/components/Provider";
|
import { useCunningham } from ":/components/Provider";
|
||||||
import {
|
import {
|
||||||
getOptionsFilter,
|
getOptionsFilter,
|
||||||
@@ -15,9 +16,10 @@ import {
|
|||||||
SubProps,
|
SubProps,
|
||||||
} from ":/components/Forms/Select/mono-common";
|
} from ":/components/Forms/Select/mono-common";
|
||||||
import { SelectHandle } from ":/components/Forms/Select";
|
import { SelectHandle } from ":/components/Forms/Select";
|
||||||
|
import { isOptionWithRender } from ":/components/Forms/Select/utils";
|
||||||
|
|
||||||
export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
||||||
(props, ref) => {
|
({ showLabelWhenSelected = true, ...props }, ref) => {
|
||||||
const { t } = useCunningham();
|
const { t } = useCunningham();
|
||||||
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
|
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
|
||||||
const [hasInputFocused, setHasInputFocused] = useState(false);
|
const [hasInputFocused, setHasInputFocused] = useState(false);
|
||||||
@@ -108,6 +110,8 @@ export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
|||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderCustomSelectedOption = !showLabelWhenSelected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectMonoAux
|
<SelectMonoAux
|
||||||
{...props}
|
{...props}
|
||||||
@@ -129,6 +133,10 @@ export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
|
className={classNames({
|
||||||
|
"c__select__inner__value__input--hidden":
|
||||||
|
renderCustomSelectedOption && !hasInputFocused,
|
||||||
|
})}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setHasInputFocused(true);
|
setHasInputFocused(true);
|
||||||
}}
|
}}
|
||||||
@@ -136,6 +144,12 @@ export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
|||||||
onInputBlur();
|
onInputBlur();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{renderCustomSelectedOption &&
|
||||||
|
!hasInputFocused &&
|
||||||
|
downshiftReturn.selectedItem &&
|
||||||
|
isOptionWithRender(downshiftReturn.selectedItem) &&
|
||||||
|
downshiftReturn.selectedItem.render()}
|
||||||
</SelectMonoAux>
|
</SelectMonoAux>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
SelectMonoAux,
|
SelectMonoAux,
|
||||||
SubProps,
|
SubProps,
|
||||||
} from ":/components/Forms/Select/mono-common";
|
} from ":/components/Forms/Select/mono-common";
|
||||||
import { SelectHandle } from ":/components/Forms/Select/index";
|
import { SelectHandle } from ":/components/Forms/Select";
|
||||||
|
import { SelectedOption } from ":/components/Forms/Select/utils";
|
||||||
|
|
||||||
export const SelectMonoSimple = forwardRef<SelectHandle, SubProps>(
|
export const SelectMonoSimple = forwardRef<SelectHandle, SubProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
@@ -61,9 +62,7 @@ export const SelectMonoSimple = forwardRef<SelectHandle, SubProps>(
|
|||||||
}}
|
}}
|
||||||
labelAsPlaceholder={!downshiftReturn.selectedItem}
|
labelAsPlaceholder={!downshiftReturn.selectedItem}
|
||||||
>
|
>
|
||||||
{downshiftReturn.selectedItem && (
|
<SelectedOption option={downshiftReturn.selectedItem} {...props} />
|
||||||
<span>{optionToString(downshiftReturn.selectedItem)}</span>
|
|
||||||
)}
|
|
||||||
</SelectMonoAux>
|
</SelectMonoAux>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -103,6 +103,19 @@ For some reasons you might want to hide the label of the select. You can do that
|
|||||||
<Story id="components-forms-select-mono--hidden-label"/>
|
<Story id="components-forms-select-mono--hidden-label"/>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
## Custom render option
|
||||||
|
|
||||||
|
You can give customize the look of the options by providing `render` callback.
|
||||||
|
|
||||||
|
> When you provide `render` the fields `label` and `value` are mandatory.
|
||||||
|
|
||||||
|
Feel free to use the attribute `showLabelWhenSelected` to choose whether you want to display selected option with the custom
|
||||||
|
HTML or with its `label`. It is set to `true` by default.
|
||||||
|
|
||||||
|
<Canvas sourceState="shown">
|
||||||
|
<Story id="components-forms-select-mono--custom-render"/>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
## Controlled / Non Controlled
|
## Controlled / Non Controlled
|
||||||
|
|
||||||
Like a native select, you can use the Select component in a controlled or non controlled way. You can see the example below
|
Like a native select, you can use the Select component in a controlled or non controlled way. You can see the example below
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import userEvent from "@testing-library/user-event";
|
|||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import { render, screen, waitFor } from "@testing-library/react";
|
||||||
import { expect } from "vitest";
|
import { expect } from "vitest";
|
||||||
import React, { createRef, FormEvent, useState } from "react";
|
import React, { createRef, FormEvent, useState } from "react";
|
||||||
import { Select, Option, SelectHandle } from ":/components/Forms/Select/index";
|
import { within } from "@testing-library/dom";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
SelectHandle,
|
||||||
|
SelectProps,
|
||||||
|
} from ":/components/Forms/Select/index";
|
||||||
import { Button } from ":/components/Button";
|
import { Button } from ":/components/Button";
|
||||||
import { CunninghamProvider } from ":/components/Provider";
|
import { CunninghamProvider } from ":/components/Provider";
|
||||||
import {
|
import {
|
||||||
@@ -836,6 +842,123 @@ describe("<Select/>", () => {
|
|||||||
await waitFor(() => expectMenuToBeClosed(menu));
|
await waitFor(() => expectMenuToBeClosed(menu));
|
||||||
expect(document.activeElement?.tagName).toEqual("BODY");
|
expect(document.activeElement?.tagName).toEqual("BODY");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders custom options", async () => {
|
||||||
|
const Wrapper = (props: SelectProps) => {
|
||||||
|
return (
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Select {...props} />
|
||||||
|
<Button>Blur</Button>
|
||||||
|
</CunninghamProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const props: SelectProps = {
|
||||||
|
label: "City",
|
||||||
|
searchable: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Paris",
|
||||||
|
value: "paris",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="paris.png" alt="Paris flag" />
|
||||||
|
Paris
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Panama",
|
||||||
|
value: "panama",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="panama.png" alt="Panama flag" />
|
||||||
|
Panama
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "London",
|
||||||
|
value: "london",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="london.png" alt="London flag" />
|
||||||
|
London
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = render(<Wrapper {...props} />);
|
||||||
|
const input = screen.getByRole("combobox", {
|
||||||
|
name: "City",
|
||||||
|
});
|
||||||
|
const menu: HTMLDivElement = screen.getByRole("listbox", {
|
||||||
|
name: "City",
|
||||||
|
});
|
||||||
|
const blurButton = screen.getByRole("button", { name: "Blur" });
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const valueRendered = document.querySelector(
|
||||||
|
".c__select__inner__value",
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
await user.click(input);
|
||||||
|
expectMenuToBeOpen(menu);
|
||||||
|
screen.getByRole("img", { name: "Paris flag" });
|
||||||
|
screen.getByRole("img", { name: "Panama flag" });
|
||||||
|
screen.getByRole("img", { name: "London flag" });
|
||||||
|
|
||||||
|
await user.type(input, "Pa");
|
||||||
|
screen.getByRole("img", { name: "Paris flag" });
|
||||||
|
screen.getByRole("img", { name: "Panama flag" });
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("img", { name: "London flag" }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("option", { name: "Paris flag Paris" }),
|
||||||
|
);
|
||||||
|
await user.click(blurButton);
|
||||||
|
|
||||||
|
// Make sure only the label is rendered by default.
|
||||||
|
expect(input).toHaveValue("Paris");
|
||||||
|
expect(input).not.toHaveClass("c__select__inner__value__input--hidden");
|
||||||
|
expect(
|
||||||
|
within(valueRendered).queryByRole("img", {
|
||||||
|
name: "Paris flag",
|
||||||
|
}),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Now showLabelWhenSelected to false.
|
||||||
|
rerender(<Wrapper {...props} showLabelWhenSelected={false} />);
|
||||||
|
|
||||||
|
// Make sure the HTML content of the option is rendered.
|
||||||
|
// The input is still present in the DOM ( but hidden for users ).
|
||||||
|
expect(input).toHaveValue("Paris");
|
||||||
|
expect(input).toHaveClass("c__select__inner__value__input--hidden");
|
||||||
|
within(valueRendered).getByRole("img", {
|
||||||
|
name: "Paris flag",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus on the input and make sure the custom HTML is removed.
|
||||||
|
await user.click(input);
|
||||||
|
expect(input).toHaveValue("Paris");
|
||||||
|
expect(input).not.toHaveClass("c__select__inner__value__input--hidden");
|
||||||
|
expect(
|
||||||
|
within(valueRendered).queryByRole("img", {
|
||||||
|
name: "Paris flag",
|
||||||
|
}),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Blur the input and make sure the custom HTML is rendered.
|
||||||
|
await user.click(blurButton);
|
||||||
|
expect(input).toHaveValue("Paris");
|
||||||
|
expect(input).toHaveClass("c__select__inner__value__input--hidden");
|
||||||
|
within(valueRendered).getByRole("img", {
|
||||||
|
name: "Paris flag",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Simple", () => {
|
describe("Simple", () => {
|
||||||
@@ -1644,5 +1767,91 @@ describe("<Select/>", () => {
|
|||||||
await waitFor(() => expectMenuToBeClosed(menu));
|
await waitFor(() => expectMenuToBeClosed(menu));
|
||||||
expect(document.activeElement?.className).toEqual("");
|
expect(document.activeElement?.className).toEqual("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders custom options", async () => {
|
||||||
|
const Wrapper = (props: SelectProps) => {
|
||||||
|
return (
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Select {...props} />
|
||||||
|
</CunninghamProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const props: SelectProps = {
|
||||||
|
label: "City",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Paris",
|
||||||
|
value: "paris",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="paris.png" alt="Paris flag" />
|
||||||
|
Paris
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Panama",
|
||||||
|
value: "panama",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="panama.png" alt="Panama flag" />
|
||||||
|
Panama
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "London",
|
||||||
|
value: "london",
|
||||||
|
render: () => (
|
||||||
|
<div>
|
||||||
|
<img src="london.png" alt="London flag" />
|
||||||
|
London
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = render(<Wrapper {...props} />);
|
||||||
|
const input = screen.getByRole("combobox", {
|
||||||
|
name: "City",
|
||||||
|
});
|
||||||
|
const menu: HTMLDivElement = screen.getByRole("listbox", {
|
||||||
|
name: "City",
|
||||||
|
});
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const valueRendered = document.querySelector(
|
||||||
|
".c__select__inner__value",
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
await user.click(input);
|
||||||
|
expectMenuToBeOpen(menu);
|
||||||
|
|
||||||
|
screen.getByRole("img", { name: "Paris flag" });
|
||||||
|
screen.getByRole("img", { name: "Panama flag" });
|
||||||
|
screen.getByRole("img", { name: "London flag" });
|
||||||
|
|
||||||
|
await user.click(
|
||||||
|
screen.getByRole("option", { name: "London flag London" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure only the label is rendered by default.
|
||||||
|
expect(valueRendered).toHaveTextContent("London");
|
||||||
|
expect(
|
||||||
|
within(valueRendered).queryByRole("img", {
|
||||||
|
name: "London flag",
|
||||||
|
}),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Now showLabelWhenSelected to false.
|
||||||
|
rerender(<Wrapper {...props} showLabelWhenSelected={false} />);
|
||||||
|
|
||||||
|
// Make sure the HTML content of the option is rendered.
|
||||||
|
expect(valueRendered).toHaveTextContent("London");
|
||||||
|
within(valueRendered).getByRole("img", {
|
||||||
|
name: "London flag",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import { onSubmit } from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils";
|
import { onSubmit } from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils";
|
||||||
import { Select, SelectHandle } from ":/components/Forms/Select";
|
import { Select, SelectHandle } from ":/components/Forms/Select";
|
||||||
import { Button } from ":/components/Button";
|
import { Button } from ":/components/Button";
|
||||||
import { RhfSelect } from ":/components/Forms/Select/stories-utils";
|
import {
|
||||||
|
getCountryOption,
|
||||||
|
RhfSelect,
|
||||||
|
} from ":/components/Forms/Select/stories-utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Components/Forms/Select/Mono",
|
title: "Components/Forms/Select/Mono",
|
||||||
@@ -228,6 +231,37 @@ export const NoOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CustomRender = {
|
||||||
|
render: Template,
|
||||||
|
args: {
|
||||||
|
label: "Select a country",
|
||||||
|
showLabelWhenSelected: false,
|
||||||
|
options: [
|
||||||
|
getCountryOption("Germany", "DE"),
|
||||||
|
getCountryOption("France", "FR"),
|
||||||
|
getCountryOption("United States", "US"),
|
||||||
|
getCountryOption("Spain", "ES"),
|
||||||
|
getCountryOption("China", "CN"),
|
||||||
|
],
|
||||||
|
defaultValue: "france",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const SearchableCustomRender = {
|
||||||
|
render: Template,
|
||||||
|
args: {
|
||||||
|
label: "Select a country",
|
||||||
|
showLabelWhenSelected: false,
|
||||||
|
searchable: true,
|
||||||
|
options: [
|
||||||
|
getCountryOption("Germany", "DE"),
|
||||||
|
getCountryOption("France", "FR"),
|
||||||
|
getCountryOption("United States", "US"),
|
||||||
|
getCountryOption("Spain", "ES"),
|
||||||
|
getCountryOption("China", "CN"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const Ref = () => {
|
export const Ref = () => {
|
||||||
const ref = useRef<SelectHandle>(null);
|
const ref = useRef<SelectHandle>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import { UseSelectStateChange } from "downshift";
|
|||||||
import { optionToValue, SubProps } from ":/components/Forms/Select/mono-common";
|
import { optionToValue, SubProps } from ":/components/Forms/Select/mono-common";
|
||||||
import { SelectMonoSearchable } from ":/components/Forms/Select/mono-searchable";
|
import { SelectMonoSearchable } from ":/components/Forms/Select/mono-searchable";
|
||||||
import { SelectMonoSimple } from ":/components/Forms/Select/mono-simple";
|
import { SelectMonoSimple } from ":/components/Forms/Select/mono-simple";
|
||||||
import { SelectHandle, SelectProps } from ":/components/Forms/Select";
|
import { Option, SelectHandle, SelectProps } from ":/components/Forms/Select";
|
||||||
|
|
||||||
export interface Option {
|
|
||||||
value?: string;
|
|
||||||
label: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SelectMono = forwardRef<SelectHandle, SelectProps>(
|
export const SelectMono = forwardRef<SelectHandle, SelectProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
|
|||||||
@@ -23,3 +23,18 @@ export const RhfSelect = (props: SelectProps & { name: string }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCountryOption = (name: string, code: string) => ({
|
||||||
|
value: name.toLowerCase(),
|
||||||
|
label: name,
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
|
||||||
|
<img
|
||||||
|
style={{ height: "24px" }}
|
||||||
|
src={`https://flagsapi.com/${code}/shiny/64.png`}
|
||||||
|
alt="Flag"
|
||||||
|
/>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|||||||
24
packages/react/src/components/Forms/Select/utils.tsx
Normal file
24
packages/react/src/components/Forms/Select/utils.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Option, OptionWithRender } from ":/components/Forms/Select";
|
||||||
|
|
||||||
|
export const isOptionWithRender = (
|
||||||
|
option: Option,
|
||||||
|
): option is OptionWithRender => {
|
||||||
|
return (option as OptionWithRender).render !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectedOption = ({
|
||||||
|
option,
|
||||||
|
showLabelWhenSelected = true,
|
||||||
|
}: {
|
||||||
|
option: Option | undefined | null;
|
||||||
|
showLabelWhenSelected?: boolean;
|
||||||
|
}) => {
|
||||||
|
if (!option) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isOptionWithRender(option) && !showLabelWhenSelected) {
|
||||||
|
return option.render();
|
||||||
|
}
|
||||||
|
return <span>{option.label}</span>;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user