feat: keystroke
This commit is contained in:
@@ -3,7 +3,11 @@ import type {
|
||||
ProductItemInterval,
|
||||
} from "../../../models/productV2Models/productItemModels/productItemModels.js";
|
||||
import type { ProductV2 } from "../../../models/productV2Models/productV2Models.js";
|
||||
import { isPriceItem } from "../../productV2Utils/productItemUtils/getItemType.js";
|
||||
import {
|
||||
isFeatureItem,
|
||||
isFeaturePriceItem,
|
||||
isPriceItem,
|
||||
} from "../../productV2Utils/productItemUtils/getItemType.js";
|
||||
|
||||
export function productV2ToBasePrice({ product }: { product: ProductV2 }): {
|
||||
amount: number;
|
||||
@@ -24,3 +28,13 @@ export function productV2ToBasePrice({ product }: { product: ProductV2 }): {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const productV2ToFeatureItems = ({
|
||||
items,
|
||||
}: {
|
||||
items: ProductItem[];
|
||||
}) => {
|
||||
return items.filter(
|
||||
(item) => isFeatureItem(item) || isFeaturePriceItem(item),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border border-zinc-200 px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-950 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-300",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
|
||||
secondary:
|
||||
"border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
|
||||
destructive:
|
||||
"border-transparent bg-red-500 text-zinc-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/80",
|
||||
outline: "text-zinc-950 dark:text-zinc-50",
|
||||
purple: "bg-purple-50 text-purple-500 border-purple-200",
|
||||
blue: "bg-blue-50 text-blue-500 border-blue-200",
|
||||
green:
|
||||
"bg-green-50 text-green-500 border-green-200 !hover:bg-green-100",
|
||||
yellow: "bg-yellow-50 text-yellow-500 border-yellow-200",
|
||||
red: "bg-red-50 text-red-500 border-red-200",
|
||||
status: "px-1.5 py-0 text-white border-none",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
60
vite/src/components/v2/badges/Badge.tsx
Normal file
60
vite/src/components/v2/badges/Badge.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-950 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-300
|
||||
|
||||
const badgeVariants = cva(
|
||||
`inline-flex items-center rounded-lg border border-zinc-200 px-1.5 py-0.5
|
||||
|
||||
// Custom stuff
|
||||
text-body-secondary
|
||||
`,
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
|
||||
|
||||
muted: "bg-muted border border-transparent",
|
||||
// secondary:
|
||||
// "border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
|
||||
// destructive:
|
||||
// "border-transparent bg-red-500 text-zinc-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/80",
|
||||
// outline: "text-zinc-950 dark:text-zinc-50",
|
||||
// purple: "bg-purple-50 text-purple-500 border-purple-200",
|
||||
// blue: "bg-blue-50 text-blue-500 border-blue-200",
|
||||
// green:
|
||||
// "bg-green-50 text-green-500 border-green-200 !hover:bg-green-100",
|
||||
// yellow: "bg-yellow-50 text-yellow-500 border-yellow-200",
|
||||
// red: "bg-red-50 text-red-500 border-red-200",
|
||||
// status: "px-1.5 py-0 text-white border-none",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
@@ -1,22 +1,118 @@
|
||||
import type { ReactElement } from "react";
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
|
||||
interface IconBadgeProps {
|
||||
icon: ReactElement;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
const iconBadgeVariants = cva(
|
||||
"flex-row justify-center gap-[3px] min-w-10", // Gap is handled in position variants
|
||||
{
|
||||
variants: {
|
||||
position: {
|
||||
left: "flex-row",
|
||||
right: "flex-row-reverse",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
position: "left",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface IconBadgeProps
|
||||
extends Omit<
|
||||
React.ComponentProps<"span">,
|
||||
keyof VariantProps<typeof iconBadgeVariants>
|
||||
>,
|
||||
VariantProps<typeof iconBadgeVariants> {
|
||||
icon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
variant?: "default" | "muted";
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
export const IconBadge = ({
|
||||
icon,
|
||||
children,
|
||||
className = "",
|
||||
}: IconBadgeProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex items-center gap-2 bg-white border border-border rounded-lg h-[24px] w-fit px-2 py-[3px] shadow-[0px_4px_4px_rgba(0,0,0,0.02),_inset_0px_-3px_4px_rgba(0,0,0,0.04)] ${className}`}
|
||||
>
|
||||
<span className="text-t4">{icon}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const IconBadge = React.forwardRef<HTMLSpanElement, IconBadgeProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant = "muted",
|
||||
position = "left",
|
||||
asChild = false,
|
||||
icon,
|
||||
rightIcon,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const getIconColor = () => {
|
||||
switch (variant) {
|
||||
case "muted":
|
||||
return "text-t4";
|
||||
case "default":
|
||||
return "text-zinc-50";
|
||||
default:
|
||||
return "text-t4"; // Default fallback
|
||||
}
|
||||
};
|
||||
|
||||
const renderIcon = (icon: React.ReactNode) => {
|
||||
if (!icon) return null;
|
||||
|
||||
// Clone the icon and add default size and color classes
|
||||
if (React.isValidElement(icon)) {
|
||||
return React.cloneElement(icon, {
|
||||
className: cn("size-3", getIconColor(), icon.props.className),
|
||||
} as React.HTMLAttributes<HTMLElement>);
|
||||
}
|
||||
|
||||
return icon;
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
switch (position) {
|
||||
case "left":
|
||||
return (
|
||||
<>
|
||||
{renderIcon(icon)}
|
||||
{children}
|
||||
{renderIcon(rightIcon)}
|
||||
</>
|
||||
);
|
||||
case "right":
|
||||
return (
|
||||
<>
|
||||
{renderIcon(rightIcon)}
|
||||
{children}
|
||||
{renderIcon(icon)}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
{renderIcon(icon)}
|
||||
{children}
|
||||
{renderIcon(rightIcon)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
asChild={asChild}
|
||||
className={cn(iconBadgeVariants({ position }), className)}
|
||||
{...props}
|
||||
>
|
||||
{renderContent()}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
IconBadge.displayName = "IconBadge";
|
||||
|
||||
export { IconBadge, iconBadgeVariants };
|
||||
|
||||
@@ -16,15 +16,6 @@ export const PlanTypeBadge = ({ product }: { product: any }) => {
|
||||
if (!badgeType) return null;
|
||||
|
||||
return (
|
||||
// <>
|
||||
// {product.is_default ? (
|
||||
// <Badge variant="outline">Default</Badge>
|
||||
// ) : product.is_add_on ? (
|
||||
// <Badge variant="outline">Add-On</Badge>
|
||||
// ) : (
|
||||
// <></>
|
||||
// )}
|
||||
// </>
|
||||
<Badge
|
||||
className={cn(
|
||||
"bg-transparent border border-t1 text-t1 rounded-md px-2 pointer-events-none",
|
||||
|
||||
@@ -114,7 +114,7 @@ export const PlanSheets = ({ sheet }: { sheet: Sheets }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md bg-card z-50 border-l shadow-sm flex flex-col overflow-y-auto h-full">
|
||||
<div className="w-full min-w-xs max-w-md bg-card z-50 border-l shadow-sm flex flex-col overflow-y-auto h-full">
|
||||
{renderSheet()}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { UserIcon } from "@phosphor-icons/react";
|
||||
import { AdminHover } from "@/components/general/AdminHover";
|
||||
import { Badge } from "@/components/v2/badge";
|
||||
import { Badge } from "@/components/v2/badges/Badge";
|
||||
import { IconBadge } from "@/components/v2/badges/IconBadge";
|
||||
import V2Breadcrumb from "@/components/v2/breadcrumb";
|
||||
import { PlanTypeBadge } from "../../components/PlanTypeBadge";
|
||||
import { useProductCountsQuery } from "../../product/hooks/queries/useProductCountsQuery";
|
||||
import { useProductQuery } from "../../product/hooks/useProductQuery";
|
||||
|
||||
export const EditPlanHeader = () => {
|
||||
const { product } = useProductQuery();
|
||||
const { counts } = useProductCountsQuery();
|
||||
|
||||
const getProductAdminHover = () => {
|
||||
return [
|
||||
@@ -25,7 +29,7 @@ export const EditPlanHeader = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-4 w-full bg-card">
|
||||
<div className="flex flex-col gap-2 p-4 pb-3 w-full bg-card">
|
||||
<V2Breadcrumb
|
||||
className="p-0"
|
||||
items={[
|
||||
@@ -50,7 +54,10 @@ export const EditPlanHeader = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<Badge variant="secondary">Add-on</Badge>
|
||||
<Badge variant="muted">Add-on</Badge>
|
||||
<IconBadge variant="muted" icon={<UserIcon />}>
|
||||
{counts?.active || 0}
|
||||
</IconBadge>
|
||||
<PlanTypeBadge product={product} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function EditorTopSection() {
|
||||
const { product } = useProductContext();
|
||||
|
||||
return (
|
||||
<div className="flex pl-4 pr-10 flex-col gap-2">
|
||||
<div className="flex pl-4 pt-6 pr-10 flex-col gap-2">
|
||||
<div className="col-span-2 flex">
|
||||
<div className="flex flex-row items-baseline justify-start gap-2 w-full whitespace-nowrap">
|
||||
<AdminHover
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { type Feature, ProductItemInterval } from "@autumn/shared";
|
||||
import {
|
||||
type Feature,
|
||||
ProductItemInterval,
|
||||
productV2ToFeatureItems,
|
||||
} from "@autumn/shared";
|
||||
import { PlusIcon } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
import { CustomDialogContent } from "@/components/general/modal-components/DialogContentWrapper";
|
||||
@@ -10,6 +14,7 @@ import {
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/v2/buttons/Button";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { getItemId } from "@/utils/product/productItemUtils";
|
||||
import { CreateFeature } from "@/views/products/features/components/CreateFeature";
|
||||
import { FeatureTypeBadge } from "@/views/products/features/components/FeatureTypeBadge";
|
||||
import { useProductContext } from "@/views/products/product/ProductContext";
|
||||
@@ -52,9 +57,10 @@ export const AddFeatureRow = ({ disabled }: AddFeatureRowProps) => {
|
||||
setPopoverOpen(false);
|
||||
|
||||
// Open edit sidebar for the new item
|
||||
const itemIndex = newItems.length;
|
||||
const itemId =
|
||||
newItem.entity_feature_id || newItem.feature_id || `item-${itemIndex}`;
|
||||
const featureItems = productV2ToFeatureItems({ items: newItems });
|
||||
const itemIndex = featureItems.length - 1;
|
||||
const itemId = getItemId({ item: newItem, itemIndex });
|
||||
|
||||
setEditingState({ type: "feature", id: itemId });
|
||||
setSheet("edit-feature");
|
||||
};
|
||||
@@ -71,7 +77,8 @@ export const AddFeatureRow = ({ disabled }: AddFeatureRowProps) => {
|
||||
disabled={disabled}
|
||||
aria-label="Add new feature"
|
||||
>
|
||||
<PlusIcon size={16} weight="regular" />
|
||||
<PlusIcon className="size-3" weight="bold" />
|
||||
Add feature
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-0" align="start">
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { Card, CardContent } from "@/components/v2/cards/Card";
|
||||
|
||||
import { useFeatureNavigation } from "./hooks/useFeatureNavigation";
|
||||
import { PlanCardHeader } from "./PlanCardHeader";
|
||||
import { PlanFeatureList } from "./PlanFeatureList";
|
||||
|
||||
export default function PlanCard() {
|
||||
// Initialize feature navigation (registers hotkeys internally)
|
||||
useFeatureNavigation();
|
||||
|
||||
useHotkeys("ctrl+s", () => {
|
||||
console.log("Save");
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="min-w-sm w-[70%] max-w-xl mx-4 bg-card">
|
||||
<PlanCardHeader />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ProductItem } from "@autumn/shared";
|
||||
import { isPriceItem } from "@/utils/product/getItemType";
|
||||
import { type ProductItem, productV2ToFeatureItems } from "@autumn/shared";
|
||||
import { useProductContext } from "@/views/products/product/ProductContext";
|
||||
import { AddFeatureRow } from "./AddFeatureRow";
|
||||
import { PlanFeatureRow } from "./PlanFeatureRow";
|
||||
@@ -8,6 +7,8 @@ export const PlanFeatureList = () => {
|
||||
const { product, setProduct, setSheet, editingState, setEditingState } =
|
||||
useProductContext();
|
||||
|
||||
const filteredItems = productV2ToFeatureItems({ items: product?.items });
|
||||
|
||||
const handleFeatureClick = (item: ProductItem) => {
|
||||
console.log("Feature clicked:", item);
|
||||
};
|
||||
@@ -45,10 +46,6 @@ export const PlanFeatureList = () => {
|
||||
setSheet("edit-feature");
|
||||
};
|
||||
|
||||
// Filter out standalone price items - only show features and priced features
|
||||
const filteredItems =
|
||||
product?.items?.filter((item: ProductItem) => !isPriceItem(item)) || [];
|
||||
|
||||
if (filteredItems.length === 0) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { type ProductItem, productV2ToFeatureItems } from "@autumn/shared";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { getItemId } from "@/utils/product/productItemUtils";
|
||||
import { useProductContext } from "@/views/products/product/ProductContext";
|
||||
|
||||
export const useFeatureNavigation = () => {
|
||||
const { product, editingState, setEditingState, setSheet } =
|
||||
useProductContext();
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
|
||||
// Get filtered items (non-price items)
|
||||
const filteredItems = productV2ToFeatureItems({ items: product?.items });
|
||||
// const filteredItems = useMemo(() => {
|
||||
// return productV2ToFeatureItems({ items: product?.items });
|
||||
// }, [product?.items]);
|
||||
|
||||
// Update selected index when editing state changes or items change
|
||||
useEffect(() => {
|
||||
if (editingState.id && filteredItems.length > 0) {
|
||||
const currentIndex = filteredItems.findIndex(
|
||||
(item: ProductItem, index: number) => {
|
||||
const itemId = getItemId({ item, itemIndex: index });
|
||||
return itemId === editingState.id;
|
||||
},
|
||||
);
|
||||
if (currentIndex !== -1) {
|
||||
setSelectedIndex(currentIndex);
|
||||
}
|
||||
} else if (filteredItems.length === 0) {
|
||||
// Reset selection when no items
|
||||
setSelectedIndex(0);
|
||||
}
|
||||
}, [editingState.id, filteredItems]);
|
||||
|
||||
// Handle when selected index becomes out of bounds (e.g., item deleted)
|
||||
useEffect(() => {
|
||||
if (selectedIndex >= filteredItems.length && filteredItems.length > 0) {
|
||||
setSelectedIndex(filteredItems.length - 1);
|
||||
}
|
||||
}, [selectedIndex, filteredItems.length]);
|
||||
|
||||
// Navigation functions
|
||||
const navigateToIndex = useCallback(
|
||||
(index: number) => {
|
||||
if (filteredItems.length === 0) return;
|
||||
|
||||
const clampedIndex = Math.max(
|
||||
0,
|
||||
Math.min(index, filteredItems.length - 1),
|
||||
);
|
||||
setSelectedIndex(clampedIndex);
|
||||
|
||||
const item = filteredItems[clampedIndex];
|
||||
if (item) {
|
||||
const itemId = getItemId({ item, itemIndex: clampedIndex });
|
||||
setEditingState({ type: "feature", id: itemId });
|
||||
setSheet("edit-feature");
|
||||
}
|
||||
},
|
||||
[filteredItems, setEditingState, setSheet],
|
||||
);
|
||||
|
||||
const navigateUp = useCallback(() => {
|
||||
navigateToIndex(selectedIndex - 1);
|
||||
}, [selectedIndex, navigateToIndex]);
|
||||
|
||||
const navigateDown = useCallback(() => {
|
||||
navigateToIndex(selectedIndex + 1);
|
||||
}, [selectedIndex, navigateToIndex]);
|
||||
|
||||
const selectFirst = useCallback(() => {
|
||||
navigateToIndex(0);
|
||||
}, [navigateToIndex]);
|
||||
|
||||
const selectLast = useCallback(() => {
|
||||
navigateToIndex(filteredItems.length - 1);
|
||||
}, [filteredItems.length, navigateToIndex]);
|
||||
|
||||
const editPlan = useCallback(() => {
|
||||
if (!product) return;
|
||||
setEditingState({ type: "plan", id: product.id });
|
||||
setSheet("edit-plan");
|
||||
}, [product, setEditingState, setSheet]);
|
||||
|
||||
// Register hotkeys
|
||||
useHotkeys("up", navigateUp, {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length > 0,
|
||||
});
|
||||
|
||||
useHotkeys("down", navigateDown, {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length > 0,
|
||||
});
|
||||
|
||||
useHotkeys("home", selectFirst, {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length > 0,
|
||||
});
|
||||
|
||||
useHotkeys("end", selectLast, {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length > 0,
|
||||
});
|
||||
|
||||
// 0 key to edit plan
|
||||
useHotkeys("0", editPlan, {
|
||||
preventDefault: true,
|
||||
enabled: !!product,
|
||||
});
|
||||
|
||||
// Number key navigation (1-9) - using individual hooks to avoid loop issues
|
||||
useHotkeys("1", () => navigateToIndex(0), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 1,
|
||||
});
|
||||
useHotkeys("2", () => navigateToIndex(1), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 2,
|
||||
});
|
||||
useHotkeys("3", () => navigateToIndex(2), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 3,
|
||||
});
|
||||
useHotkeys("4", () => navigateToIndex(3), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 4,
|
||||
});
|
||||
useHotkeys("5", () => navigateToIndex(4), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 5,
|
||||
});
|
||||
useHotkeys("6", () => navigateToIndex(5), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 6,
|
||||
});
|
||||
useHotkeys("7", () => navigateToIndex(6), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 7,
|
||||
});
|
||||
useHotkeys("8", () => navigateToIndex(7), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 8,
|
||||
});
|
||||
useHotkeys("9", () => navigateToIndex(8), {
|
||||
preventDefault: true,
|
||||
enabled: filteredItems.length >= 9,
|
||||
});
|
||||
|
||||
return {
|
||||
selectedIndex,
|
||||
filteredItems,
|
||||
navigateToIndex,
|
||||
navigateUp,
|
||||
navigateDown,
|
||||
selectFirst,
|
||||
selectLast,
|
||||
editPlan,
|
||||
hasItems: filteredItems.length > 0,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user