@dust-tt/sparkle 0.2.448 → 0.2.449-rc-samuel
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/esm/components/AppCommandPalette.d.ts +11 -0
- package/dist/esm/components/AppCommandPalette.d.ts.map +1 -0
- package/dist/esm/components/AppCommandPalette.js +44 -0
- package/dist/esm/components/AppCommandPalette.js.map +1 -0
- package/dist/esm/components/CommandPalette.d.ts +3 -0
- package/dist/esm/components/CommandPalette.d.ts.map +1 -0
- package/dist/esm/components/CommandPalette.js +61 -0
- package/dist/esm/components/CommandPalette.js.map +1 -0
- package/dist/esm/components/Input.d.ts +1 -0
- package/dist/esm/components/Input.d.ts.map +1 -1
- package/dist/esm/components/Input.js +2 -2
- package/dist/esm/components/Input.js.map +1 -1
- package/dist/esm/components/Notification.d.ts +2 -2
- package/dist/esm/components/Notification.d.ts.map +1 -1
- package/dist/esm/components/Notification.js +25 -3
- package/dist/esm/components/Notification.js.map +1 -1
- package/dist/esm/components/Resizable.d.ts +1 -1
- package/dist/esm/components/SamsCommandPalette.d.ts +81 -0
- package/dist/esm/components/SamsCommandPalette.d.ts.map +1 -0
- package/dist/esm/components/SamsCommandPalette.js +56 -0
- package/dist/esm/components/SamsCommandPalette.js.map +1 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/components/markdown/List.d.ts.map +1 -1
- package/dist/esm/components/markdown/List.js +3 -7
- package/dist/esm/components/markdown/List.js.map +1 -1
- package/dist/esm/contexts/CommandPaletteContext.d.ts +28 -0
- package/dist/esm/contexts/CommandPaletteContext.d.ts.map +1 -0
- package/dist/esm/contexts/CommandPaletteContext.js +83 -0
- package/dist/esm/contexts/CommandPaletteContext.js.map +1 -0
- package/dist/esm/hooks/useCommandPalette.d.ts +27 -0
- package/dist/esm/hooks/useCommandPalette.d.ts.map +1 -0
- package/dist/esm/hooks/useCommandPalette.js +62 -0
- package/dist/esm/hooks/useCommandPalette.js.map +1 -0
- package/dist/esm/stories/Notification.stories.js +8 -1
- package/dist/esm/stories/Notification.stories.js.map +1 -1
- package/dist/esm/stories/SamsCommandPalette.stories.d.ts +25 -0
- package/dist/esm/stories/SamsCommandPalette.stories.d.ts.map +1 -0
- package/dist/esm/stories/SamsCommandPalette.stories.js +100 -0
- package/dist/esm/stories/SamsCommandPalette.stories.js.map +1 -0
- package/dist/sparkle.css +136 -0
- package/package.json +2 -1
- package/src/components/AppCommandPalette.tsx +58 -0
- package/src/components/CommandPalette.tsx +112 -0
- package/src/components/Input.tsx +8 -1
- package/src/components/Notification.tsx +38 -19
- package/src/components/SamsCommandPalette.tsx +166 -0
- package/src/components/index.ts +1 -0
- package/src/components/markdown/List.tsx +3 -7
- package/src/contexts/CommandPaletteContext.tsx +135 -0
- package/src/hooks/useCommandPalette.tsx +87 -0
- package/src/stories/Notification.stories.tsx +10 -0
- package/src/stories/SamsCommandPalette.stories.tsx +133 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@sparkle/lib/utils";
|
|
6
|
+
|
|
7
|
+
import { useCommandPalette } from "../hooks/useCommandPalette";
|
|
8
|
+
import {
|
|
9
|
+
SamsCommand,
|
|
10
|
+
SamsCommandDialog,
|
|
11
|
+
SamsCommandEmpty,
|
|
12
|
+
SamsCommandGroup,
|
|
13
|
+
SamsCommandInput,
|
|
14
|
+
SamsCommandItem,
|
|
15
|
+
SamsCommandList,
|
|
16
|
+
} from "./SamsCommandPalette";
|
|
17
|
+
|
|
18
|
+
export const CommandPalette: React.FC = () => {
|
|
19
|
+
const { commands, isOpen, setOpen, closeCommandPalette } =
|
|
20
|
+
useCommandPalette();
|
|
21
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
22
|
+
|
|
23
|
+
// Focus the input when the palette opens
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (isOpen) {
|
|
26
|
+
// Use a small timeout to ensure the dialog is fully rendered before focusing
|
|
27
|
+
const focusTimeout = setTimeout(() => {
|
|
28
|
+
inputRef.current?.focus();
|
|
29
|
+
}, 50);
|
|
30
|
+
|
|
31
|
+
return () => clearTimeout(focusTimeout);
|
|
32
|
+
}
|
|
33
|
+
}, [isOpen]);
|
|
34
|
+
|
|
35
|
+
// Group commands by category
|
|
36
|
+
const groupedCommands = commands.reduce<Record<string, typeof commands>>(
|
|
37
|
+
(acc, command) => {
|
|
38
|
+
const category = command.category || "General";
|
|
39
|
+
if (!acc[category]) {
|
|
40
|
+
acc[category] = [];
|
|
41
|
+
}
|
|
42
|
+
acc[category].push(command);
|
|
43
|
+
return acc;
|
|
44
|
+
},
|
|
45
|
+
{}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Sort categories and commands
|
|
49
|
+
const sortedCategories = Object.keys(groupedCommands).sort();
|
|
50
|
+
|
|
51
|
+
// Sort commands by priority (if specified)
|
|
52
|
+
Object.keys(groupedCommands).forEach((category) => {
|
|
53
|
+
groupedCommands[category].sort((a, b) => {
|
|
54
|
+
const priorityA = a.priority ?? 0;
|
|
55
|
+
const priorityB = b.priority ?? 0;
|
|
56
|
+
return priorityA - priorityB; // Lower priority first
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<SamsCommandDialog open={isOpen} onOpenChange={setOpen}>
|
|
62
|
+
<SamsCommand>
|
|
63
|
+
<SamsCommandInput
|
|
64
|
+
ref={inputRef}
|
|
65
|
+
placeholder="Type a command or search..."
|
|
66
|
+
autoFocus
|
|
67
|
+
/>
|
|
68
|
+
<SamsCommandList>
|
|
69
|
+
<SamsCommandEmpty>No results found.</SamsCommandEmpty>
|
|
70
|
+
{sortedCategories.map((category, categoryIndex) => (
|
|
71
|
+
<SamsCommandGroup key={category} heading={category}>
|
|
72
|
+
{groupedCommands[category].map((command, commandIndex) => (
|
|
73
|
+
<SamsCommandItem
|
|
74
|
+
key={command.id}
|
|
75
|
+
onSelect={() => {
|
|
76
|
+
command.action();
|
|
77
|
+
// Optionally close the palette after executing a command
|
|
78
|
+
closeCommandPalette();
|
|
79
|
+
}}
|
|
80
|
+
className={cn(
|
|
81
|
+
"s-relative s-flex s-items-center s-gap-2 s-rounded-md s-px-2 s-py-1.5 s-text-sm s-font-medium",
|
|
82
|
+
command.id.includes("warning")
|
|
83
|
+
? "s-text-warning-500 data-[selected=true]:s-bg-warning-50 data-[selected=true]:s-text-warning-500"
|
|
84
|
+
: "s-text-foreground data-[selected=true]:s-bg-muted-background data-[selected=true]:s-text-foreground",
|
|
85
|
+
|
|
86
|
+
// if it's the last one, rounded-b-md"
|
|
87
|
+
categoryIndex === sortedCategories.length - 1 &&
|
|
88
|
+
commandIndex === groupedCommands[category].length - 1
|
|
89
|
+
? "s-rounded-b-xl"
|
|
90
|
+
: ""
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{command.icon && (
|
|
94
|
+
<span className="s-mr-2 s-flex s-h-5 s-w-5 s-items-center s-justify-center">
|
|
95
|
+
{command.icon}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
<span>{command.label}</span>
|
|
99
|
+
{command.shortcut && (
|
|
100
|
+
<span className="s-ml-auto s-text-xs s-tracking-widest s-text-muted-foreground">
|
|
101
|
+
{command.shortcut}
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
</SamsCommandItem>
|
|
105
|
+
))}
|
|
106
|
+
</SamsCommandGroup>
|
|
107
|
+
))}
|
|
108
|
+
</SamsCommandList>
|
|
109
|
+
</SamsCommand>
|
|
110
|
+
</SamsCommandDialog>
|
|
111
|
+
);
|
|
112
|
+
};
|
package/src/components/Input.tsx
CHANGED
|
@@ -19,6 +19,7 @@ export interface InputProps
|
|
|
19
19
|
isError?: boolean;
|
|
20
20
|
className?: string;
|
|
21
21
|
label?: string;
|
|
22
|
+
isInCommandPalette?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const INPUT_STATES = ["error", "disabled", "default"];
|
|
@@ -91,6 +92,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
91
92
|
label,
|
|
92
93
|
isError,
|
|
93
94
|
disabled,
|
|
95
|
+
isInCommandPalette,
|
|
94
96
|
...props
|
|
95
97
|
},
|
|
96
98
|
ref
|
|
@@ -102,7 +104,12 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
102
104
|
? "disabled"
|
|
103
105
|
: "default";
|
|
104
106
|
return (
|
|
105
|
-
<div
|
|
107
|
+
<div
|
|
108
|
+
className={cn(
|
|
109
|
+
"s-flex s-flex-col s-gap-1",
|
|
110
|
+
isInCommandPalette && "s-w-full"
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
106
113
|
{label && (
|
|
107
114
|
<Label htmlFor={props.name} className="s-mb-1">
|
|
108
115
|
{label}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { toast, Toaster } from "sonner";
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import {
|
|
6
|
+
CheckCircleIcon,
|
|
7
|
+
InformationCircleIcon,
|
|
8
|
+
XCircleIcon,
|
|
9
|
+
} from "@sparkle/icons";
|
|
10
|
+
import { assertNever, cn } from "@sparkle/lib/utils";
|
|
6
11
|
|
|
7
12
|
import { Icon } from "./Icon";
|
|
8
13
|
|
|
@@ -11,7 +16,7 @@ const NOTIFICATION_DELAY = 5000;
|
|
|
11
16
|
export type NotificationType = {
|
|
12
17
|
title?: string;
|
|
13
18
|
description?: string;
|
|
14
|
-
type: "success" | "error";
|
|
19
|
+
type: "success" | "error" | "info";
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
const NotificationsContext = React.createContext<(n: NotificationType) => void>(
|
|
@@ -21,11 +26,34 @@ export interface NotificationProps {
|
|
|
21
26
|
className?: string;
|
|
22
27
|
description?: string;
|
|
23
28
|
title?: string;
|
|
24
|
-
variant: "success" | "error";
|
|
29
|
+
variant: "success" | "error" | "info";
|
|
25
30
|
onClick?: () => void;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
function NotificationContent({ type, title, description }: NotificationType) {
|
|
34
|
+
const icon = (() => {
|
|
35
|
+
switch (type) {
|
|
36
|
+
case "success":
|
|
37
|
+
return CheckCircleIcon;
|
|
38
|
+
case "error":
|
|
39
|
+
return XCircleIcon;
|
|
40
|
+
case "info":
|
|
41
|
+
return InformationCircleIcon;
|
|
42
|
+
default:
|
|
43
|
+
assertNever(type);
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
|
|
47
|
+
const variantClassName = cva("s-pt-0.5", {
|
|
48
|
+
variants: {
|
|
49
|
+
type: {
|
|
50
|
+
success: "s-text-success-600 dark:s-text-success-400-night",
|
|
51
|
+
error: "s-text-warning-500 dark:s-text-warning-500-night",
|
|
52
|
+
info: "s-text-info-600 dark:s-text-info-400-night",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
29
57
|
return (
|
|
30
58
|
<div
|
|
31
59
|
className={cn(
|
|
@@ -35,21 +63,12 @@ function NotificationContent({ type, title, description }: NotificationType) {
|
|
|
35
63
|
"s-p-4 s-shadow-xl"
|
|
36
64
|
)}
|
|
37
65
|
>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/>
|
|
45
|
-
) : (
|
|
46
|
-
<Icon
|
|
47
|
-
size="lg"
|
|
48
|
-
visual={XCircleIcon}
|
|
49
|
-
className="s-pt-0.5 s-text-warning-500 dark:s-text-warning-500-night"
|
|
50
|
-
aria-hidden="true"
|
|
51
|
-
/>
|
|
52
|
-
)}
|
|
66
|
+
<Icon
|
|
67
|
+
size="lg"
|
|
68
|
+
visual={icon}
|
|
69
|
+
className={variantClassName({ type })}
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
/>
|
|
53
72
|
<div className="s-flex s-flex-col">
|
|
54
73
|
<div
|
|
55
74
|
className={cn(
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type DialogProps } from "@radix-ui/react-dialog";
|
|
4
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { Dialog, DialogContent, Input } from "@sparkle/components";
|
|
8
|
+
import { menuStyleClasses } from "@sparkle/components/Dropdown";
|
|
9
|
+
import { cn } from "@sparkle/lib";
|
|
10
|
+
|
|
11
|
+
const Command = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof CommandPrimitive>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<CommandPrimitive
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
));
|
|
24
|
+
Command.displayName = CommandPrimitive.displayName;
|
|
25
|
+
|
|
26
|
+
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
|
27
|
+
return (
|
|
28
|
+
<Dialog {...props}>
|
|
29
|
+
<DialogContent className="s-overflow-hidden s-p-0">
|
|
30
|
+
<Command className="[&_[cmdk-group-heading]]:s-p-2 [&_[cmdk-group-heading]]:s-font-medium [&_[cmdk-group-heading]]:s-text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:s-pt-0 [&_[cmdk-group]]:s-px-2 [&_[cmdk-input-wrapper]_svg]:s-h-5 [&_[cmdk-input-wrapper]_svg]:s-w-5 [&_[cmdk-input]]:s-h-12 [&_[cmdk-item]]:s-px-2 [&_[cmdk-item]]:s-py-3 [&_[cmdk-item]_svg]:s-h-5 [&_[cmdk-item]_svg]:s-w-5">
|
|
31
|
+
{children}
|
|
32
|
+
</Command>
|
|
33
|
+
</DialogContent>
|
|
34
|
+
</Dialog>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const CommandInput = React.forwardRef<
|
|
39
|
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
40
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
41
|
+
>(({ className, ...props }, ref) => (
|
|
42
|
+
<div
|
|
43
|
+
className="s-flex s-w-full s-items-center s-border-b s-p-3"
|
|
44
|
+
cmdk-input-wrapper=""
|
|
45
|
+
>
|
|
46
|
+
<CommandPrimitive.Input
|
|
47
|
+
asChild
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(className, "s-min-w-full")}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<Input
|
|
53
|
+
isInCommandPalette
|
|
54
|
+
className="s-h-10 s-w-full s-min-w-full s-rounded-md s-bg-transparent s-py-3 s-text-sm s-outline-none placeholder:s-text-muted-foreground disabled:s-cursor-not-allowed disabled:s-opacity-50"
|
|
55
|
+
/>
|
|
56
|
+
</CommandPrimitive.Input>
|
|
57
|
+
</div>
|
|
58
|
+
));
|
|
59
|
+
|
|
60
|
+
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
|
61
|
+
|
|
62
|
+
const CommandList = React.forwardRef<
|
|
63
|
+
React.ElementRef<typeof CommandPrimitive.List>,
|
|
64
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
65
|
+
>(({ className, ...props }, ref) => (
|
|
66
|
+
<CommandPrimitive.List
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
"s-max-h-[300px] s-overflow-y-auto s-overflow-x-hidden",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
));
|
|
75
|
+
|
|
76
|
+
CommandList.displayName = CommandPrimitive.List.displayName;
|
|
77
|
+
|
|
78
|
+
const CommandEmpty = React.forwardRef<
|
|
79
|
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
80
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
81
|
+
>((props, ref) => (
|
|
82
|
+
<CommandPrimitive.Empty
|
|
83
|
+
ref={ref}
|
|
84
|
+
className="s-py-6 s-text-center s-text-sm"
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
));
|
|
88
|
+
|
|
89
|
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
|
90
|
+
|
|
91
|
+
const CommandGroup = React.forwardRef<
|
|
92
|
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
94
|
+
>(({ className, ...props }, ref) => (
|
|
95
|
+
<CommandPrimitive.Group
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(
|
|
98
|
+
"s-overflow-hidden s-p-1 s-text-foreground [&_[cmdk-group-heading]]:s-px-2 [&_[cmdk-group-heading]]:s-py-1.5 [&_[cmdk-group-heading]]:s-text-xs [&_[cmdk-group-heading]]:s-font-medium [&_[cmdk-group-heading]]:s-text-muted-foreground [&_[cmdk-group-items]]:s-pb-1",
|
|
99
|
+
menuStyleClasses,
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
));
|
|
105
|
+
|
|
106
|
+
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
|
107
|
+
|
|
108
|
+
const CommandSeparator = React.forwardRef<
|
|
109
|
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
110
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
111
|
+
>(({ className, ...props }, ref) => (
|
|
112
|
+
<CommandPrimitive.Separator
|
|
113
|
+
ref={ref}
|
|
114
|
+
className={cn(
|
|
115
|
+
"-s-mx-1 s-h-px s-bg-border dark:s-bg-border-night",
|
|
116
|
+
className
|
|
117
|
+
)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
));
|
|
121
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
|
122
|
+
|
|
123
|
+
const CommandItem = React.forwardRef<
|
|
124
|
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
125
|
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
126
|
+
>(({ className, ...props }, ref) => (
|
|
127
|
+
<CommandPrimitive.Item
|
|
128
|
+
ref={ref}
|
|
129
|
+
className={cn(
|
|
130
|
+
"data-[selected=true]:s-bg-accent data-[selected=true]:s-text-accent-foreground [&_svg]:s-size-4 s-relative s-flex s-cursor-default s-select-none s-items-center s-gap-2 s-rounded-sm s-px-2 s-py-1.5 s-text-sm s-outline-none data-[disabled=true]:s-pointer-events-none data-[disabled=true]:s-opacity-50 [&_svg]:s-pointer-events-none [&_svg]:s-shrink-0",
|
|
131
|
+
// menuStyleClasses.item({ variant: "default" }),
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
));
|
|
137
|
+
|
|
138
|
+
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
|
139
|
+
|
|
140
|
+
const CommandShortcut = ({
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
144
|
+
return (
|
|
145
|
+
<span
|
|
146
|
+
className={cn(
|
|
147
|
+
"s-ml-auto s-text-xs s-tracking-widest s-text-muted-foreground",
|
|
148
|
+
className
|
|
149
|
+
)}
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
CommandShortcut.displayName = "CommandShortcut";
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
Command as SamsCommand,
|
|
158
|
+
CommandDialog as SamsCommandDialog,
|
|
159
|
+
CommandEmpty as SamsCommandEmpty,
|
|
160
|
+
CommandGroup as SamsCommandGroup,
|
|
161
|
+
CommandInput as SamsCommandInput,
|
|
162
|
+
CommandItem as SamsCommandItem,
|
|
163
|
+
CommandList as SamsCommandList,
|
|
164
|
+
CommandSeparator as SamsCommandSeparator,
|
|
165
|
+
CommandShortcut as SamsCommandShortcut,
|
|
166
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -3,9 +3,7 @@ import React from "react";
|
|
|
3
3
|
|
|
4
4
|
import { cn } from "@sparkle/lib";
|
|
5
5
|
|
|
6
|
-
export const ulBlockVariants = cva([
|
|
7
|
-
"s-list-disc s-py-2 s-pl-8 first:s-pt-0 last:s-pb-0",
|
|
8
|
-
]);
|
|
6
|
+
export const ulBlockVariants = cva(["s-list-disc s-pb-2 s-pl-6"]);
|
|
9
7
|
|
|
10
8
|
interface UlBlockProps {
|
|
11
9
|
children: React.ReactNode;
|
|
@@ -19,9 +17,7 @@ export function UlBlock({ children, textColor, textSize }: UlBlockProps) {
|
|
|
19
17
|
);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
export const olBlockVariants = cva([
|
|
23
|
-
"s-list-decimal s-py-3 s-pl-8 first:s-pt-0 last:s-pb-0",
|
|
24
|
-
]);
|
|
20
|
+
export const olBlockVariants = cva(["s-list-decimal s-pb-2 s-pl-6"]);
|
|
25
21
|
|
|
26
22
|
interface OlBlockProps {
|
|
27
23
|
children: React.ReactNode;
|
|
@@ -43,7 +39,7 @@ export function OlBlock({
|
|
|
43
39
|
);
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
export const liBlockVariants = cva(["s-break-words
|
|
42
|
+
export const liBlockVariants = cva(["s-break-words"]);
|
|
47
43
|
|
|
48
44
|
interface LiBlockProps {
|
|
49
45
|
children: React.ReactNode;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
// Command type definition
|
|
12
|
+
export interface Command {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
shortcut?: string;
|
|
16
|
+
icon?: React.ReactNode;
|
|
17
|
+
action: () => void;
|
|
18
|
+
category?: string;
|
|
19
|
+
priority?: number;
|
|
20
|
+
entityType?: string;
|
|
21
|
+
entityId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CommandPaletteContextType {
|
|
25
|
+
commands: Command[];
|
|
26
|
+
isOpen: boolean;
|
|
27
|
+
registerCommand: (command: Command) => void;
|
|
28
|
+
registerCommands: (commands: Command[]) => void;
|
|
29
|
+
unregisterCommand: (commandId: string) => void;
|
|
30
|
+
unregisterCommandsByEntityType: (entityType: string) => void;
|
|
31
|
+
unregisterCommandsByEntityId: (entityId: string) => void;
|
|
32
|
+
setOpen: (isOpen: boolean) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const CommandPaletteContext = createContext<
|
|
36
|
+
CommandPaletteContextType | undefined
|
|
37
|
+
>(undefined);
|
|
38
|
+
|
|
39
|
+
export const CommandPaletteProvider: React.FC<{
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}> = ({ children }) => {
|
|
42
|
+
const [commands, setCommands] = useState<Command[]>([]);
|
|
43
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
44
|
+
|
|
45
|
+
// Register a single command
|
|
46
|
+
const registerCommand = useCallback((command: Command) => {
|
|
47
|
+
setCommands((prevCommands) => {
|
|
48
|
+
// Check if command with same ID already exists
|
|
49
|
+
const index = prevCommands.findIndex((c) => c.id === command.id);
|
|
50
|
+
if (index >= 0) {
|
|
51
|
+
// Replace existing command
|
|
52
|
+
const newCommands = [...prevCommands];
|
|
53
|
+
newCommands[index] = command;
|
|
54
|
+
return newCommands;
|
|
55
|
+
}
|
|
56
|
+
// Add new command
|
|
57
|
+
return [...prevCommands, command];
|
|
58
|
+
});
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
// Register multiple commands at once
|
|
62
|
+
const registerCommands = useCallback((newCommands: Command[]) => {
|
|
63
|
+
setCommands((prevCommands) => {
|
|
64
|
+
const commandMap = new Map(prevCommands.map((c) => [c.id, c]));
|
|
65
|
+
|
|
66
|
+
// Update or add new commands
|
|
67
|
+
newCommands.forEach((command) => {
|
|
68
|
+
commandMap.set(command.id, command);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return Array.from(commandMap.values());
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
// Unregister a command by ID
|
|
76
|
+
const unregisterCommand = useCallback((commandId: string) => {
|
|
77
|
+
setCommands((prevCommands) =>
|
|
78
|
+
prevCommands.filter((c) => c.id !== commandId)
|
|
79
|
+
);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
// Unregister commands by entity type
|
|
83
|
+
const unregisterCommandsByEntityType = useCallback((entityType: string) => {
|
|
84
|
+
setCommands((prevCommands) =>
|
|
85
|
+
prevCommands.filter((c) => c.entityType !== entityType)
|
|
86
|
+
);
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
// Unregister commands by entity ID
|
|
90
|
+
const unregisterCommandsByEntityId = useCallback((entityId: string) => {
|
|
91
|
+
setCommands((prevCommands) =>
|
|
92
|
+
prevCommands.filter((c) => c.entityId !== entityId)
|
|
93
|
+
);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
// Handle keyboard shortcut
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
99
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "k") {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
setIsOpen((prev) => !prev);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
106
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
const contextValue: CommandPaletteContextType = {
|
|
110
|
+
commands,
|
|
111
|
+
isOpen,
|
|
112
|
+
registerCommand,
|
|
113
|
+
registerCommands,
|
|
114
|
+
unregisterCommand,
|
|
115
|
+
unregisterCommandsByEntityType,
|
|
116
|
+
unregisterCommandsByEntityId,
|
|
117
|
+
setOpen: setIsOpen,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<CommandPaletteContext.Provider value={contextValue}>
|
|
122
|
+
{children}
|
|
123
|
+
</CommandPaletteContext.Provider>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const useCommandPaletteContext = (): CommandPaletteContextType => {
|
|
128
|
+
const context = useContext(CommandPaletteContext);
|
|
129
|
+
if (context === undefined) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"useCommandPaletteContext must be used within a CommandPaletteProvider"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return context;
|
|
135
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Command,
|
|
7
|
+
useCommandPaletteContext,
|
|
8
|
+
} from "../contexts/CommandPaletteContext";
|
|
9
|
+
|
|
10
|
+
interface UseCommandPaletteProps {
|
|
11
|
+
commands?: Command[];
|
|
12
|
+
entityType?: string;
|
|
13
|
+
entityId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook for interacting with the command palette
|
|
18
|
+
*
|
|
19
|
+
* Optionally register commands when the component mounts and
|
|
20
|
+
* automatically unregister them when the component unmounts.
|
|
21
|
+
*/
|
|
22
|
+
export const useCommandPalette = (props?: UseCommandPaletteProps) => {
|
|
23
|
+
const {
|
|
24
|
+
commands: allCommands,
|
|
25
|
+
isOpen,
|
|
26
|
+
registerCommand,
|
|
27
|
+
registerCommands,
|
|
28
|
+
unregisterCommand,
|
|
29
|
+
unregisterCommandsByEntityType,
|
|
30
|
+
unregisterCommandsByEntityId,
|
|
31
|
+
setOpen,
|
|
32
|
+
} = useCommandPaletteContext();
|
|
33
|
+
|
|
34
|
+
// Register commands on mount, unregister on unmount
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (props?.commands?.length) {
|
|
37
|
+
registerCommands(props.commands);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
// Clean up commands when component unmounts
|
|
42
|
+
if (props?.entityType) {
|
|
43
|
+
unregisterCommandsByEntityType(props.entityType);
|
|
44
|
+
}
|
|
45
|
+
if (props?.entityId) {
|
|
46
|
+
unregisterCommandsByEntityId(props.entityId);
|
|
47
|
+
}
|
|
48
|
+
if (props?.commands) {
|
|
49
|
+
props.commands.forEach((command) => {
|
|
50
|
+
unregisterCommand(command.id);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}, [
|
|
55
|
+
props?.commands,
|
|
56
|
+
props?.entityType,
|
|
57
|
+
props?.entityId,
|
|
58
|
+
registerCommands,
|
|
59
|
+
unregisterCommand,
|
|
60
|
+
unregisterCommandsByEntityType,
|
|
61
|
+
unregisterCommandsByEntityId,
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Utility functions
|
|
65
|
+
const openCommandPalette = () => setOpen(true);
|
|
66
|
+
const closeCommandPalette = () => setOpen(false);
|
|
67
|
+
const toggleCommandPalette = () => setOpen(!isOpen);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
// State
|
|
71
|
+
commands: allCommands,
|
|
72
|
+
isOpen,
|
|
73
|
+
|
|
74
|
+
// Register/unregister methods
|
|
75
|
+
registerCommand,
|
|
76
|
+
registerCommands,
|
|
77
|
+
unregisterCommand,
|
|
78
|
+
unregisterCommandsByEntityType,
|
|
79
|
+
unregisterCommandsByEntityId,
|
|
80
|
+
|
|
81
|
+
// Open/close controls
|
|
82
|
+
openCommandPalette,
|
|
83
|
+
closeCommandPalette,
|
|
84
|
+
toggleCommandPalette,
|
|
85
|
+
setOpen,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -44,6 +44,16 @@ const NotificationExample = () => {
|
|
|
44
44
|
}
|
|
45
45
|
label="Show Error"
|
|
46
46
|
/>
|
|
47
|
+
<Button
|
|
48
|
+
onClick={() =>
|
|
49
|
+
sendNotification({
|
|
50
|
+
title: "Info",
|
|
51
|
+
description: "Some information",
|
|
52
|
+
type: "info",
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
label="Show Info"
|
|
56
|
+
/>
|
|
47
57
|
</div>
|
|
48
58
|
);
|
|
49
59
|
};
|