@krizaka/ui 2.0.0-beta.2 → 2.0.0-beta.3
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/README.md +30 -0
- package/dist/alert.d.ts +2 -0
- package/dist/alert.js.map +1 -1
- package/dist/avatar.d.ts +5 -0
- package/dist/avatar.js.map +1 -1
- package/dist/badge.d.ts +12 -0
- package/dist/badge.js.map +1 -1
- package/dist/{button-G5qY4qSY.d.ts → button-8La7l0V4.d.ts} +15 -0
- package/dist/button.d.ts +19 -0
- package/dist/button.js.map +1 -1
- package/dist/card.d.ts +24 -1
- package/dist/card.js.map +1 -1
- package/dist/chip.d.ts +23 -0
- package/dist/chip.js.map +1 -1
- package/dist/{chunk-5FRYKFTO.js → chunk-3X5MVH54.js} +2 -2
- package/dist/chunk-3X5MVH54.js.map +1 -0
- package/dist/{chunk-QRG5RYEI.js → chunk-ZUD4U22H.js} +1 -1
- package/dist/chunk-ZUD4U22H.js.map +1 -0
- package/dist/command.d.ts +4 -1
- package/dist/command.js +2 -2
- package/dist/command.js.map +1 -1
- package/dist/confirm-button.d.ts +1 -1
- package/dist/confirm-button.js +1 -1
- package/dist/countdown.d.ts +3 -0
- package/dist/countdown.js.map +1 -1
- package/dist/{dialog-DGBY2grk.d.ts → dialog-GQ4Nvuyf.d.ts} +10 -0
- package/dist/dialog.d.ts +9 -1
- package/dist/dialog.js +2 -2
- package/dist/dialog.js.map +1 -1
- package/dist/dropdown-menu.d.ts +1 -0
- package/dist/dropdown-menu.js.map +1 -1
- package/dist/empty-state.d.ts +2 -0
- package/dist/empty-state.js.map +1 -1
- package/dist/kbd.d.ts +7 -1
- package/dist/kbd.js.map +1 -1
- package/dist/page-header.d.ts +2 -0
- package/dist/page-header.js.map +1 -1
- package/dist/progress.d.ts +5 -0
- package/dist/progress.js.map +1 -1
- package/dist/skeleton.d.ts +7 -1
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.d.ts +2 -0
- package/dist/slider.js.map +1 -1
- package/dist/spinner.d.ts +5 -0
- package/dist/spinner.js.map +1 -1
- package/dist/stat.d.ts +2 -0
- package/dist/stat.js.map +1 -1
- package/dist/switch.d.ts +7 -3
- package/dist/switch.js.map +1 -1
- package/dist/tabs.d.ts +4 -1
- package/dist/tabs.js.map +1 -1
- package/dist/theme.d.ts +10 -2
- package/dist/theme.js +4 -2
- package/dist/theme.js.map +1 -1
- package/dist/tooltip.d.ts +3 -0
- package/dist/tooltip.js.map +1 -1
- package/package.json +14 -3
- package/registry/alert.json +59 -0
- package/registry/avatar.json +114 -0
- package/registry/badge.json +59 -0
- package/registry/button.json +115 -0
- package/registry/card.json +154 -0
- package/registry/checkbox.json +46 -0
- package/registry/chip.json +164 -0
- package/registry/cn.json +21 -0
- package/registry/command.json +162 -0
- package/registry/confirm-button.json +104 -0
- package/registry/countdown.json +84 -0
- package/registry/demos/alert.d.ts +5 -0
- package/registry/demos/alert.js +12 -0
- package/registry/demos/alert.tsx +15 -0
- package/registry/demos/avatar.d.ts +5 -0
- package/registry/demos/avatar.js +14 -0
- package/registry/demos/avatar.tsx +13 -0
- package/registry/demos/badge.d.ts +5 -0
- package/registry/demos/badge.js +11 -0
- package/registry/demos/badge.tsx +5 -0
- package/registry/demos/button.d.ts +5 -0
- package/registry/demos/button.js +11 -0
- package/registry/demos/button.tsx +5 -0
- package/registry/demos/card.d.ts +5 -0
- package/registry/demos/card.js +30 -0
- package/registry/demos/card.tsx +37 -0
- package/registry/demos/checkbox.d.ts +5 -0
- package/registry/demos/checkbox.js +11 -0
- package/registry/demos/checkbox.tsx +5 -0
- package/registry/demos/chip.d.ts +5 -0
- package/registry/demos/chip.js +15 -0
- package/registry/demos/chip.tsx +11 -0
- package/registry/demos/cn.d.ts +7 -0
- package/registry/demos/cn.js +17 -0
- package/registry/demos/cn.tsx +9 -0
- package/registry/demos/command.d.ts +5 -0
- package/registry/demos/command.js +32 -0
- package/registry/demos/command.tsx +28 -0
- package/registry/demos/confirm-button.d.ts +5 -0
- package/registry/demos/confirm-button.js +12 -0
- package/registry/demos/confirm-button.tsx +13 -0
- package/registry/demos/countdown.d.ts +5 -0
- package/registry/demos/countdown.js +13 -0
- package/registry/demos/countdown.tsx +10 -0
- package/registry/demos/dialog.d.ts +7 -0
- package/registry/demos/dialog.js +29 -0
- package/registry/demos/dialog.tsx +31 -0
- package/registry/demos/dropdown-menu.d.ts +7 -0
- package/registry/demos/dropdown-menu.js +36 -0
- package/registry/demos/dropdown-menu.tsx +27 -0
- package/registry/demos/empty-state.d.ts +5 -0
- package/registry/demos/empty-state.js +15 -0
- package/registry/demos/empty-state.tsx +12 -0
- package/registry/demos/field.d.ts +5 -0
- package/registry/demos/field.js +15 -0
- package/registry/demos/field.tsx +11 -0
- package/registry/demos/kbd.d.ts +5 -0
- package/registry/demos/kbd.js +11 -0
- package/registry/demos/kbd.tsx +5 -0
- package/registry/demos/page-header.d.ts +5 -0
- package/registry/demos/page-header.js +11 -0
- package/registry/demos/page-header.tsx +5 -0
- package/registry/demos/popover.d.ts +7 -0
- package/registry/demos/popover.js +25 -0
- package/registry/demos/popover.tsx +31 -0
- package/registry/demos/progress.d.ts +5 -0
- package/registry/demos/progress.js +11 -0
- package/registry/demos/progress.tsx +5 -0
- package/registry/demos/radio-group.d.ts +5 -0
- package/registry/demos/radio-group.js +16 -0
- package/registry/demos/radio-group.tsx +14 -0
- package/registry/demos/separator.d.ts +5 -0
- package/registry/demos/separator.js +15 -0
- package/registry/demos/separator.tsx +11 -0
- package/registry/demos/skeleton.d.ts +5 -0
- package/registry/demos/skeleton.js +20 -0
- package/registry/demos/skeleton.tsx +16 -0
- package/registry/demos/slider.d.ts +5 -0
- package/registry/demos/slider.js +11 -0
- package/registry/demos/slider.tsx +5 -0
- package/registry/demos/slot.d.ts +5 -0
- package/registry/demos/slot.js +14 -0
- package/registry/demos/slot.tsx +13 -0
- package/registry/demos/spinner.d.ts +5 -0
- package/registry/demos/spinner.js +11 -0
- package/registry/demos/spinner.tsx +5 -0
- package/registry/demos/stat.d.ts +5 -0
- package/registry/demos/stat.js +11 -0
- package/registry/demos/stat.tsx +5 -0
- package/registry/demos/switch.d.ts +5 -0
- package/registry/demos/switch.js +11 -0
- package/registry/demos/switch.tsx +5 -0
- package/registry/demos/tabs.d.ts +5 -0
- package/registry/demos/tabs.js +22 -0
- package/registry/demos/tabs.tsx +28 -0
- package/registry/demos/theme.d.ts +5 -0
- package/registry/demos/theme.js +11 -0
- package/registry/demos/theme.tsx +9 -0
- package/registry/demos/toast.d.ts +8 -0
- package/registry/demos/toast.js +29 -0
- package/registry/demos/toast.tsx +31 -0
- package/registry/demos/tooltip.d.ts +7 -0
- package/registry/demos/tooltip.js +15 -0
- package/registry/demos/tooltip.tsx +15 -0
- package/registry/dialog.json +218 -0
- package/registry/dropdown-menu.json +63 -0
- package/registry/empty-state.json +59 -0
- package/registry/field.json +66 -0
- package/registry/index.json +320 -0
- package/registry/kbd.json +38 -0
- package/registry/page-header.json +66 -0
- package/registry/popover.json +44 -0
- package/registry/progress.json +80 -0
- package/registry/radio-group.json +64 -0
- package/registry/separator.json +25 -0
- package/registry/skeleton.json +38 -0
- package/registry/slider.json +95 -0
- package/registry/slot.json +31 -0
- package/registry/spinner.json +45 -0
- package/registry/stat.json +66 -0
- package/registry/switch.json +53 -0
- package/registry/tabs.json +44 -0
- package/registry/theme.json +78 -0
- package/registry/toast.json +54 -0
- package/registry/tooltip.json +91 -0
- package/dist/chunk-5FRYKFTO.js.map +0 -1
- package/dist/chunk-QRG5RYEI.js.map +0 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/demos/tabs.tsx
|
|
4
|
+
import { Tabs } from "@krizaka/ui/tabs";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function TabsDemo() {
|
|
7
|
+
return /* @__PURE__ */ jsxs(Tabs.Root, { defaultValue: "videos", children: [
|
|
8
|
+
/* @__PURE__ */ jsxs(Tabs.List, { "aria-label": "Profile", children: [
|
|
9
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "videos", children: "Videos" }),
|
|
10
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "stories", children: "Stories" }),
|
|
11
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "collections", children: "Collections" }),
|
|
12
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "about", disabled: true, children: "About" })
|
|
13
|
+
] }),
|
|
14
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "videos", className: "text-sm text-fg-secondary", children: "Twelve videos, newest first." }),
|
|
15
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "stories", className: "text-sm text-fg-secondary", children: "Stories of the last 24 hours." }),
|
|
16
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "collections", className: "text-sm text-fg-secondary", children: "Three public collections." }),
|
|
17
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "about", className: "text-sm text-fg-secondary", children: "About this channel." })
|
|
18
|
+
] });
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
TabsDemo as default
|
|
22
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Tabs } from "@krizaka/ui/tabs";
|
|
2
|
+
|
|
3
|
+
export default function TabsDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<Tabs.Root defaultValue="videos">
|
|
6
|
+
<Tabs.List aria-label="Profile">
|
|
7
|
+
<Tabs.Trigger value="videos">Videos</Tabs.Trigger>
|
|
8
|
+
<Tabs.Trigger value="stories">Stories</Tabs.Trigger>
|
|
9
|
+
<Tabs.Trigger value="collections">Collections</Tabs.Trigger>
|
|
10
|
+
<Tabs.Trigger value="about" disabled>
|
|
11
|
+
About
|
|
12
|
+
</Tabs.Trigger>
|
|
13
|
+
</Tabs.List>
|
|
14
|
+
<Tabs.Content value="videos" className="text-sm text-fg-secondary">
|
|
15
|
+
Twelve videos, newest first.
|
|
16
|
+
</Tabs.Content>
|
|
17
|
+
<Tabs.Content value="stories" className="text-sm text-fg-secondary">
|
|
18
|
+
Stories of the last 24 hours.
|
|
19
|
+
</Tabs.Content>
|
|
20
|
+
<Tabs.Content value="collections" className="text-sm text-fg-secondary">
|
|
21
|
+
Three public collections.
|
|
22
|
+
</Tabs.Content>
|
|
23
|
+
<Tabs.Content value="about" className="text-sm text-fg-secondary">
|
|
24
|
+
About this channel.
|
|
25
|
+
</Tabs.Content>
|
|
26
|
+
</Tabs.Root>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/demos/theme.tsx
|
|
4
|
+
import { ThemeProvider, ThemeToggle } from "@krizaka/ui/theme";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
function ThemeDemo() {
|
|
7
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(ThemeToggle, { label: "Change the theme" }) });
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
ThemeDemo as default
|
|
11
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/** `defaultOpen`: the toasts show at once and stay (the screenshot); otherwise the button shows them. */
|
|
4
|
+
declare function ToastDemo({ defaultOpen }: {
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
}): react.JSX.Element;
|
|
7
|
+
|
|
8
|
+
export { ToastDemo as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/demos/toast.tsx
|
|
4
|
+
import { Button } from "@krizaka/ui/button";
|
|
5
|
+
import { toast, Toaster } from "@krizaka/ui/toast";
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var toasterId = "tones";
|
|
9
|
+
var show = () => {
|
|
10
|
+
toast("Draft saved", { toasterId, description: "Autosaved a moment ago." });
|
|
11
|
+
toast.success("Payment received", { toasterId, description: "\u20AC12.00 from @maya." });
|
|
12
|
+
toast.warning("Your balance is low", { toasterId, description: "Two tips left before a top-up." });
|
|
13
|
+
toast.error("Upload failed", { toasterId, description: "The connection dropped at 64 %.", action: { label: "Retry", onClick: () => {
|
|
14
|
+
} } });
|
|
15
|
+
};
|
|
16
|
+
function ToastDemo({ defaultOpen = false }) {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!defaultOpen) return;
|
|
19
|
+
toast.dismiss();
|
|
20
|
+
show();
|
|
21
|
+
}, [defaultOpen]);
|
|
22
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23
|
+
/* @__PURE__ */ jsx(Toaster, { id: toasterId, label: "Notifications", closeLabel: "Dismiss", position: "bottom-right", expand: true, visibleToasts: 4, duration: defaultOpen ? Number.POSITIVE_INFINITY : void 0 }),
|
|
24
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: show, children: "Show again" })
|
|
25
|
+
] });
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
ToastDemo as default
|
|
29
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@krizaka/ui/button";
|
|
4
|
+
import { toast, Toaster } from "@krizaka/ui/toast";
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
|
|
7
|
+
// Each toaster has its own `id`: the toasts of this demo stay in this demo's toaster.
|
|
8
|
+
const toasterId = "tones";
|
|
9
|
+
const show = () => {
|
|
10
|
+
toast("Draft saved", { toasterId, description: "Autosaved a moment ago." });
|
|
11
|
+
toast.success("Payment received", { toasterId, description: "€12.00 from @maya." });
|
|
12
|
+
toast.warning("Your balance is low", { toasterId, description: "Two tips left before a top-up." });
|
|
13
|
+
toast.error("Upload failed", { toasterId, description: "The connection dropped at 64 %.", action: { label: "Retry", onClick: () => {} } });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** `defaultOpen`: the toasts show at once and stay (the screenshot); otherwise the button shows them. */
|
|
17
|
+
export default function ToastDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!defaultOpen) return;
|
|
20
|
+
toast.dismiss();
|
|
21
|
+
show();
|
|
22
|
+
}, [defaultOpen]);
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<Toaster id={toasterId} label="Notifications" closeLabel="Dismiss" position="bottom-right" expand visibleToasts={4} duration={defaultOpen ? Number.POSITIVE_INFINITY : undefined} />
|
|
26
|
+
<Button variant="outline" onClick={show}>
|
|
27
|
+
Show again
|
|
28
|
+
</Button>
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// registry/demos/tooltip.tsx
|
|
4
|
+
import { IconButton } from "@krizaka/ui/button";
|
|
5
|
+
import { Tooltip } from "@krizaka/ui/tooltip";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function TooltipDemo({ defaultOpen = false }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(Tooltip, { content: "Copy the link", defaultOpen, children: /* @__PURE__ */ jsx(IconButton, { label: "Copy", variant: "outline", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
|
|
9
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "12", height: "12", rx: "2" }),
|
|
10
|
+
/* @__PURE__ */ jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
|
|
11
|
+
] }) }) });
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
TooltipDemo as default
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IconButton } from "@krizaka/ui/button";
|
|
2
|
+
import { Tooltip } from "@krizaka/ui/tooltip";
|
|
3
|
+
|
|
4
|
+
export default function TooltipDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {
|
|
5
|
+
return (
|
|
6
|
+
<Tooltip content="Copy the link" defaultOpen={defaultOpen}>
|
|
7
|
+
<IconButton label="Copy" variant="outline">
|
|
8
|
+
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden>
|
|
9
|
+
<rect x="9" y="9" width="12" height="12" rx="2" />
|
|
10
|
+
<path d="M5 15V5a2 2 0 0 1 2-2h10" />
|
|
11
|
+
</svg>
|
|
12
|
+
</IconButton>
|
|
13
|
+
</Tooltip>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dialog",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`Dialog.*`, `Sheet`, `AlertDialog`, `dialog` (the slots). Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "dialog/alert-dialog.tsx",
|
|
8
|
+
"content": "// Client. A confirmation that interrupts: Radix AlertDialog (role=\"alertdialog\", no outside click, focus on Cancel).\n// `onConfirm` may be async: the dialog stays open with the confirm button loading until it settles, closes when it\n// resolves, and stays open when it rejects (the product shows the error).\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"../button/button\";\nimport { dialog } from \"./dialog\";\n\nconst s = dialog({ placement: \"center\", size: \"sm\" });\n\nexport type AlertDialogProps = {\n /** Open or closed (controlled), with `onOpenChange`. */\n open?: boolean;\n /** Open at first, uncontrolled. */\n defaultOpen?: boolean;\n /** Called when it opens or closes (never while `onConfirm` is pending). */\n onOpenChange?: (open: boolean) => void;\n /** The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton. */\n trigger?: React.ReactNode;\n /** The question asked (\"Delete this video?\"): the dialog's accessible name. */\n title: React.ReactNode;\n /** What the action does and what it costs: the dialog's description. */\n description?: React.ReactNode;\n /** More content between the description and the actions. */\n children?: React.ReactNode;\n /** The words of the confirm button — passed translated. */\n confirmLabel: string;\n /** The words of the cancel button (it takes the focus at open) — passed translated. */\n cancelLabel: string;\n /** `danger` for a destructive action (delete, leave, cancel a payment). */\n tone?: \"danger\" | \"primary\";\n /** Runs on confirm. A promise keeps the dialog open and the button loading until it settles. */\n onConfirm: () => void | Promise<unknown>;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n /** Classes of the dialog's content, merged last. */\n className?: string;\n};\n\nexport function AlertDialog({\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n description,\n children,\n confirmLabel,\n cancelLabel,\n tone = \"primary\",\n onConfirm,\n container,\n className,\n}: AlertDialogProps) {\n const [openState, setOpenState] = React.useState(defaultOpen);\n const [pending, setPending] = React.useState(false);\n const open = openProp ?? openState;\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setOpenState(next);\n onOpenChange?.(next);\n };\n\n const confirm = async () => {\n setPending(true);\n try {\n await onConfirm();\n setOpen(false);\n } catch {\n // Stays open: the product reports the failure (a toast, an inline error) and the person may retry.\n } finally {\n setPending(false);\n }\n };\n\n return (\n <AlertDialogPrimitive.Root open={open} onOpenChange={(next) => (pending ? undefined : setOpen(next))}>\n {trigger && <AlertDialogPrimitive.Trigger asChild>{trigger}</AlertDialogPrimitive.Trigger>}\n <AlertDialogPrimitive.Portal container={container}>\n <AlertDialogPrimitive.Overlay className={s.overlay()} />\n <AlertDialogPrimitive.Content\n data-tone={tone}\n aria-busy={pending || undefined}\n className={s.content({ className })}\n onEscapeKeyDown={(event) => pending && event.preventDefault()}\n >\n <div className={s.header({ className: \"pr-5\" })}>\n <AlertDialogPrimitive.Title className={s.title()}>{title}</AlertDialogPrimitive.Title>\n {description && <AlertDialogPrimitive.Description className={s.description()}>{description}</AlertDialogPrimitive.Description>}\n </div>\n {children && <div className={s.body()}>{children}</div>}\n <div className={s.footer()}>\n <AlertDialogPrimitive.Cancel asChild>\n <Button variant=\"ghost\" disabled={pending}>\n {cancelLabel}\n </Button>\n </AlertDialogPrimitive.Cancel>\n <Button variant={tone === \"danger\" ? \"danger\" : \"primary\"} loading={pending} onClick={confirm}>\n {confirmLabel}\n </Button>\n </div>\n </AlertDialogPrimitive.Content>\n </AlertDialogPrimitive.Portal>\n </AlertDialogPrimitive.Root>\n );\n}\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "dialog/dialog.tsx",
|
|
12
|
+
"content": "// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height).\n * @default \"center\"\n */\n placement?: DialogVariants[\"placement\"];\n /**\n * sm · md · lg: the width of the dialog.\n * @default \"md\"\n */\n size?: DialogVariants[\"size\"];\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <DialogPrimitive.Content\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "dialog/index.ts",
|
|
16
|
+
"content": "/** @krizaka/ui/dialog — `Dialog.*`, `Sheet`, `AlertDialog`, `dialog` (the slots). Client (\"use client\"). */\nexport { AlertDialog, type AlertDialogProps } from \"./alert-dialog\";\nexport {\n Dialog,\n dialog,\n DialogBody,\n DialogContent,\n type DialogContentProps,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n type DialogVariants,\n Sheet,\n type SheetProps,\n} from \"./dialog\";\n"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"dependencies": [
|
|
20
|
+
"radix-ui@^1.7.0",
|
|
21
|
+
"tailwind-variants@^3.3.1"
|
|
22
|
+
],
|
|
23
|
+
"registryDependencies": [
|
|
24
|
+
"button"
|
|
25
|
+
],
|
|
26
|
+
"demo": {
|
|
27
|
+
"path": "demos/dialog.tsx",
|
|
28
|
+
"content": "import { Button } from \"@krizaka/ui/button\";\nimport { Dialog } from \"@krizaka/ui/dialog\";\nimport { Field, Input } from \"@krizaka/ui/field\";\n\nexport default function DialogDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <Dialog.Root defaultOpen={defaultOpen}>\n <Dialog.Trigger asChild>\n <Button>Open</Button>\n </Dialog.Trigger>\n <Dialog.Content closeLabel=\"Close\">\n <Dialog.Header>\n <Dialog.Title>Invite a collaborator</Dialog.Title>\n <Dialog.Description>They get access to the drafts of this channel.</Dialog.Description>\n </Dialog.Header>\n <Dialog.Body>\n <Field.Root>\n <Field.Label htmlFor=\"invite-email\">Email</Field.Label>\n <Input id=\"invite-email\" type=\"email\" placeholder=\"name@example.com\" />\n </Field.Root>\n </Dialog.Body>\n <Dialog.Footer>\n <Dialog.Close asChild>\n <Button variant=\"ghost\">Cancel</Button>\n </Dialog.Close>\n <Button variant=\"primary\">Send the invitation</Button>\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog.Root>\n );\n}\n"
|
|
29
|
+
},
|
|
30
|
+
"props": [
|
|
31
|
+
{
|
|
32
|
+
"component": "AlertDialog",
|
|
33
|
+
"description": "",
|
|
34
|
+
"props": [
|
|
35
|
+
{
|
|
36
|
+
"name": "cancelLabel",
|
|
37
|
+
"type": "string",
|
|
38
|
+
"default": null,
|
|
39
|
+
"description": "The words of the cancel button (it takes the focus at open) — passed translated.",
|
|
40
|
+
"required": true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "confirmLabel",
|
|
44
|
+
"type": "string",
|
|
45
|
+
"default": null,
|
|
46
|
+
"description": "The words of the confirm button — passed translated.",
|
|
47
|
+
"required": true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "onConfirm",
|
|
51
|
+
"type": "() => void | Promise<unknown>",
|
|
52
|
+
"default": null,
|
|
53
|
+
"description": "Runs on confirm. A promise keeps the dialog open and the button loading until it settles.",
|
|
54
|
+
"required": true
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "title",
|
|
58
|
+
"type": "ReactNode",
|
|
59
|
+
"default": null,
|
|
60
|
+
"description": "The question asked (\"Delete this video?\"): the dialog's accessible name.",
|
|
61
|
+
"required": true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "children",
|
|
65
|
+
"type": "ReactNode",
|
|
66
|
+
"default": null,
|
|
67
|
+
"description": "More content between the description and the actions.",
|
|
68
|
+
"required": false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "className",
|
|
72
|
+
"type": "string",
|
|
73
|
+
"default": null,
|
|
74
|
+
"description": "Classes of the dialog's content, merged last.",
|
|
75
|
+
"required": false
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "container",
|
|
79
|
+
"type": "HTMLElement | null",
|
|
80
|
+
"default": null,
|
|
81
|
+
"description": "The element the portal renders into (default: document.body).",
|
|
82
|
+
"required": false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "defaultOpen",
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"default": "false",
|
|
88
|
+
"description": "Open at first, uncontrolled.",
|
|
89
|
+
"required": false
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "description",
|
|
93
|
+
"type": "ReactNode",
|
|
94
|
+
"default": null,
|
|
95
|
+
"description": "What the action does and what it costs: the dialog's description.",
|
|
96
|
+
"required": false
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "onOpenChange",
|
|
100
|
+
"type": "((open: boolean) => void)",
|
|
101
|
+
"default": null,
|
|
102
|
+
"description": "Called when it opens or closes (never while `onConfirm` is pending).",
|
|
103
|
+
"required": false
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "open",
|
|
107
|
+
"type": "boolean",
|
|
108
|
+
"default": null,
|
|
109
|
+
"description": "Open or closed (controlled), with `onOpenChange`.",
|
|
110
|
+
"required": false
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "tone",
|
|
114
|
+
"type": "\"danger\" | \"primary\"",
|
|
115
|
+
"default": "primary",
|
|
116
|
+
"description": "`danger` for a destructive action (delete, leave, cancel a payment).",
|
|
117
|
+
"required": false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "trigger",
|
|
121
|
+
"type": "ReactNode",
|
|
122
|
+
"default": null,
|
|
123
|
+
"description": "The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton.",
|
|
124
|
+
"required": false
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"component": "DialogContent",
|
|
130
|
+
"description": "The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`.",
|
|
131
|
+
"props": [
|
|
132
|
+
{
|
|
133
|
+
"name": "closeLabel",
|
|
134
|
+
"type": "string",
|
|
135
|
+
"default": null,
|
|
136
|
+
"description": "The accessible name of the close button — passed translated.",
|
|
137
|
+
"required": false
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "container",
|
|
141
|
+
"type": "HTMLElement | null",
|
|
142
|
+
"default": null,
|
|
143
|
+
"description": "The element the portal renders into (default: document.body).",
|
|
144
|
+
"required": false
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "dismissible",
|
|
148
|
+
"type": "boolean",
|
|
149
|
+
"default": "true",
|
|
150
|
+
"description": "`false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\nthrough `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.",
|
|
151
|
+
"required": false
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "hideClose",
|
|
155
|
+
"type": "boolean",
|
|
156
|
+
"default": null,
|
|
157
|
+
"description": "Leaves the close button out (the content brings its own way out, or none: see `dismissible`).",
|
|
158
|
+
"required": false
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "placement",
|
|
162
|
+
"type": "\"center\" | \"bottom\" | \"right\"",
|
|
163
|
+
"default": "center",
|
|
164
|
+
"description": "center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height).",
|
|
165
|
+
"required": false
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "size",
|
|
169
|
+
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
170
|
+
"default": "md",
|
|
171
|
+
"description": "sm · md · lg: the width of the dialog.",
|
|
172
|
+
"required": false
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"component": "Sheet",
|
|
178
|
+
"description": "A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`.",
|
|
179
|
+
"props": [
|
|
180
|
+
{
|
|
181
|
+
"name": "closeLabel",
|
|
182
|
+
"type": "string",
|
|
183
|
+
"default": null,
|
|
184
|
+
"description": "The accessible name of the close button — passed translated.",
|
|
185
|
+
"required": false
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "container",
|
|
189
|
+
"type": "HTMLElement | null",
|
|
190
|
+
"default": null,
|
|
191
|
+
"description": "The element the portal renders into (default: document.body).",
|
|
192
|
+
"required": false
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "dismissible",
|
|
196
|
+
"type": "boolean",
|
|
197
|
+
"default": null,
|
|
198
|
+
"description": "`false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\nthrough `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.",
|
|
199
|
+
"required": false
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"name": "hideClose",
|
|
203
|
+
"type": "boolean",
|
|
204
|
+
"default": null,
|
|
205
|
+
"description": "Leaves the close button out (the content brings its own way out, or none: see `dismissible`).",
|
|
206
|
+
"required": false
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"name": "size",
|
|
210
|
+
"type": "\"sm\" | \"md\" | \"lg\"",
|
|
211
|
+
"default": "md",
|
|
212
|
+
"description": "sm · md · lg: the width of the dialog.",
|
|
213
|
+
"required": false
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dropdown-menu",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`DropdownMenu.*`, `menu` (the slots). Client (\"use client\").",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "dropdown-menu/dropdown-menu.tsx",
|
|
8
|
+
"content": "// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The danger token is too light for small text on a light surface: the label stays a text role, the icon and the\n // highlight carry the danger.\n tone: { default: {}, danger: { item: \"data-highlighted:bg-danger/15 [&>svg]:text-danger\" } },\n },\n defaultVariants: { tone: \"default\" },\n});\nconst s = menu();\n\nexport type DropdownMenuContentProps = React.ComponentProps<typeof MenuPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function DropdownMenuContent({ sideOffset = 6, align = \"end\", collisionPadding = 8, container, className, ...props }: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal container={container}>\n <MenuPrimitive.Content sideOffset={sideOffset} align={align} collisionPadding={collisionPadding} className={s.content({ className })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\nexport type DropdownMenuItemProps = React.ComponentProps<typeof MenuPrimitive.Item> & {\n /** `danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible. */\n tone?: \"default\" | \"danger\";\n};\n\n/** An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes). */\nexport function DropdownMenuItem({ tone, className, ...props }: DropdownMenuItemProps) {\n return <MenuPrimitive.Item data-tone={tone ?? \"default\"} className={menu({ tone }).item({ className })} {...props} />;\n}\n\n/** A toggled option: `checked` + `onCheckedChange`. */\nexport function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Label>) {\n return <MenuPrimitive.Label className={s.label({ className })} {...props} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {\n return <MenuPrimitive.Separator className={s.separator({ className })} {...props} />;\n}\n\n/** `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const DropdownMenu = {\n Root: MenuPrimitive.Root,\n Trigger: MenuPrimitive.Trigger,\n Content: DropdownMenuContent,\n Item: DropdownMenuItem,\n CheckboxItem: DropdownMenuCheckboxItem,\n Label: DropdownMenuLabel,\n Separator: DropdownMenuSeparator,\n Group: MenuPrimitive.Group,\n};\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "dropdown-menu/index.ts",
|
|
12
|
+
"content": "/** @krizaka/ui/dropdown-menu — `DropdownMenu.*`, `menu` (the slots). Client (\"use client\"). */\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n type DropdownMenuContentProps,\n DropdownMenuItem,\n type DropdownMenuItemProps,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n menu,\n} from \"./dropdown-menu\";\n"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "floating.ts",
|
|
16
|
+
"content": "// The one surface of everything that floats next to its trigger (popover, menu, tooltip): a raised surface, a border,\n// the large shadow and the `kz-pop` entrance from the side Radix measured (`--radix-*-transform-origin`).\nexport const floating =\n \"kz-pop z-50 rounded-lg border border-border-default bg-surface-2 text-fg shadow-lg outline-hidden\";\n"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"dependencies": [
|
|
20
|
+
"radix-ui@^1.7.0",
|
|
21
|
+
"tailwind-variants@^3.3.1"
|
|
22
|
+
],
|
|
23
|
+
"registryDependencies": [
|
|
24
|
+
"cn"
|
|
25
|
+
],
|
|
26
|
+
"demo": {
|
|
27
|
+
"path": "demos/dropdown-menu.tsx",
|
|
28
|
+
"content": "import { Button } from \"@krizaka/ui/button\";\nimport { DropdownMenu } from \"@krizaka/ui/dropdown-menu\";\n\nconst icon = (d: string) => (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d={d} />\n </svg>\n);\n\nexport default function DropdownMenuDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <DropdownMenu.Root defaultOpen={defaultOpen} modal={false}>\n <DropdownMenu.Trigger asChild>\n <Button variant=\"outline\">Actions</Button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"center\">\n <DropdownMenu.Label>Video</DropdownMenu.Label>\n <DropdownMenu.Item>{icon(\"M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z\")}Edit</DropdownMenu.Item>\n <DropdownMenu.Item>{icon(\"M4 12v8h16v-8M16 6l-4-4-4 4M12 2v13\")}Share</DropdownMenu.Item>\n <DropdownMenu.Item disabled>{icon(\"M12 3v12M7 10l5 5 5-5M5 21h14\")}Download</DropdownMenu.Item>\n <DropdownMenu.CheckboxItem checked>Pinned to the profile</DropdownMenu.CheckboxItem>\n <DropdownMenu.Separator />\n <DropdownMenu.Item tone=\"danger\">{icon(\"M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6\")}Delete</DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n"
|
|
29
|
+
},
|
|
30
|
+
"props": [
|
|
31
|
+
{
|
|
32
|
+
"component": "DropdownMenuContent",
|
|
33
|
+
"description": "",
|
|
34
|
+
"props": [
|
|
35
|
+
{
|
|
36
|
+
"name": "container",
|
|
37
|
+
"type": "HTMLElement | null",
|
|
38
|
+
"default": null,
|
|
39
|
+
"description": "The element the portal renders into (default: document.body).",
|
|
40
|
+
"required": false
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"component": "DropdownMenuItem",
|
|
46
|
+
"description": "An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes).",
|
|
47
|
+
"props": [
|
|
48
|
+
{
|
|
49
|
+
"name": "tone",
|
|
50
|
+
"type": "\"danger\" | \"default\"",
|
|
51
|
+
"default": null,
|
|
52
|
+
"description": "`danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible.",
|
|
53
|
+
"required": false
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"component": "DropdownMenuCheckboxItem",
|
|
59
|
+
"description": "A toggled option: `checked` + `onCheckedChange`.",
|
|
60
|
+
"props": []
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "empty-state",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"description": "`EmptyState`. Server-safe.",
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"path": "empty-state/empty-state.tsx",
|
|
8
|
+
"content": "// Server-safe. What a list, a search or a panel says when it has nothing to show — every word is a prop.\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const emptyState = tv({\n slots: {\n root: \"flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border-default px-6 py-10 text-center\",\n icon: \"flex h-12 w-12 items-center justify-center rounded-full bg-surface-2 text-fg-secondary [&>svg]:h-6 [&>svg]:w-6\",\n title: \"text-sm font-semibold text-fg\",\n description: \"max-w-sm text-sm text-fg-secondary\",\n action: \"mt-1 flex flex-wrap items-center justify-center gap-2\",\n },\n});\nconst s = emptyState();\n\nexport type EmptyStateProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** A decorative icon (hidden from assistive technology). */\n icon?: React.ReactNode;\n /** What is empty, in a few words (\"No videos yet\"). */\n title: React.ReactNode;\n /** Why, or what will fill it. */\n description?: React.ReactNode;\n /** What to do next: a Button, a link. */\n action?: React.ReactNode;\n};\n\nexport function EmptyState({ icon, title, description, action, className, ...props }: EmptyStateProps) {\n return (\n <div role=\"status\" className={s.root({ className })} {...props}>\n {icon && (\n <div aria-hidden className={s.icon()}>\n {icon}\n </div>\n )}\n <p className={s.title()}>{title}</p>\n {description && <p className={s.description()}>{description}</p>}\n {action && <div className={s.action()}>{action}</div>}\n </div>\n );\n}\n"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "empty-state/index.ts",
|
|
12
|
+
"content": "/** @krizaka/ui/empty-state — `EmptyState`. Server-safe. */\nexport { EmptyState, emptyState, type EmptyStateProps } from \"./empty-state\";\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"dependencies": [
|
|
16
|
+
"tailwind-variants@^3.3.1"
|
|
17
|
+
],
|
|
18
|
+
"registryDependencies": [],
|
|
19
|
+
"demo": {
|
|
20
|
+
"path": "demos/empty-state.tsx",
|
|
21
|
+
"content": "import { EmptyState } from \"@krizaka/ui/empty-state\";\n\nconst ICON = (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m10 9 5 3-5 3z\" />\n </svg>\n);\n\nexport default function EmptyStateDemo() {\n return <EmptyState icon={ICON} title=\"No videos yet\" description=\"The videos you publish appear here.\" />;\n}\n"
|
|
22
|
+
},
|
|
23
|
+
"props": [
|
|
24
|
+
{
|
|
25
|
+
"component": "EmptyState",
|
|
26
|
+
"description": "",
|
|
27
|
+
"props": [
|
|
28
|
+
{
|
|
29
|
+
"name": "title",
|
|
30
|
+
"type": "ReactNode",
|
|
31
|
+
"default": null,
|
|
32
|
+
"description": "What is empty, in a few words (\"No videos yet\").",
|
|
33
|
+
"required": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "action",
|
|
37
|
+
"type": "ReactNode",
|
|
38
|
+
"default": null,
|
|
39
|
+
"description": "What to do next: a Button, a link.",
|
|
40
|
+
"required": false
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "description",
|
|
44
|
+
"type": "ReactNode",
|
|
45
|
+
"default": null,
|
|
46
|
+
"description": "Why, or what will fill it.",
|
|
47
|
+
"required": false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "icon",
|
|
51
|
+
"type": "ReactNode",
|
|
52
|
+
"default": null,
|
|
53
|
+
"description": "A decorative icon (hidden from assistive technology).",
|
|
54
|
+
"required": false
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|