Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): add select-comobobox reusable component #94

Merged
merged 6 commits into from May 6, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/modern-carpets-brush.md
@@ -0,0 +1,5 @@
---
"@trueplan/forecast-components": minor
---

[select-combobox]: creates a reusable version that gets passed select and combobox state. Also still exports styled parts.
7 changes: 2 additions & 5 deletions packages/components/src/components/select-combobox/index.ts
Expand Up @@ -2,8 +2,5 @@ export {
SelectItem as SelectComboboxItem,
useSelectState as useSelectComboboxState,
} from "ariakit/select";
export {
StyledSelect as SelectCombobox,
StyledSelectPopover as SelectComboboxPopover,
StyledComboboxList as SelectComboboxList,
} from "./src/styles";
export * from "./src/styles";
export * from "./src";
61 changes: 61 additions & 0 deletions packages/components/src/components/select-combobox/src/index.tsx
@@ -0,0 +1,61 @@
import * as React from "react";
import { SelectItem as SelectComboboxItem } from "ariakit/select";
import type { SelectState } from "ariakit/select";
import type { ComboboxState } from "ariakit/combobox";
import { InputBox } from "../../input-box";
import { Box } from "../../../primitives/box";
import { Label } from "../../label";
import { VisuallyHidden } from "../../visually-hidden";
import { StyledComboboxItem, StyledCombobox } from "../../combobox";
import {
StyledSelectElement as SelectComboboxElement,
StyledSelectPopover as SelectComboboxPopover,
StyledComboboxList as SelectComboboxList,
} from "./styles";

export interface SelectComoboxElementProps {
comboboxState: ComboboxState;
id: string;
label: string;
selectState: SelectState;
}

const SelectCombobox = React.forwardRef<
HTMLButtonElement,
SelectComoboxElementProps
>(({ comboboxState, id, label, selectState, ...props }, ref) => {
return (
<>
<VisuallyHidden>
<Label htmlFor={id}>{label}</Label>
</VisuallyHidden>
<InputBox hasHover {...props}>
<SelectComboboxElement state={selectState} id={id} ref={ref} />
</InputBox>
<SelectComboboxPopover state={selectState} composite={false}>
<Box css={{ marginBottom: "$25" }}>
<InputBox>
<StyledCombobox
state={comboboxState}
autoSelect
placeholder="Search..."
/>
</InputBox>
</Box>
<SelectComboboxList state={comboboxState}>
{comboboxState.matches.map((itemValue, i) => (
<StyledComboboxItem key={itemValue + i} focusOnHover>
{(itemProps) => (
<SelectComboboxItem {...itemProps} value={itemValue} />
)}
</StyledComboboxItem>
))}
</SelectComboboxList>
</SelectComboboxPopover>
</>
);
});

SelectCombobox.displayName = "SelectComobox";

export { SelectCombobox };
Expand Up @@ -9,7 +9,7 @@ export const StyledComboboxList = styled(ComboboxList, {
overflow: "auto",
});

