Skip to content

Commit

Permalink
wip(combobox): style the combobox
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcMcIntosh committed Jan 12, 2024
1 parent 60582a4 commit cead59d
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 14 deletions.
19 changes: 19 additions & 0 deletions src/components/ComboBox/ComboBox.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.popover {
position: relative;
z-index: 50;
min-width: 180px;
max-width: 280px;
border-radius: max(var(--radius-2), var(--radius-full));
}

.popover__scroll {
--scrollarea-scrollbar-vertical-margin-right: 2px;
max-height: min(var(--popover-available-height, 186px), 186px);
}

.item {
display: flex;
cursor: default;
align-items: center;
justify-content: flex-start;
}
133 changes: 120 additions & 13 deletions src/components/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,97 @@ import {
Combobox,
ComboboxPopover,
ComboboxItem,
type ComboboxStore,
} from "@ariakit/react";
import { Box } from "@radix-ui/themes";
import { matchSorter } from "match-sorter";
import { getAnchorRect } from "./utils";
import { getAnchorRect, type AnchorRect } from "./utils";
import { TextArea } from "../TextArea";
import { ScrollArea } from "../ScrollArea";
import { Button } from "@radix-ui/themes";
import classNames from "classnames";
import styles from "./ComboBox.module.css";

const Item: React.FC<{
onClick: React.MouseEventHandler<HTMLDivElement>;
value: string;
children: React.ReactNode;
}> = ({ children, value, onClick }) => {
return (
<Button className={styles.item} variant="ghost" asChild>
<ComboboxItem value={value} onClick={onClick} focusOnHover>
{children}
</ComboboxItem>
</Button>
);
};

const Popover: React.FC<
React.PropsWithChildren & {
store: ComboboxStore;
hidden: boolean;
getAnchorRect: (anchor: HTMLElement | null) => AnchorRect | null;
}
> = ({ children, ...props }) => {
return (
<Box
asChild
className={classNames(
"rt-PopperContent",
"rt-HoverCardContent",
styles.popover,
)}
>
<ComboboxPopover unmountOnHide fitViewport {...props}>
<ScrollArea scrollbars="vertical" className={styles.popover__scroll}>
<Box p="1" style={{ overflowY: "hidden" }}>
{children}
</Box>
</ScrollArea>
</ComboboxPopover>
</Box>
);
};

export const ComboBox = () => {
const commands = ["@workspace", "@help", "@list"];
const commands = [
"@workspace",
"@help",
"@list",
"@web",
"@database",
"@?",
"@longlonglonglong",
"@refactor",
"@test",
"@marc",
"@Apple",
"@Banana",
"@Carrot",
"@Dill",
"@Elderberries",
"@Figs",
"@Grapes",
"@Honeydew",
"@Iced melon",
"@Jackfruit",
"@Kale",
"@Lettuce",
"@Mango",
"@Nectarines",
"@Oranges",
"@Pineapple",
"@Quince",
"@Raspberries",
"@Strawberries",
"@Turnips",
"@Ugli fruit",
"@Vanilla beans",
"@Watermelon",
"@Xigua",
"@Yuzu",
"@Zucchini",
];
const ref = React.useRef<HTMLTextAreaElement>(null);
const [value, setValue] = React.useState("");
const [trigger, setTrigger] = React.useState<string>("");
Expand All @@ -19,7 +103,7 @@ export const ComboBox = () => {

const matches = matchSorter(commands, trigger, {
baseSort: (a, b) => (a.index < b.index ? -1 : 1),
}).slice(0, 10);
});

const hasMatches = !!matches.length;

Expand All @@ -32,6 +116,8 @@ export const ComboBox = () => {
}, [combobox, value]);

const onKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
// TODO: pressing enter should submit the form
// TODO: shift+enter should create a new line
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
combobox.hide();
}
Expand Down Expand Up @@ -81,7 +167,7 @@ export const ComboBox = () => {
render={
<TextArea
ref={ref}
rows={5}
rows={5} // TODO: remove this later
placeholder="Type @ for commands"
onScroll={combobox.render}
onPointerDown={combobox.hide}
Expand All @@ -90,28 +176,49 @@ export const ComboBox = () => {
/>
}
/>
<ComboboxPopover
<Popover
store={combobox}
hidden={!hasMatches}
unmountOnHide
fitViewport
getAnchorRect={() => {
const textarea = ref.current;
if (!textarea) return null;
return getAnchorRect(textarea, commands);
}}
>
{matches.map((item) => (
<ComboboxItem
key={item}
{matches.map((item, index) => (
<Item
key={item + "-" + index}
value={item}
focusOnHover
onClick={() => onItemClick(item)}
>
{item}
</ComboboxItem>
</Item>
))}
</ComboboxPopover>
</Popover>
{/* <Box asChild>
<ComboboxPopover
className={styles.popover2}
store={combobox}
hidden={!hasMatches}
unmountOnHide
fitViewport
getAnchorRect={() => {
const textarea = ref.current;
if (!textarea) return null;
return getAnchorRect(textarea, commands);
}}
>
{matches.map((item, index) => (
<Item
key={item + "-" + index}
value={item}
onClick={() => onItemClick(item)}
>
{item}
</Item>
))}
</ComboboxPopover>
</Box> */}
</>
);
};
8 changes: 7 additions & 1 deletion src/components/ComboBox/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ export function getTriggerOffset(
return -1;
}

export type AnchorRect = {
x: number;
y: number;
height: number;
};

export function getAnchorRect(
element: HTMLTextAreaElement,
triggers: string[],
) {
): AnchorRect {
const offset = getTriggerOffset(element, triggers);
const { left, top, height } = getCaretCoordinates(element, offset + 1);
const { x, y } = element.getBoundingClientRect();
Expand Down

0 comments on commit cead59d

Please sign in to comment.