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
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export const Filters: React.FC<{ identifier?: boolean; interval?: boolean }> = (
);

return (
<div className="flex items-center justify-between gap-2">
<div className="flex flex-row items-end justify-between gap-2">
{props.identifier ? (
<div>
<div className="flex-col align-end">
<ArrayInput
title="Identifiers"
selected={identifier}
Expand All @@ -60,7 +60,7 @@ export const Filters: React.FC<{ identifier?: boolean; interval?: boolean }> = (
</div>
) : null}{" "}
{props.interval ? (
<div>
<div className="flex flex-col">
<Select
value={interval}
onValueChange={(i: Interval) => {
Expand Down
120 changes: 54 additions & 66 deletions apps/dashboard/components/array-input.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"use client";

import { CornerDownLeft, X } from "lucide-react";
import * as React from "react";

import { Badge } from "@/components/ui/badge";
import { Command } from "@/components/ui/command";
import { Command as CommandPrimitive } from "cmdk";
import { CornerDownLeft, X } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";

type Props = {
title?: string;
Expand All @@ -15,85 +12,76 @@ type Props = {
};

export const ArrayInput: React.FC<Props> = ({ title, placeholder, selected, setSelected }) => {
const inputRef = React.useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = React.useState("");
const [items, setItems] = useState<string[]>(selected);

useEffect(() => {
setItems(selected);
}, [selected]);
const [inputValue, setInputValue] = useState("");

const handleUnselect = (o: string) => {
setSelected(selected.filter((s) => s !== o));
const handleUnselect = (item: string) => {
const newItems = items.filter((i) => i !== item);
setItems(newItems);
setSelected(newItems);
};

const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === "Delete" || e.key === "Backspace") {
if (input.value === "") {
setSelected(selected.slice(0, -1));
}
}
if (e.key === "Enter" && input.value !== "") {
setSelected(Array.from(new Set([...selected, input.value])));
setInputValue("");
}
// This is not a default behaviour of the <input /> field
if (e.key === "Escape") {
input.blur();
}
const handleAdd = () => {
if (inputValue.trim()) {
const newItems = Array.from(new Set([...items, inputValue.trim()]));
setItems(newItems);
setSelected(newItems);
setInputValue("");
}
};

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
handleAdd();
}
},
[selected, setSelected],
[handleAdd],
);

return (
<Command onKeyDown={handleKeyDown} className="overflow-visible bg-transparent">
<div className="flex items-center h-8 p-1 text-sm border rounded-md group focus-within:border-primary">
<div className="flex flex-wrap items-center w-full gap-1 px-2">
{title ? <span className="mr-1 text-xs font-medium">{title}:</span> : null}
{selected.map((o) => {
return (
<Badge key={o} variant="secondary">
{o}
<div className="bg-transparent flex-col relative max-w-96">
<div className="flex flex-row gap-2">
<div className="flex flex-col justify-end">
{title && <span className="text-xs font-medium mb-2 ">{title}:</span>}
</div>
<ul className="flex flex-wrap gap-1 mb-2 list-none p-0" aria-label="Selected items">
{items?.map((item) => (
<li key={item}>
<Badge variant="secondary">
{item}
<button
type="button"
className="ml-1 rounded-full outline-none"
onKeyDown={(e) => {
if (e.key === "Enter") {
handleUnselect(o);
}
}}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onClick={() => handleUnselect(o)}
onClick={() => handleUnselect(item)}
aria-label={`Remove ${item}`}
>
<X className="w-3 h-3 text-content-muted hover:text-content" />
</button>
</Badge>
);
})}
{/* Avoid having the "Search" Icon */}
<CommandPrimitive.Input
ref={inputRef}
</li>
))}
</ul>
</div>
<div className="flex items-center justify-center gap-2">
<div className="flex flex-wrap items-center w-full gap-1">
<Input
value={inputValue}
onValueChange={setInputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className="flex-1 w-full bg-transparent outline-none placeholder:text-content-subtle"
/>
</div>
<button
type="button"
className="inline-flex items-center rounded-md border px-2 py-0.5 text-xs border-border bg-secondary text-secondary-foreground hover:bg-secondary/20 font-normal"
onClick={() => {
if (inputValue !== "") {
setSelected(Array.from(new Set([...selected, inputValue])));
setInputValue("");
}
}}
>
<CornerDownLeft className="w-3 h-4" />
</button>
<Button size="icon" variant="secondary" onClick={handleAdd}>
<CornerDownLeft className="w-4 h-4" />
</Button>
</div>
</Command>
</div>
);
};