export const StyledSelect = styled(SelectPrimitive, {
export const StyledSelectElement = styled(SelectPrimitive, {
appearance: "none",
backgroundColor: "transparent",
border: "none",
Expand All @@ -29,6 +29,12 @@ export const StyledSelect = styled(SelectPrimitive, {
padding: theme.space[20],
textAlign: "inherit",
width: "100%",
// Fix for empty value
minHeight: "40px",
"& span": {
marginLeft: "auto",
},
// End fix for empty value
"&:disabled": {
color: theme.colors.gray70,
cursor: "not-allowed",
Expand Down
@@ -1,68 +1,36 @@
import * as React from "react";
import { InputBox } from "../../input-box";
import { Box } from "../../../primitives/box";
import {
StyledComboboxItem,
StyledCombobox,
useComboboxState,
} from "../../combobox";
import {
SelectComboboxItem,
useSelectComboboxState,
SelectCombobox,
SelectComboboxPopover,
SelectComboboxList,
} from "../index";
import { useUID } from "react-uid";
import { useComboboxState } from "../../combobox";
import { SelectCombobox, useSelectComboboxState } from "../index";
import { itemList as list } from "../../combobox/__fixtures__/item-list";

// eslint-disable-next-line import/no-default-export
export default {
title: "Components/SelectCombobox",
component: SelectCombobox,
};

export const Default: React.FC = () => {
const combobox = useComboboxState({ list, gutter: 4, sameWidth: true });
// value and setValue shouldn't be passed to the select state because the
// select value and the combobox value are different things.
// value and setValue are in the combobox state
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { value, setValue, ...selectProps } = combobox;
const select = useSelectComboboxState({
...selectProps,
defaultValue: "Apple",
});

// Resets combobox value when popover is collapsed
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// eslint-disable-next-line unicorn/consistent-destructuring
if (!select.mounted && combobox.value) {
combobox.setValue("");
if (!select.mounted && value) {
setValue("");
}

const selectComboboxID = useUID();

return (
<>
<InputBox hasHover>
<SelectCombobox state={select} />
</InputBox>
<SelectComboboxPopover state={select} composite={false}>
<Box css={{ marginBottom: "$25" }}>
<InputBox>
<StyledCombobox
state={combobox}
autoSelect
placeholder="Search..."
/>
</InputBox>
</Box>
<SelectComboboxList state={combobox}>
{combobox.matches.map((itemValue, i) => (
<StyledComboboxItem key={itemValue + i} focusOnHover>
{(props) => <SelectComboboxItem {...props} value={itemValue} />}
</StyledComboboxItem>
))}
</SelectComboboxList>
</SelectComboboxPopover>
</>
<SelectCombobox
comboboxState={combobox}
id={selectComboboxID}
label="Select a fruit"
selectState={select}
/>
);
};

Expand All @@ -73,50 +41,53 @@ export const DefaultOpen: React.FC = () => {
sameWidth: true,
visible: true,
});
// value and setValue shouldn't be passed to the select state because the
// select value and the combobox value are different things.
// value and setValue are in the combobox state
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { value, setValue, ...selectProps } = combobox;
const select = useSelectComboboxState({
...selectProps,
defaultValue: "Apple",
visible: true,
});

// Resets combobox value when popover is collapsed
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// eslint-disable-next-line unicorn/consistent-destructuring
if (!select.mounted && combobox.value) {
combobox.setValue("");
if (!select.mounted && value) {
setValue("");
}

const selectComboboxID = useUID();

return (
<SelectCombobox
comboboxState={combobox}
id={selectComboboxID}
label="Select a fruit"
selectState={select}
/>
);
};

export const EmptyValue: React.FC = () => {
const combobox = useComboboxState({
list,
gutter: 4,
sameWidth: true,
});
const { value, setValue, ...selectProps } = combobox;
const select = useSelectComboboxState({
...selectProps,
defaultValue: "",
});

if (!select.mounted && value) {
setValue("");
}

const selectComboboxID = useUID();

return (
<>
<InputBox hasHover>
<SelectCombobox state={select} />
</InputBox>
<SelectComboboxPopover state={select} composite={false}>
<Box css={{ marginBottom: "$25" }}>
<InputBox>
<StyledCombobox
state={combobox}
autoSelect
placeholder="Search..."
/>
</InputBox>
</Box>
<SelectComboboxList state={combobox}>
{
// eslint-disable-next-line sonarjs/no-identical-functions
combobox.matches.map((itemValue, i) => (
<StyledComboboxItem key={itemValue + i} focusOnHover>
{(props) => <SelectComboboxItem {...props} value={itemValue} />}
</StyledComboboxItem>
))
}
</SelectComboboxList>
</SelectComboboxPopover>
</>
<SelectCombobox
comboboxState={combobox}
id={selectComboboxID}
label="Select a fruit"
selectState={select}
/>
);
};