Skip to content

Commit

Permalink
feat(ComboBox): add ComboBox component
Browse files Browse the repository at this point in the history
  • Loading branch information
Lisa18289 committed Oct 23, 2024
1 parent 87d7dc0 commit cfaff34
Show file tree
Hide file tree
Showing 17 changed files with 90 additions and 74 deletions.
8 changes: 4 additions & 4 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
"types": "./dist/js/types/components/AlertIcon/index.d.ts",
"import": "./dist/js/AlertIcon.js"
},
"./Autocomplete/styles.css": "./dist/css/Autocomplete.css",
"./Autocomplete": {
"types": "./dist/types/components/Autocomplete/index.d.ts",
"import": "./dist/Autocomplete.js"
"./ComboBox/styles.css": "./dist/css/ComboBox.css",
"./ComboBox": {
"types": "./dist/types/components/ComboBox/index.d.ts",
"import": "./dist/ComboBox.js"
},
"./Avatar/styles.css": "./dist/css/Avatar.css",
"./Avatar": {
Expand Down
4 changes: 0 additions & 4 deletions packages/components/src/components/Autocomplete/index.ts

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@use "@/styles/mixins/formControl.scss";

.autocomplete {
.comboBox {
.input {
order: 2;
display: grid;
Expand All @@ -10,13 +10,16 @@
@include formControl.formControl();
grid-area: input;
}

.toggle {
grid-area: input;
justify-self: end;
margin: var(--form-control--border-width);

&:hover {
background-color: transparent;
}

&[data-pressed] {
background-color: transparent;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FC, PropsWithChildren } from "react";
import React from "react";
import type { Key } from "react-aria-components";
import * as Aria from "react-aria-components";
import { TunnelExit, TunnelProvider } from "@mittwald/react-tunnel";
import { Button } from "@/components/Button";
Expand All @@ -8,23 +9,24 @@ import { Options } from "@/components/Options";
import type { PropsContext } from "@/lib/propsContext";
import { PropsContextProvider } from "@/lib/propsContext";
import clsx from "clsx";
import styles from "./Autocomplete.module.scss";
import styles from "./ComboBox.module.scss";
import formFieldStyles from "@/components/FormField/FormField.module.scss";
import locales from "./locales/*.locale.json";
import { useLocalizedStringFormatter } from "react-aria";
import type { FlowComponentProps } from "@/lib/componentFactory/flowComponent";
import { flowComponent } from "@/lib/componentFactory/flowComponent";
import type { Key } from "react-aria-components";
import { type OverlayController, useOverlayController } from "@/lib/controller";

export interface AutocompleteProps
export interface ComboBoxProps
extends Omit<Aria.ComboBoxProps<never>, "children">,
PropsWithChildren,
FlowComponentProps {
onChange?: (value: string) => void;
controller?: OverlayController;
}

export const Autocomplete: FC<AutocompleteProps> = flowComponent(
"Autocomplete",
export const ComboBox: FC<ComboBoxProps> = flowComponent(
"ComboBox",
(props) => {
const {
children,
Expand All @@ -36,14 +38,15 @@ export const Autocomplete: FC<AutocompleteProps> = flowComponent(
onSelectionChange = () => {
// default: do nothing
},
controller: controllerFromProps,
refProp: ref,
...rest
} = props;

const stringFormatter = useLocalizedStringFormatter(locales);

const rootClassName = clsx(
styles.autocomplete,
styles.comboBox,
formFieldStyles.formField,
className,
);
Expand All @@ -69,21 +72,32 @@ export const Autocomplete: FC<AutocompleteProps> = flowComponent(
onSelectionChange(key);
};

const controllerFromContext = useOverlayController("ComboBox", {
reuseControllerFromContext: true,
});

const controller = controllerFromProps ?? controllerFromContext;

const isOpen = controller.useIsOpen();

console.log(isOpen);

return (
<Aria.ComboBox
menuTrigger={menuTrigger}
className={rootClassName}
{...rest}
ref={ref}
onSelectionChange={handleOnSelectionChange}
onOpenChange={(isOpen) => controller.setOpen(isOpen)}
>
<PropsContextProvider props={propsContext}>
<TunnelProvider>
<div className={styles.input}>
<Aria.Input />
<Button
className={styles.toggle}
aria-label={stringFormatter.format("autocomplete.showOptions")}
aria-label={stringFormatter.format("comboBox.showOptions")}
variant="plain"
color="secondary"
>
Expand All @@ -93,7 +107,7 @@ export const Autocomplete: FC<AutocompleteProps> = flowComponent(

{children}

<Options>
<Options controller={controller}>
<TunnelExit id="options" />
</Options>
</TunnelProvider>
Expand All @@ -103,4 +117,4 @@ export const Autocomplete: FC<AutocompleteProps> = flowComponent(
},
);

export default Autocomplete;
export default ComboBox;
5 changes: 5 additions & 0 deletions packages/components/src/components/ComboBox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ComboBox } from "./ComboBox";

export { type ComboBoxProps, ComboBox } from "./ComboBox";
export * from "@/components/Options/components/Option";
export default ComboBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"comboBox.showOptions": "Auswahlmöglichkeiten anzeigen"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"comboBox.showOptions": "Auswahlmöglichkeiten anzeigen"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Label } from "@/components/Label";
import { Option } from "@/components/Options";
import FieldDescription from "@/components/FieldDescription";
import { FieldError } from "@/components/FieldError";
import { Autocomplete } from "@/components/Autocomplete";
import { ComboBox } from "src/components/ComboBox";

const meta: Meta<typeof Autocomplete> = {
title: "Form Controls/Autocomplete",
component: Autocomplete,
const meta: Meta<typeof ComboBox> = {
title: "Form Controls/ComboBox",
component: ComboBox,
render: (props) => (
<Autocomplete {...props}>
<ComboBox {...props}>
<Label>Domain</Label>
<Option>mydomain.de</Option>
<Option>shop.mydomain.de</Option>
Expand All @@ -20,12 +20,12 @@ const meta: Meta<typeof Autocomplete> = {
<Option>anotherdomain.com/blog</Option>
<Option>onemoredomain.de</Option>
<Option>www.onemoredomain.de</Option>
</Autocomplete>
</ComboBox>
),
};
export default meta;

type Story = StoryObj<typeof Autocomplete>;
type Story = StoryObj<typeof ComboBox>;

export const Default: Story = {};

Expand All @@ -37,7 +37,7 @@ export const Required: Story = {

export const WithFieldDescription: Story = {
render: (props) => (
<Autocomplete {...props}>
<ComboBox {...props}>
<Label>Domain</Label>
<Option>mydomain.de</Option>
<Option>shop.mydomain.de</Option>
Expand All @@ -48,13 +48,13 @@ export const WithFieldDescription: Story = {
<Option>onemoredomain.de</Option>
<Option>www.onemoredomain.de</Option>
<FieldDescription>Select a domain</FieldDescription>
</Autocomplete>
</ComboBox>
),
};

export const WithDefaultValue: Story = {
render: (props) => (
<Autocomplete {...props} defaultSelectedKey="mydomain.de">
<ComboBox {...props} defaultSelectedKey="mydomain.de">
<Label>Domain</Label>
<Option value="mydomain.de">mydomain.de</Option>
<Option>shop.mydomain.de</Option>
Expand All @@ -64,13 +64,13 @@ export const WithDefaultValue: Story = {
<Option>anotherdomain.com/blog</Option>
<Option>onemoredomain.de</Option>
<Option>www.onemoredomain.de</Option>
</Autocomplete>
</ComboBox>
),
};

export const WithFieldError: Story = {
render: (props) => (
<Autocomplete {...props} isInvalid isRequired>
<ComboBox {...props} isInvalid isRequired>
<Label>Domain</Label>
<Option>mydomain.de</Option>
<Option>shop.mydomain.de</Option>
Expand All @@ -81,6 +81,6 @@ export const WithFieldError: Story = {
<Option>onemoredomain.de</Option>
<Option>www.onemoredomain.de</Option>
<FieldError>Select a domain to continue</FieldError>
</Autocomplete>
</ComboBox>
),
};
12 changes: 7 additions & 5 deletions packages/components/src/components/Options/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import { Popover } from "@/components/Popover";
import clsx from "clsx";
import type { OptionProps } from "@/components/Select";
import styles from "./Options.module.scss";
import { useOverlayController } from "@/lib/controller";
import type { OverlayController } from "@/lib/controller";

export type OptionsProps = Aria.ListBoxProps<OptionProps>;
export interface OptionsProps extends Aria.ListBoxProps<OptionProps> {
controller: OverlayController;
}

export const Options: FC<OptionsProps> = (props) => {
const { className, children, ...rest } = props;
const { className, children, controller, ...rest } = props;

const rootClassName = clsx(styles.options, className);

const controller = useOverlayController("Select");
const isOpen = controller.useIsOpen();

return (
<Popover className={styles.popover} controller={controller}>
<Popover className={styles.popover} isOpen={isOpen} controller={controller}>
<Aria.ListBox className={rootClassName} {...rest}>
{children}
</Aria.ListBox>
Expand Down
31 changes: 14 additions & 17 deletions packages/components/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import clsx from "clsx";
import { IconChevronDown } from "@/components/Icon/components/icons";
import type { FlowComponentProps } from "@/lib/componentFactory/flowComponent";
import { flowComponent } from "@/lib/componentFactory/flowComponent";
import { Options } from "@/components/Select/components/Options";
import { Options } from "@/components/Options";
import { TunnelExit, TunnelProvider } from "@mittwald/react-tunnel";
import type { PropsWithClassName } from "@/lib/types/props";
import { type OverlayController, useOverlayController } from "@/lib/controller";
import OverlayContextProvider from "@/lib/controller/overlay/OverlayContextProvider";

export interface SelectProps
extends PropsWithChildren<
Expand Down Expand Up @@ -86,23 +85,21 @@ export const Select = flowComponent("Select", (props) => {
onOpenChange={(isOpen) => controller.setOpen(isOpen)}
isOpen={isOpen}
>
<OverlayContextProvider type="Select" controller={controller}>
<PropsContextProvider props={propsContext}>
<TunnelProvider>
<Aria.Button className={styles.toggle}>
<Aria.SelectValue />
<IconChevronDown />
</Aria.Button>
<PropsContextProvider props={propsContext}>
<TunnelProvider>
<Aria.Button className={styles.toggle}>
<Aria.SelectValue />
<IconChevronDown />
</Aria.Button>

{children}
<Options>
<TunnelExit id="options" />
</Options>
{children}
<Options controller={controller}>
<TunnelExit id="options" />
</Options>

<FieldError className={formFieldStyles.fieldError} />
</TunnelProvider>
</PropsContextProvider>
</OverlayContextProvider>
<FieldError className={formFieldStyles.fieldError} />
</TunnelProvider>
</PropsContextProvider>
</Aria.Select>
);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/components/propTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import type { DateRangePickerProps } from "@/components/DateRangePicker";
import type { TimeFieldProps } from "@/components/TimeField";
import type { AlertIconProps } from "@/components/AlertIcon";
import type { ListSummaryProps } from "@/components/List/components/ListSummary/ListSummary";
import type { AutocompleteProps } from "@/components/Autocomplete";
import type { ComboBoxProps } from "src/components/ComboBox";

export * from "./types";

Expand All @@ -60,7 +60,7 @@ export interface FlowComponentPropsTypes {
Alert: AlertProps;
AlertBadge: AlertBadgeProps;
AlertIcon: AlertIconProps;
Autocomplete: AutocompleteProps;
ComboBox: ComboBoxProps;
Avatar: AvatarProps;
Badge: BadgeProps;
Button: ButtonProps;
Expand Down Expand Up @@ -120,7 +120,7 @@ const propsContextSupportingComponentsMap: Record<
Alert: true,
AlertBadge: true,
AlertIcon: true,
Autocomplete: true,
ComboBox: true,
Avatar: true,
Badge: true,
Button: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function Field<T extends FieldValues>(props: Props<T>) {
DatePicker: formControlProps,
DateRangePicker: formControlProps,
TimeField: formControlProps,
Autocomplete: formControlProps,
ComboBox: formControlProps,
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import { DateRangePicker } from "@/components/DateRangePicker";
import { getLocalTimeZone, today } from "@internationalized/date";
import { TimeField } from "@/components/TimeField";
import { TextArea } from "@/components/TextArea";
import { Autocomplete } from "@/components/Autocomplete";

import { ComboBox } from "src/components/ComboBox";

const submitAction = action("submit");

Expand Down Expand Up @@ -192,11 +191,11 @@ const meta: Meta<typeof Field> = {
required: "Please select a domain",
}}
>
<Autocomplete>
<ComboBox>
<Label>Domain</Label>
<Option value="mydomain.de">mydomain.de</Option>
<Option value="anotherdomain.com">anotherdomain.com</Option>
</Autocomplete>
</ComboBox>
</Field>

<ActionGroup>
Expand Down
Loading

0 comments on commit cfaff34

Please sign in to comment.