@nikala-ui/core 0.11.0 → 0.12.0
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/package.json +43 -2
- package/registry/api-table.json +21 -0
- package/registry/banner.json +1 -1
- package/registry/button.json +1 -1
- package/registry/callout.json +19 -0
- package/registry/checkbox.json +4 -1
- package/registry/code-block.json +26 -0
- package/registry/code-group.json +20 -0
- package/registry/combobox.json +1 -1
- package/registry/command.json +3 -2
- package/registry/component-viewer.json +25 -0
- package/registry/container.json +18 -0
- package/registry/context-menu.json +1 -1
- package/registry/create-app-updater.json +13 -0
- package/registry/create-document-tabs.json +13 -0
- package/registry/create-global-shortcut.json +13 -0
- package/registry/create-tauri-window.json +13 -0
- package/registry/create-tiptap-editor.json +34 -0
- package/registry/dialog.json +1 -1
- package/registry/dropdown-menu.json +3 -2
- package/registry/field.json +1 -1
- package/registry/file-tree.json +21 -0
- package/registry/footer.json +1 -1
- package/registry/form-message.json +3 -2
- package/registry/icon-button.json +1 -1
- package/registry/index.json +336 -5
- package/registry/navbar.json +1 -1
- package/registry/navigation-menu.json +1 -1
- package/registry/number-input.json +1 -1
- package/registry/package-manager-tabs.json +24 -0
- package/registry/pager.json +21 -0
- package/registry/popover.json +4 -1
- package/registry/resizable.json +1 -1
- package/registry/rich-text-editor.json +95 -0
- package/registry/scroll-area.json +1 -1
- package/registry/section-heading.json +21 -0
- package/registry/select.json +3 -2
- package/registry/sheet.json +1 -1
- package/registry/sidebar.json +1 -1
- package/registry/status.json +1 -1
- package/registry/steps.json +20 -0
- package/registry/switch.json +4 -1
- package/registry/table-of-contents.json +23 -0
- package/registry/tabs.json +4 -1
- package/registry/theme-manager.json +6 -5
- package/registry/titlebar-tabs.json +24 -0
- package/registry/titlebar.json +26 -0
- package/registry/toggle-group.json +1 -1
- package/registry/updater-modal.json +26 -0
- package/src/index.ts +48 -0
- package/src/registry/components/ui/api-table.tsx +117 -0
- package/src/registry/components/ui/banner.tsx +0 -2
- package/src/registry/components/ui/button.tsx +1 -1
- package/src/registry/components/ui/callout.tsx +137 -0
- package/src/registry/components/ui/checkbox.tsx +1 -1
- package/src/registry/components/ui/code-block.tsx +326 -0
- package/src/registry/components/ui/code-group.tsx +105 -0
- package/src/registry/components/ui/combobox.tsx +83 -18
- package/src/registry/components/ui/command.tsx +110 -106
- package/src/registry/components/ui/component-viewer.tsx +363 -0
- package/src/registry/components/ui/container.tsx +45 -0
- package/src/registry/components/ui/context-menu.tsx +67 -22
- package/src/registry/components/ui/dialog.tsx +42 -32
- package/src/registry/components/ui/dropdown-menu.tsx +69 -31
- package/src/registry/components/ui/field.tsx +7 -3
- package/src/registry/components/ui/file-tree.tsx +181 -0
- package/src/registry/components/ui/footer.tsx +1 -1
- package/src/registry/components/ui/form-message.tsx +2 -2
- package/src/registry/components/ui/icon-button.tsx +1 -1
- package/src/registry/components/ui/navbar.tsx +18 -11
- package/src/registry/components/ui/navigation-menu.tsx +2 -2
- package/src/registry/components/ui/number-input.tsx +3 -3
- package/src/registry/components/ui/package-manager-tabs.tsx +226 -0
- package/src/registry/components/ui/pager.tsx +102 -0
- package/src/registry/components/ui/popover.tsx +1 -1
- package/src/registry/components/ui/resizable.tsx +3 -1
- package/src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
- package/src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
- package/src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
- package/src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
- package/src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
- package/src/registry/components/ui/rich-text-editor/index.ts +11 -0
- package/src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
- package/src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
- package/src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
- package/src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
- package/src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
- package/src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
- package/src/registry/components/ui/scroll-area.tsx +12 -2
- package/src/registry/components/ui/section-heading.tsx +108 -0
- package/src/registry/components/ui/select.tsx +16 -19
- package/src/registry/components/ui/sheet.tsx +58 -53
- package/src/registry/components/ui/sidebar.tsx +4 -4
- package/src/registry/components/ui/status.tsx +7 -5
- package/src/registry/components/ui/steps.tsx +198 -0
- package/src/registry/components/ui/switch.tsx +1 -1
- package/src/registry/components/ui/table-of-contents.tsx +131 -0
- package/src/registry/components/ui/tabs.tsx +1 -1
- package/src/registry/components/ui/theme-toggle.tsx +175 -139
- package/src/registry/components/ui/titlebar-tabs.tsx +773 -0
- package/src/registry/components/ui/titlebar.tsx +468 -0
- package/src/registry/components/ui/toggle-group.tsx +1 -1
- package/src/registry/components/ui/updater-modal.tsx +254 -0
- package/src/registry/metadata.ts +167 -6
- package/src/registry/providers/theme-provider.tsx +16 -8
- package/src/registry/providers/theme-script.tsx +29 -9
- package/src/registry/providers/theme-transitions.ts +81 -50
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
Show,
|
|
12
12
|
} from "solid-js";
|
|
13
13
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
14
|
-
import { createClickOutside } from "
|
|
14
|
+
import { createClickOutside } from "@/hooks/create-click-outside";
|
|
15
15
|
import { cn } from "@/lib/cn";
|
|
16
16
|
import { ChevronDown } from "lucide-solid";
|
|
17
17
|
|
|
@@ -26,7 +26,7 @@ interface NavigationMenuContextValue {
|
|
|
26
26
|
|
|
27
27
|
const NavigationMenuContext = createContext<NavigationMenuContextValue>();
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
function useNavigationMenu() {
|
|
30
30
|
const context = useContext(NavigationMenuContext);
|
|
31
31
|
if (!context) {
|
|
32
32
|
throw new Error("useNavigationMenu must be used within a <NavigationMenu />");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createSignal, onCleanup, splitProps, type Component, type JSX } from "solid-js";
|
|
2
2
|
import { NumberField as KobalteNumberField } from "@kobalte/core/number-field";
|
|
3
3
|
import { Plus, Minus } from "lucide-solid";
|
|
4
|
-
import { Input } from "
|
|
5
|
-
import { Button } from "
|
|
6
|
-
import { createLongPress } from "
|
|
4
|
+
import { Input } from "@/components/ui/input";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { createLongPress } from "@/hooks/create-long-press";
|
|
7
7
|
import { cn } from "@/lib/cn";
|
|
8
8
|
|
|
9
9
|
export interface NumberInputProps {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSignal,
|
|
3
|
+
createMemo,
|
|
4
|
+
splitProps,
|
|
5
|
+
For,
|
|
6
|
+
Show,
|
|
7
|
+
type JSX,
|
|
8
|
+
type ParentComponent,
|
|
9
|
+
} from "solid-js";
|
|
10
|
+
import { Check, Copy } from "lucide-solid";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
13
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
|
14
|
+
import { createClipboard } from "@/hooks/create-clipboard";
|
|
15
|
+
import { cn } from "@/lib/cn";
|
|
16
|
+
|
|
17
|
+
export type DefaultPackageManager = "bun" | "pnpm" | "npm" | "yarn" | "bunx" | "npx" | "deno";
|
|
18
|
+
|
|
19
|
+
export interface PackageManagerTabsProps
|
|
20
|
+
extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
21
|
+
/** List of package managers to show in the switcher tabs */
|
|
22
|
+
managers?: string[];
|
|
23
|
+
/** Single CLI command template (e.g. "add @nikala-ui/core", "create @nikala-ui/docs my-app") */
|
|
24
|
+
command?: string;
|
|
25
|
+
/** Explicit map of custom commands per manager (e.g. { bun: "bun add foo", npm: "npm i foo" }) */
|
|
26
|
+
commands?: Record<string, string>;
|
|
27
|
+
/** Selected manager (controlled) */
|
|
28
|
+
value?: string;
|
|
29
|
+
/** Initial selected manager (uncontrolled) */
|
|
30
|
+
defaultValue?: string;
|
|
31
|
+
/** Callback fired when the active package manager changes */
|
|
32
|
+
onChange?: (manager: string) => void;
|
|
33
|
+
/** Whether to show the copy button in the header */
|
|
34
|
+
copyable?: boolean;
|
|
35
|
+
class?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Automatically formats a command string for a specific package manager.
|
|
40
|
+
*/
|
|
41
|
+
function resolvePmCommand(manager: string, command: string): string {
|
|
42
|
+
const trimmed = command.trim();
|
|
43
|
+
const pm = manager.toLowerCase();
|
|
44
|
+
|
|
45
|
+
// 1. "create" command
|
|
46
|
+
const createMatch = trimmed.match(/^(?:bun|npm|npx|pnpm|yarn|deno)?\s*create\s+(.*)$/);
|
|
47
|
+
if (createMatch) {
|
|
48
|
+
const args = createMatch[1];
|
|
49
|
+
if (pm === "bun" || pm === "bunx") return `bun create ${args}`;
|
|
50
|
+
if (pm === "pnpm") return `pnpm create ${args}`;
|
|
51
|
+
if (pm === "yarn") return `yarn create ${args}`;
|
|
52
|
+
if (pm === "deno") return `deno run -A npm:create-${args}`;
|
|
53
|
+
return `npm create ${args}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. "add" / "install" command
|
|
57
|
+
const addMatch = trimmed.match(/^(?:bun\s+add|npm\s+i|npm\s+install|pnpm\s+add|yarn\s+add|deno\s+add|add|install|i)\s+(.*)$/);
|
|
58
|
+
if (addMatch) {
|
|
59
|
+
const args = addMatch[1];
|
|
60
|
+
if (pm === "npm") return `npm i ${args}`;
|
|
61
|
+
if (pm === "pnpm") return `pnpm add ${args}`;
|
|
62
|
+
if (pm === "yarn") return `yarn add ${args}`;
|
|
63
|
+
if (pm === "deno") return `deno add ${args}`;
|
|
64
|
+
return `bun add ${args}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. CLI runner (e.g. "nikala add button" or "@nikala-ui/cli add button")
|
|
68
|
+
const runnerMatch = trimmed.match(/^(?:bunx|npx|pnpm\s+dlx|yarn\s+dlx)?\s*(.*)$/);
|
|
69
|
+
if (runnerMatch) {
|
|
70
|
+
const rest = runnerMatch[1];
|
|
71
|
+
if (pm === "npx") return `npx ${rest}`;
|
|
72
|
+
if (pm === "pnpm") return `pnpm dlx ${rest}`;
|
|
73
|
+
if (pm === "yarn") return `yarn dlx ${rest}`;
|
|
74
|
+
if (pm === "bun" || pm === "bunx") return `bunx ${rest}`;
|
|
75
|
+
return `${pm} ${rest}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return trimmed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function highlightCliCommandText(code: string): string {
|
|
82
|
+
const trimmed = code.trim();
|
|
83
|
+
if (!trimmed) return "";
|
|
84
|
+
const parts = trimmed.split(/\s+/);
|
|
85
|
+
|
|
86
|
+
let pkgIndex = 1;
|
|
87
|
+
if ((parts[0] === "pnpm" || parts[0] === "yarn") && parts[1] === "dlx") {
|
|
88
|
+
pkgIndex = 2;
|
|
89
|
+
} else if (parts[0] === "bun" && (parts[1] === "create" || parts[1] === "add" || parts[1] === "run")) {
|
|
90
|
+
pkgIndex = 2;
|
|
91
|
+
} else if (parts[0] === "npm" && (parts[1] === "create" || parts[1] === "run")) {
|
|
92
|
+
pkgIndex = 2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const mainPart = parts.slice(0, pkgIndex).join(" ");
|
|
96
|
+
const restTokens = parts.slice(pkgIndex);
|
|
97
|
+
|
|
98
|
+
const highlightedRest = restTokens
|
|
99
|
+
.map((token) => {
|
|
100
|
+
if (token.startsWith("-")) {
|
|
101
|
+
return `<span class="text-amber-500 dark:text-amber-400 font-medium">${token}</span>`;
|
|
102
|
+
}
|
|
103
|
+
if (["add", "create", "init", "run", "dev", "build", "set", "theme", "diff", "list"].includes(token)) {
|
|
104
|
+
return `<span class="text-sky-500 dark:text-sky-400 font-semibold">${token}</span>`;
|
|
105
|
+
}
|
|
106
|
+
return `<span class="text-violet-500 dark:text-violet-400 font-medium">${token}</span>`;
|
|
107
|
+
})
|
|
108
|
+
.join(" ");
|
|
109
|
+
|
|
110
|
+
return `<span class="text-emerald-500 dark:text-emerald-400 font-semibold">${mainPart}</span>${
|
|
111
|
+
highlightedRest ? " " + highlightedRest : ""
|
|
112
|
+
}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const PackageManagerTabs: ParentComponent<PackageManagerTabsProps> = (props) => {
|
|
116
|
+
const [local, rest] = splitProps(props, [
|
|
117
|
+
"managers",
|
|
118
|
+
"command",
|
|
119
|
+
"commands",
|
|
120
|
+
"value",
|
|
121
|
+
"defaultValue",
|
|
122
|
+
"onChange",
|
|
123
|
+
"copyable",
|
|
124
|
+
"class",
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const defaultManagers = ["bun", "pnpm", "npm", "yarn"];
|
|
128
|
+
const managersList = () => local.managers || defaultManagers;
|
|
129
|
+
|
|
130
|
+
const [internalActive, setInternalActive] = createSignal(
|
|
131
|
+
local.defaultValue || managersList()[0] || "bun"
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const activeManager = () => local.value ?? internalActive();
|
|
135
|
+
|
|
136
|
+
const handleSelect = (pm: string) => {
|
|
137
|
+
if (local.value === undefined) {
|
|
138
|
+
setInternalActive(pm);
|
|
139
|
+
}
|
|
140
|
+
local.onChange?.(pm);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const currentCommand = createMemo(() => {
|
|
144
|
+
const pm = activeManager();
|
|
145
|
+
if (local.commands && local.commands[pm]) {
|
|
146
|
+
return local.commands[pm];
|
|
147
|
+
}
|
|
148
|
+
if (local.command) {
|
|
149
|
+
return resolvePmCommand(pm, local.command);
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const clipboard = createClipboard();
|
|
155
|
+
const copyable = () => local.copyable ?? true;
|
|
156
|
+
|
|
157
|
+
const handleCopy = async () => {
|
|
158
|
+
const text = currentCommand();
|
|
159
|
+
if (!text) return;
|
|
160
|
+
await clipboard.copy(text);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div
|
|
165
|
+
class={cn(
|
|
166
|
+
"group relative my-4 w-full overflow-hidden rounded-lg border border-border bg-muted/40 font-mono text-sm shadow-2xs",
|
|
167
|
+
local.class
|
|
168
|
+
)}
|
|
169
|
+
{...rest}
|
|
170
|
+
>
|
|
171
|
+
{/* Header with Manager Switcher Tabs and Copy Button */}
|
|
172
|
+
<div class="flex h-9 items-center justify-between border-b border-border/70 bg-muted/70 px-3.5 select-none">
|
|
173
|
+
{/* Switcher Tabs */}
|
|
174
|
+
<Tabs
|
|
175
|
+
value={activeManager()}
|
|
176
|
+
onChange={handleSelect}
|
|
177
|
+
class="w-auto flex-none"
|
|
178
|
+
>
|
|
179
|
+
<TabsList class="w-auto inline-flex h-auto bg-background/60 p-0.5 border border-border/50 gap-0.5 rounded-md font-mono">
|
|
180
|
+
<For each={managersList()}>
|
|
181
|
+
{(pm) => (
|
|
182
|
+
<TabsTrigger
|
|
183
|
+
value={pm}
|
|
184
|
+
class="rounded-xs px-2 py-0.5 text-[11px] font-mono transition-colors cursor-pointer data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:font-semibold data-[state=active]:shadow-2xs"
|
|
185
|
+
>
|
|
186
|
+
{pm}
|
|
187
|
+
</TabsTrigger>
|
|
188
|
+
)}
|
|
189
|
+
</For>
|
|
190
|
+
</TabsList>
|
|
191
|
+
</Tabs>
|
|
192
|
+
|
|
193
|
+
{/* Copy Button */}
|
|
194
|
+
<Show when={copyable() && currentCommand()}>
|
|
195
|
+
<Tooltip>
|
|
196
|
+
<TooltipTrigger
|
|
197
|
+
as={Button}
|
|
198
|
+
variant="ghost"
|
|
199
|
+
size="icon"
|
|
200
|
+
onClick={handleCopy}
|
|
201
|
+
aria-label={clipboard.copied() ? "Copied command" : "Copy command"}
|
|
202
|
+
class="size-6 p-0 rounded-xs text-muted-foreground hover:bg-background hover:text-foreground cursor-pointer"
|
|
203
|
+
>
|
|
204
|
+
<Show when={clipboard.copied()} fallback={<Copy class="size-3.5" />}>
|
|
205
|
+
<Check class="size-3.5 text-emerald-500 dark:text-emerald-400" />
|
|
206
|
+
</Show>
|
|
207
|
+
</TooltipTrigger>
|
|
208
|
+
<TooltipContent class="text-[10px] py-1 px-2">
|
|
209
|
+
{clipboard.copied() ? "Copied!" : "Copy command"}
|
|
210
|
+
</TooltipContent>
|
|
211
|
+
</Tooltip>
|
|
212
|
+
</Show>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Command Box Content */}
|
|
216
|
+
<div class="overflow-x-auto p-4 text-[13px] leading-relaxed [scrollbar-width:thin]">
|
|
217
|
+
<pre class="font-mono">
|
|
218
|
+
<code>
|
|
219
|
+
<span class="text-sky-500 dark:text-sky-400 font-semibold select-none">$ </span>
|
|
220
|
+
<span innerHTML={highlightCliCommandText(currentCommand())} />
|
|
221
|
+
</code>
|
|
222
|
+
</pre>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { splitProps, Show, children, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { ChevronLeft, ChevronRight } from "lucide-solid";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
CardDescription,
|
|
8
|
+
} from "@/components/ui/card";
|
|
9
|
+
import { cn } from "@/lib/cn";
|
|
10
|
+
|
|
11
|
+
export interface PagerItem {
|
|
12
|
+
title: string;
|
|
13
|
+
href: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PagerLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
17
|
+
title: string;
|
|
18
|
+
href: string;
|
|
19
|
+
type: "prev" | "next";
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Individual Next or Previous page navigation card.
|
|
25
|
+
*/
|
|
26
|
+
export const PagerLink: Component<PagerLinkProps> = (props) => {
|
|
27
|
+
const [local, rest] = splitProps(props, ["title", "href", "type", "class"]);
|
|
28
|
+
|
|
29
|
+
const isNext = () => local.type === "next";
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<a
|
|
33
|
+
href={local.href}
|
|
34
|
+
class={cn(
|
|
35
|
+
"group block rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
36
|
+
isNext() && "sm:col-start-2",
|
|
37
|
+
local.class
|
|
38
|
+
)}
|
|
39
|
+
{...rest}
|
|
40
|
+
>
|
|
41
|
+
<Card class="h-full transition-all group-hover:bg-accent/40 group-hover:shadow-2xs">
|
|
42
|
+
<CardHeader class={cn("p-4 flex flex-col space-y-1.5", isNext() && "items-end text-right")}>
|
|
43
|
+
<CardDescription class="flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
|
44
|
+
<Show when={!isNext()}>
|
|
45
|
+
<ChevronLeft class="size-3.5 transition-transform group-hover:-translate-x-0.5" />
|
|
46
|
+
<span>Previous</span>
|
|
47
|
+
</Show>
|
|
48
|
+
<Show when={isNext()}>
|
|
49
|
+
<span>Next</span>
|
|
50
|
+
<ChevronRight class="size-3.5 transition-transform group-hover:translate-x-0.5" />
|
|
51
|
+
</Show>
|
|
52
|
+
</CardDescription>
|
|
53
|
+
<CardTitle class="text-sm font-semibold tracking-tight group-hover:text-primary transition-colors line-clamp-1">
|
|
54
|
+
{local.title}
|
|
55
|
+
</CardTitle>
|
|
56
|
+
</CardHeader>
|
|
57
|
+
</Card>
|
|
58
|
+
</a>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export interface PagerProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
63
|
+
/** Previous documentation page item */
|
|
64
|
+
prev?: PagerItem | null;
|
|
65
|
+
/** Next documentation page item */
|
|
66
|
+
next?: PagerItem | null;
|
|
67
|
+
class?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Documentation Next / Previous article navigation block composed of Nikala UI Card primitives.
|
|
72
|
+
*/
|
|
73
|
+
export const Pager: Component<PagerProps> = (props) => {
|
|
74
|
+
const [local, rest] = splitProps(props, ["prev", "next", "class", "children"]);
|
|
75
|
+
const resolved = children(() => local.children);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
class={cn("grid grid-cols-1 gap-4 sm:grid-cols-2 mt-10 pt-6 border-t border-border", local.class)}
|
|
80
|
+
{...rest}
|
|
81
|
+
>
|
|
82
|
+
<Show
|
|
83
|
+
when={resolved()}
|
|
84
|
+
fallback={
|
|
85
|
+
<>
|
|
86
|
+
<Show
|
|
87
|
+
when={local.prev}
|
|
88
|
+
fallback={<span class="hidden sm:block" />}
|
|
89
|
+
>
|
|
90
|
+
<PagerLink type="prev" title={local.prev!.title} href={local.prev!.href} />
|
|
91
|
+
</Show>
|
|
92
|
+
<Show when={local.next}>
|
|
93
|
+
<PagerLink type="next" title={local.next!.title} href={local.next!.href} />
|
|
94
|
+
</Show>
|
|
95
|
+
</>
|
|
96
|
+
}
|
|
97
|
+
>
|
|
98
|
+
{resolved()}
|
|
99
|
+
</Show>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { splitProps, type Component, type ComponentProps } from "solid-js";
|
|
2
2
|
import { Popover as KobaltePopover } from "@kobalte/core/popover";
|
|
3
|
-
import { createClickOutside } from "
|
|
3
|
+
import { createClickOutside } from "@/hooks/create-click-outside";
|
|
4
4
|
import { X } from "lucide-solid";
|
|
5
5
|
import { cn } from "@/lib/cn";
|
|
6
6
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type JSX,
|
|
8
8
|
type Accessor,
|
|
9
9
|
} from "solid-js";
|
|
10
|
-
import { createElementSize } from "
|
|
10
|
+
import { createElementSize } from "@/hooks/create-resize-observer";
|
|
11
11
|
import { GripVertical, GripHorizontal } from "lucide-solid";
|
|
12
12
|
import { cn } from "@/lib/cn";
|
|
13
13
|
|
|
@@ -66,6 +66,7 @@ export const ResizableGroup: Component<ResizableGroupProps> = (props) => {
|
|
|
66
66
|
let newLeft = startLeftPct + deltaPct;
|
|
67
67
|
let newRight = startRightPct - deltaPct;
|
|
68
68
|
|
|
69
|
+
// Min size limits (10% minimum)
|
|
69
70
|
if (newLeft < 10) {
|
|
70
71
|
newLeft = 10;
|
|
71
72
|
newRight = startLeftPct + startRightPct - 10;
|
|
@@ -134,6 +135,7 @@ export const ResizablePanel: Component<ResizablePanelProps> = (props) => {
|
|
|
134
135
|
|
|
135
136
|
const currentPct = () => ctx.sizes()[local.id] ?? local.initialSize ?? 50;
|
|
136
137
|
|
|
138
|
+
// Utilize Nikala UI createElementSize hook for reactive size inspection
|
|
137
139
|
const containerSize = createElementSize(() => ctx.containerRef());
|
|
138
140
|
|
|
139
141
|
return (
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSignal,
|
|
3
|
+
createEffect,
|
|
4
|
+
onCleanup,
|
|
5
|
+
Show,
|
|
6
|
+
For,
|
|
7
|
+
type Component,
|
|
8
|
+
} from "solid-js";
|
|
9
|
+
import { Portal } from "solid-js/web";
|
|
10
|
+
import { useRichTextEditor } from "./editor.js";
|
|
11
|
+
import { ToolbarButton, HIGHLIGHT_PALETTE } from "./toolbar.js";
|
|
12
|
+
import {
|
|
13
|
+
Bold,
|
|
14
|
+
Italic,
|
|
15
|
+
Underline as UnderlineIcon,
|
|
16
|
+
Strikethrough,
|
|
17
|
+
Code,
|
|
18
|
+
Link as LinkIcon,
|
|
19
|
+
Highlighter,
|
|
20
|
+
Heading1,
|
|
21
|
+
Heading2,
|
|
22
|
+
List,
|
|
23
|
+
ListTodo,
|
|
24
|
+
} from "lucide-solid";
|
|
25
|
+
|
|
26
|
+
export const EditorBubbleMenu: Component = () => {
|
|
27
|
+
const { actions, container, isFullscreen, setShowLinkDialog } = useRichTextEditor();
|
|
28
|
+
const [isVisible, setIsVisible] = createSignal(false);
|
|
29
|
+
const [position, setPosition] = createSignal({ top: 0, left: 0 });
|
|
30
|
+
const [showHighlightPalette, setShowHighlightPalette] = createSignal(false);
|
|
31
|
+
|
|
32
|
+
createEffect(() => {
|
|
33
|
+
if (typeof window === "undefined") return;
|
|
34
|
+
|
|
35
|
+
const handleSelectionChange = () => {
|
|
36
|
+
const ed = actions.editor();
|
|
37
|
+
const containerEl = container();
|
|
38
|
+
if (!ed || !ed.isFocused || !ed.isEditable) {
|
|
39
|
+
setIsVisible(false);
|
|
40
|
+
setShowHighlightPalette(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { from, to } = ed.state.selection;
|
|
45
|
+
if (from === to || ed.state.selection.empty) {
|
|
46
|
+
setIsVisible(false);
|
|
47
|
+
setShowHighlightPalette(false);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const selection = window.getSelection();
|
|
52
|
+
if (!selection || selection.rangeCount === 0) {
|
|
53
|
+
setIsVisible(false);
|
|
54
|
+
setShowHighlightPalette(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const range = selection.getRangeAt(0);
|
|
59
|
+
const rect = range.getBoundingClientRect();
|
|
60
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
61
|
+
setIsVisible(false);
|
|
62
|
+
setShowHighlightPalette(false);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check that selection is inside the editor view element
|
|
67
|
+
const editorDom = ed.view.dom;
|
|
68
|
+
if (!editorDom.contains(range.commonAncestorContainer)) {
|
|
69
|
+
setIsVisible(false);
|
|
70
|
+
setShowHighlightPalette(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isFullscreen() && containerEl) {
|
|
75
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
76
|
+
const top = rect.top - containerRect.top - 46;
|
|
77
|
+
const left = rect.left - containerRect.left + rect.width / 2;
|
|
78
|
+
|
|
79
|
+
setPosition({
|
|
80
|
+
top: Math.max(8, top),
|
|
81
|
+
left: Math.max(140, Math.min(containerRect.width - 140, left)),
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
// Normal mode: viewport coordinates
|
|
85
|
+
setPosition({
|
|
86
|
+
top: Math.max(12, rect.top - 46),
|
|
87
|
+
left: Math.max(140, Math.min(window.innerWidth - 140, rect.left + rect.width / 2)),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setIsVisible(true);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleMouseDown = (e: MouseEvent) => {
|
|
95
|
+
const target = e.target as HTMLElement;
|
|
96
|
+
// If clicking toolbar or any open popovers, hide bubble menu
|
|
97
|
+
if (
|
|
98
|
+
target.closest(".editor-popover-trigger") ||
|
|
99
|
+
target.closest(".editor-popover-content") ||
|
|
100
|
+
target.closest(".editor-toolbar")
|
|
101
|
+
) {
|
|
102
|
+
setIsVisible(false);
|
|
103
|
+
setShowHighlightPalette(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
document.addEventListener("selectionchange", handleSelectionChange);
|
|
108
|
+
document.addEventListener("mousedown", handleMouseDown);
|
|
109
|
+
|
|
110
|
+
onCleanup(() => {
|
|
111
|
+
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
112
|
+
document.removeEventListener("mousedown", handleMouseDown);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const portalTarget = () => {
|
|
117
|
+
if (typeof document === "undefined") return undefined;
|
|
118
|
+
return isFullscreen() ? container() || document.body : document.body;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Show when={isVisible()}>
|
|
123
|
+
<Portal mount={portalTarget()}>
|
|
124
|
+
<div
|
|
125
|
+
class={`${
|
|
126
|
+
isFullscreen() ? "absolute" : "fixed"
|
|
127
|
+
} z-[999999] flex items-center gap-0.5 p-1 rounded-md border border-border bg-popover text-popover-foreground shadow-xl backdrop-blur-md -translate-x-1/2 transition-all duration-75 animate-in fade-in zoom-in-95 pointer-events-auto select-none`}
|
|
128
|
+
style={{
|
|
129
|
+
top: `${position().top}px`,
|
|
130
|
+
left: `${position().left}px`,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<ToolbarButton onClick={actions.toggleBold} isActive={actions.isActive("bold")} tooltip="Bold">
|
|
134
|
+
<Bold class="h-3.5 w-3.5" />
|
|
135
|
+
</ToolbarButton>
|
|
136
|
+
<ToolbarButton onClick={actions.toggleItalic} isActive={actions.isActive("italic")} tooltip="Italic">
|
|
137
|
+
<Italic class="h-3.5 w-3.5" />
|
|
138
|
+
</ToolbarButton>
|
|
139
|
+
<ToolbarButton onClick={actions.toggleUnderline} isActive={actions.isActive("underline")} tooltip="Underline">
|
|
140
|
+
<UnderlineIcon class="h-3.5 w-3.5" />
|
|
141
|
+
</ToolbarButton>
|
|
142
|
+
<ToolbarButton onClick={actions.toggleStrike} isActive={actions.isActive("strike")} tooltip="Strikethrough">
|
|
143
|
+
<Strikethrough class="h-3.5 w-3.5" />
|
|
144
|
+
</ToolbarButton>
|
|
145
|
+
<ToolbarButton onClick={actions.toggleCode} isActive={actions.isActive("code")} tooltip="Code">
|
|
146
|
+
<Code class="h-3.5 w-3.5" />
|
|
147
|
+
</ToolbarButton>
|
|
148
|
+
|
|
149
|
+
{/* Marker Highlight with Tailwind Palette */}
|
|
150
|
+
<div class="relative">
|
|
151
|
+
<ToolbarButton
|
|
152
|
+
onClick={() => setShowHighlightPalette((p) => !p)}
|
|
153
|
+
isActive={actions.isActive("highlight")}
|
|
154
|
+
tooltip="Highlight Color"
|
|
155
|
+
>
|
|
156
|
+
<Highlighter class="h-3.5 w-3.5" />
|
|
157
|
+
</ToolbarButton>
|
|
158
|
+
|
|
159
|
+
<Show when={showHighlightPalette()}>
|
|
160
|
+
<div class="absolute top-full left-1/2 -translate-x-1/2 mt-1.5 z-30 flex flex-col gap-1.5 p-2 bg-popover text-popover-foreground border border-border rounded-lg shadow-2xl min-w-[200px]">
|
|
161
|
+
<div class="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
|
|
162
|
+
Marker Palette
|
|
163
|
+
</div>
|
|
164
|
+
<div class="grid grid-cols-5 gap-1.5">
|
|
165
|
+
<For each={HIGHLIGHT_PALETTE}>
|
|
166
|
+
{(color) => (
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
title={color.name}
|
|
170
|
+
class="h-6 w-6 rounded-md border border-border/60 hover:scale-110 transition-transform cursor-pointer shadow-2xs"
|
|
171
|
+
style={{ "background-color": color.value }}
|
|
172
|
+
onClick={() => {
|
|
173
|
+
actions.toggleHighlight(color.value);
|
|
174
|
+
setShowHighlightPalette(false);
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</For>
|
|
179
|
+
</div>
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
class="w-full text-center py-1 mt-1 text-xs rounded-md bg-muted hover:bg-muted/80 text-foreground transition-colors cursor-pointer font-medium"
|
|
183
|
+
onClick={() => {
|
|
184
|
+
actions.toggleHighlight();
|
|
185
|
+
setShowHighlightPalette(false);
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
Clear Highlight
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</Show>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<ToolbarButton
|
|
195
|
+
onClick={() => setShowLinkDialog(true)}
|
|
196
|
+
isActive={actions.isActive("link")}
|
|
197
|
+
tooltip="Insert Link"
|
|
198
|
+
>
|
|
199
|
+
<LinkIcon class="h-3.5 w-3.5" />
|
|
200
|
+
</ToolbarButton>
|
|
201
|
+
|
|
202
|
+
<div class="h-4 w-px bg-border mx-0.5" />
|
|
203
|
+
|
|
204
|
+
<ToolbarButton
|
|
205
|
+
onClick={() => actions.setHeading(1)}
|
|
206
|
+
isActive={actions.isActive("heading", { level: 1 })}
|
|
207
|
+
tooltip="H1"
|
|
208
|
+
>
|
|
209
|
+
<Heading1 class="h-3.5 w-3.5" />
|
|
210
|
+
</ToolbarButton>
|
|
211
|
+
<ToolbarButton
|
|
212
|
+
onClick={() => actions.setHeading(2)}
|
|
213
|
+
isActive={actions.isActive("heading", { level: 2 })}
|
|
214
|
+
tooltip="H2"
|
|
215
|
+
>
|
|
216
|
+
<Heading2 class="h-3.5 w-3.5" />
|
|
217
|
+
</ToolbarButton>
|
|
218
|
+
<ToolbarButton
|
|
219
|
+
onClick={actions.toggleBulletList}
|
|
220
|
+
isActive={actions.isActive("bulletList")}
|
|
221
|
+
tooltip="List"
|
|
222
|
+
>
|
|
223
|
+
<List class="h-3.5 w-3.5" />
|
|
224
|
+
</ToolbarButton>
|
|
225
|
+
<ToolbarButton
|
|
226
|
+
onClick={actions.toggleTaskList}
|
|
227
|
+
isActive={actions.isActive("taskList")}
|
|
228
|
+
tooltip="Tasks"
|
|
229
|
+
>
|
|
230
|
+
<ListTodo class="h-3.5 w-3.5" />
|
|
231
|
+
</ToolbarButton>
|
|
232
|
+
</div>
|
|
233
|
+
</Portal>
|
|
234
|
+
</Show>
|
|
235
|
+
);
|
|
236
|
+
};
|