astro-awesomeness 0.4.0 → 0.5.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/dist/{cn-B3UhF_Ec.js → cn-C_hacsED.js} +1 -1
- package/dist/{cn-B3UhF_Ec.js.map → cn-C_hacsED.js.map} +1 -1
- package/dist/components/index.d.ts +25 -28
- package/dist/components/index.js +20 -19
- package/dist/components/index.js.map +1 -1
- package/dist/content/index.d.ts +17 -2
- package/dist/content/index.js +11 -2
- package/dist/content/index.js.map +1 -1
- package/dist/index-pemcLff6.d.ts +51 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +3 -3
- package/dist/{lib-BjqsRto0.js → lib-Dn4hRxdC.js} +37 -7
- package/dist/lib-Dn4hRxdC.js.map +1 -0
- package/package.json +19 -8
- package/src/astro/post-card.astro +1 -4
- package/src/components/command-menu.tsx +2 -4
- package/src/components/theme-toggle.tsx +1 -2
- package/src/components/ui/button.tsx +19 -21
- package/src/components/ui/command.tsx +1 -0
- package/src/components/ui/dialog.tsx +10 -11
- package/src/content/index.ts +18 -1
- package/src/lib/index.ts +2 -0
- package/src/lib/post-url.test.ts +50 -0
- package/src/lib/post-url.ts +55 -0
- package/dist/index-BAPPsft-.d.ts +0 -31
- package/dist/lib-BjqsRto0.js.map +0 -1
- /package/dist/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cn-
|
|
1
|
+
{"version":3,"file":"cn-C_hacsED.js","names":[],"sources":["../src/lib/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nconst cn = (...inputs: Array<ClassValue>) => twMerge(clsx(inputs));\n\nexport { cn };\n"],"mappings":";;;AAGA,MAAM,MAAM,GAAG,WAA8B,QAAQ,KAAK,MAAM,CAAC"}
|
|
@@ -1,42 +1,39 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { Dialog as Dialog$1 } from "radix-ui";
|
|
4
3
|
import { Command as Command$1 } from "cmdk";
|
|
4
|
+
import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
|
|
5
5
|
|
|
6
6
|
//#region src/components/ui/button.d.ts
|
|
7
7
|
declare const buttonVariants: (props?: ({
|
|
8
8
|
size?: "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | undefined;
|
|
9
9
|
variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined;
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
|
-
declare const Button: ({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
18
|
-
asChild?: boolean;
|
|
19
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
declare const Button: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react").ButtonProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "className"> & VariantProps<(props?: ({
|
|
12
|
+
size?: "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | undefined;
|
|
13
|
+
variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined;
|
|
14
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & {
|
|
15
|
+
className?: string;
|
|
16
|
+
} & React.RefAttributes<HTMLElement>>;
|
|
20
17
|
//#endregion
|
|
21
18
|
//#region src/components/ui/dialog.d.ts
|
|
22
19
|
declare function Dialog({
|
|
23
20
|
...props
|
|
24
|
-
}: React.ComponentProps<typeof Dialog$1.Root>):
|
|
21
|
+
}: React.ComponentProps<typeof Dialog$1.Root>): React.JSX.Element;
|
|
25
22
|
declare function DialogTrigger({
|
|
26
23
|
...props
|
|
27
|
-
}: React.ComponentProps<typeof Dialog$1.Trigger>):
|
|
24
|
+
}: React.ComponentProps<typeof Dialog$1.Trigger>): React.JSX.Element;
|
|
28
25
|
declare function DialogContent({
|
|
29
26
|
children,
|
|
30
27
|
className,
|
|
31
28
|
showCloseButton,
|
|
32
29
|
...props
|
|
33
|
-
}: React.ComponentProps<typeof Dialog$1.
|
|
30
|
+
}: React.ComponentProps<typeof Dialog$1.Popup> & {
|
|
34
31
|
showCloseButton?: boolean;
|
|
35
|
-
}):
|
|
32
|
+
}): React.JSX.Element;
|
|
36
33
|
declare function DialogHeader({
|
|
37
34
|
className,
|
|
38
35
|
...props
|
|
39
|
-
}: React.ComponentProps<"div">):
|
|
36
|
+
}: React.ComponentProps<"div">): React.JSX.Element;
|
|
40
37
|
declare function DialogFooter({
|
|
41
38
|
children,
|
|
42
39
|
className,
|
|
@@ -44,48 +41,48 @@ declare function DialogFooter({
|
|
|
44
41
|
...props
|
|
45
42
|
}: React.ComponentProps<"div"> & {
|
|
46
43
|
showCloseButton?: boolean;
|
|
47
|
-
}):
|
|
44
|
+
}): React.JSX.Element;
|
|
48
45
|
declare function DialogTitle({
|
|
49
46
|
className,
|
|
50
47
|
...props
|
|
51
|
-
}: React.ComponentProps<typeof Dialog$1.Title>):
|
|
48
|
+
}: React.ComponentProps<typeof Dialog$1.Title>): React.JSX.Element;
|
|
52
49
|
declare function DialogDescription({
|
|
53
50
|
className,
|
|
54
51
|
...props
|
|
55
|
-
}: React.ComponentProps<typeof Dialog$1.Description>):
|
|
52
|
+
}: React.ComponentProps<typeof Dialog$1.Description>): React.JSX.Element;
|
|
56
53
|
//#endregion
|
|
57
54
|
//#region src/components/ui/command.d.ts
|
|
58
55
|
declare function Command({
|
|
59
56
|
className,
|
|
60
57
|
...props
|
|
61
|
-
}: React.ComponentProps<typeof Command$1>):
|
|
58
|
+
}: React.ComponentProps<typeof Command$1>): React.JSX.Element;
|
|
62
59
|
declare function CommandInput({
|
|
63
60
|
className,
|
|
64
61
|
...props
|
|
65
|
-
}: React.ComponentProps<typeof Command$1.Input>):
|
|
62
|
+
}: React.ComponentProps<typeof Command$1.Input>): React.JSX.Element;
|
|
66
63
|
declare function CommandList({
|
|
67
64
|
className,
|
|
68
65
|
...props
|
|
69
|
-
}: React.ComponentProps<typeof Command$1.List>):
|
|
66
|
+
}: React.ComponentProps<typeof Command$1.List>): React.JSX.Element;
|
|
70
67
|
declare function CommandEmpty({
|
|
71
68
|
...props
|
|
72
|
-
}: React.ComponentProps<typeof Command$1.Empty>):
|
|
69
|
+
}: React.ComponentProps<typeof Command$1.Empty>): React.JSX.Element;
|
|
73
70
|
declare function CommandGroup({
|
|
74
71
|
className,
|
|
75
72
|
...props
|
|
76
|
-
}: React.ComponentProps<typeof Command$1.Group>):
|
|
73
|
+
}: React.ComponentProps<typeof Command$1.Group>): React.JSX.Element;
|
|
77
74
|
declare function CommandSeparator({
|
|
78
75
|
className,
|
|
79
76
|
...props
|
|
80
|
-
}: React.ComponentProps<typeof Command$1.Separator>):
|
|
77
|
+
}: React.ComponentProps<typeof Command$1.Separator>): React.JSX.Element;
|
|
81
78
|
declare function CommandItem({
|
|
82
79
|
className,
|
|
83
80
|
...props
|
|
84
|
-
}: React.ComponentProps<typeof Command$1.Item>):
|
|
81
|
+
}: React.ComponentProps<typeof Command$1.Item>): React.JSX.Element;
|
|
85
82
|
declare function CommandShortcut({
|
|
86
83
|
className,
|
|
87
84
|
...props
|
|
88
|
-
}: React.ComponentProps<"span">):
|
|
85
|
+
}: React.ComponentProps<"span">): React.JSX.Element;
|
|
89
86
|
//#endregion
|
|
90
87
|
//#region src/components/command-menu.d.ts
|
|
91
88
|
type CommandMenuItem = {
|
|
@@ -104,7 +101,7 @@ declare const CommandMenu: ({
|
|
|
104
101
|
groupHeading,
|
|
105
102
|
items,
|
|
106
103
|
placeholder
|
|
107
|
-
}: CommandMenuProps) => import("react
|
|
104
|
+
}: CommandMenuProps) => import("react").JSX.Element;
|
|
108
105
|
//#endregion
|
|
109
106
|
//#region src/components/theme-toggle.d.ts
|
|
110
107
|
type Props = {
|
|
@@ -112,7 +109,7 @@ type Props = {
|
|
|
112
109
|
};
|
|
113
110
|
declare const ThemeToggle: ({
|
|
114
111
|
ariaLabel
|
|
115
|
-
}: Props) => import("react
|
|
112
|
+
}: Props) => import("react").JSX.Element;
|
|
116
113
|
//#endregion
|
|
117
114
|
export { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandMenu, type CommandMenuItem, type CommandMenuProps, CommandSeparator, CommandShortcut, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, ThemeToggle, buttonVariants };
|
|
118
115
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/components/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { t as cn } from "../cn-
|
|
1
|
+
import { t as cn } from "../cn-C_hacsED.js";
|
|
2
|
+
import { Button as Button$1 } from "@base-ui/react/button";
|
|
2
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import * as React from "react";
|
|
3
5
|
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
4
|
-
import { Dialog as Dialog$1, Slot } from "radix-ui";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { Command as Command$1 } from "cmdk";
|
|
7
8
|
import { Moon, Search, SearchIcon, Sun, XIcon } from "lucide-react";
|
|
9
|
+
import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
|
|
8
10
|
//#region src/components/ui/button.tsx
|
|
9
11
|
const buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
10
12
|
defaultVariants: {
|
|
@@ -32,8 +34,8 @@ const buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
});
|
|
35
|
-
const Button = ({
|
|
36
|
-
return /* @__PURE__ */ jsx(
|
|
37
|
+
const Button = React.forwardRef(({ className, size = "default", variant = "default", ...props }, ref) => {
|
|
38
|
+
return /* @__PURE__ */ jsx(Button$1, {
|
|
37
39
|
className: cn(buttonVariants({
|
|
38
40
|
className,
|
|
39
41
|
size,
|
|
@@ -42,9 +44,11 @@ const Button = ({ asChild = false, className, size = "default", variant = "defau
|
|
|
42
44
|
"data-size": size,
|
|
43
45
|
"data-slot": "button",
|
|
44
46
|
"data-variant": variant,
|
|
47
|
+
ref,
|
|
45
48
|
...props
|
|
46
49
|
});
|
|
47
|
-
};
|
|
50
|
+
});
|
|
51
|
+
Button.displayName = "Button";
|
|
48
52
|
//#endregion
|
|
49
53
|
//#region src/components/ui/dialog.tsx
|
|
50
54
|
function Dialog({ ...props }) {
|
|
@@ -66,8 +70,8 @@ function DialogPortal({ ...props }) {
|
|
|
66
70
|
});
|
|
67
71
|
}
|
|
68
72
|
function DialogOverlay({ className, ...props }) {
|
|
69
|
-
return /* @__PURE__ */ jsx(Dialog$1.
|
|
70
|
-
className: cn("fixed inset-0 z-50 bg-black/50 data-[
|
|
73
|
+
return /* @__PURE__ */ jsx(Dialog$1.Backdrop, {
|
|
74
|
+
className: cn("fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0", className),
|
|
71
75
|
"data-slot": "dialog-overlay",
|
|
72
76
|
...props
|
|
73
77
|
});
|
|
@@ -75,13 +79,14 @@ function DialogOverlay({ className, ...props }) {
|
|
|
75
79
|
function DialogContent({ children, className, showCloseButton = true, ...props }) {
|
|
76
80
|
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
77
81
|
"data-slot": "dialog-portal",
|
|
78
|
-
children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.
|
|
79
|
-
className: cn("fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[
|
|
82
|
+
children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Popup, {
|
|
83
|
+
className: cn("fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg", className),
|
|
80
84
|
"data-slot": "dialog-content",
|
|
81
85
|
...props,
|
|
82
86
|
children: [children, showCloseButton && /* @__PURE__ */ jsxs(Dialog$1.Close, {
|
|
83
87
|
className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
84
88
|
"data-slot": "dialog-close",
|
|
89
|
+
type: "button",
|
|
85
90
|
children: [/* @__PURE__ */ jsx(XIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
86
91
|
className: "sr-only",
|
|
87
92
|
children: "Close"
|
|
@@ -103,11 +108,8 @@ function DialogFooter({ children, className, showCloseButton = false, ...props }
|
|
|
103
108
|
"data-slot": "dialog-footer",
|
|
104
109
|
...props,
|
|
105
110
|
children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, {
|
|
106
|
-
|
|
107
|
-
children:
|
|
108
|
-
variant: "outline",
|
|
109
|
-
children: "Close"
|
|
110
|
-
})
|
|
111
|
+
render: /* @__PURE__ */ jsx(Button, { variant: "outline" }),
|
|
112
|
+
children: "Close"
|
|
111
113
|
})]
|
|
112
114
|
});
|
|
113
115
|
}
|
|
@@ -207,13 +209,12 @@ const CommandMenu = ({ emptyMessage = "No results.", groupHeading = "Posts", ite
|
|
|
207
209
|
onOpenChange: setOpen,
|
|
208
210
|
open,
|
|
209
211
|
children: [/* @__PURE__ */ jsx(DialogTrigger, {
|
|
210
|
-
|
|
211
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
212
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
212
213
|
"aria-label": "Open search",
|
|
213
214
|
size: "icon",
|
|
214
|
-
variant: "ghost"
|
|
215
|
-
|
|
216
|
-
})
|
|
215
|
+
variant: "ghost"
|
|
216
|
+
}),
|
|
217
|
+
children: /* @__PURE__ */ jsx(Search, {})
|
|
217
218
|
}), /* @__PURE__ */ jsx(DialogContent, {
|
|
218
219
|
className: "p-0",
|
|
219
220
|
children: /* @__PURE__ */ jsxs(Command, { children: [/* @__PURE__ */ jsx(CommandInput, { placeholder }), /* @__PURE__ */ jsxs(CommandList, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: emptyMessage }), /* @__PURE__ */ jsx(CommandGroup, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/components/command-menu.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\nconst Button = ({\n asChild = false,\n className,\n size = \"default\",\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) => {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n {...props}\n />\n );\n};\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Content\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close asChild>\n <Button variant=\"outline\">Close</Button>\n </DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n}\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger asChild>\n <Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\">\n <Search />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n window.location.href = item.url;\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useState, useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n // useSyncExternalStore avoids hydration mismatch warnings while still letting\n // us read the actual system preference on the client.\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n const handleToggleTheme = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n window.localStorage.setItem(\"theme\", next);\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n setOverride(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleToggleTheme} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ButtonPrimitive","DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/components/command-menu.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\ntype ButtonProps = Omit<React.ComponentPropsWithoutRef<typeof ButtonPrimitive>, \"className\"> &\n VariantProps<typeof buttonVariants> & {\n className?: string;\n };\n\nconst Button = React.forwardRef<React.ComponentRef<typeof ButtonPrimitive>, ButtonProps>(\n ({ className, size = \"default\", variant = \"default\", ...props }, ref) => {\n return (\n <ButtonPrimitive\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) {\n return (\n <DialogPrimitive.Backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Popup> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Popup\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n type=\"button\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n children?: React.ReactNode;\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n}\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger render={<Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\" />}>\n <Search />\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n window.location.href = item.url;\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useState, useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n // useSyncExternalStore avoids hydration mismatch on system preference reads.\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n const handleToggleTheme = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n window.localStorage.setItem(\"theme\", next);\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n setOverride(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleToggleTheme} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":";;;;;;;;;;AAMA,MAAM,iBAAiB,IACrB,+bACA;CACE,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;GACJ,SAAS;GACT,MAAM;GACN,WAAW;GACX,WAAW;GACX,WAAW;GACX,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,SAAS;GACP,SAAS;GACT,aACE;GACF,OAAO;GACP,MAAM;GACN,SACE;GACF,WAAW;EACb;CACF;AACF,CACF;AAOA,MAAM,SAAS,MAAM,YAClB,EAAE,WAAW,OAAO,WAAW,UAAU,WAAW,GAAG,SAAS,QAAQ;CACvE,OACE,oBAACA,UAAD;EACE,WAAW,GAAG,eAAe;GAAE;GAAW;GAAM;EAAQ,CAAC,CAAC;EAC1D,aAAW;EACX,aAAU;EACV,gBAAc;EACT;EACL,GAAI;CACL,CAAA;AAEL,CACF;AACA,OAAO,cAAc;;;AClDrB,SAAS,OAAO,EAAE,GAAG,SAA4D;CAC/E,OAAO,oBAACC,SAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,SAAS,cAAc,EAAE,GAAG,SAA+D;CACzF,OAAO,oBAACA,SAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,aAAa,EAAE,GAAG,SAA8D;CACvF,OAAO,oBAACA,SAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ,CAAA;AACvE;AAMA,SAAS,cAAc,EACrB,WACA,GAAG,SACqD;CACxD,OACE,oBAACA,SAAgB,UAAjB;EACE,WAAW,GACT,kIACA,SACF;EACA,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,cAAc,EACrB,UACA,WACA,kBAAkB,MAClB,GAAG,SAGF;CACD,OACE,qBAAC,cAAD;EAAc,aAAU;YAAxB,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,qBAACA,SAAgB,OAAjB;GACE,WAAW,GACT,wVACA,SACF;GACA,aAAU;GACV,GAAI;aANN,CAQG,UACA,mBACC,qBAACA,SAAgB,OAAjB;IACE,WAAU;IACV,aAAU;IACV,MAAK;cAHP,CAKE,oBAAC,OAAD,CAAQ,CAAA,GACR,oBAAC,QAAD;KAAM,WAAU;eAAU;IAAW,CAAA,CAChB;KAEJ;IACX;;AAElB;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EACE,WAAW,GAAG,gDAAgD,SAAS;EACvE,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,EACpB,UACA,WACA,kBAAkB,OAClB,GAAG,SAGF;CACD,OACE,qBAAC,OAAD;EACE,WAAW,GAAG,0DAA0D,SAAS;EACjF,aAAU;EACV,GAAI;YAHN,CAKG,UACA,mBACC,oBAACA,SAAgB,OAAjB;GAAuB,QAAQ,oBAAC,QAAD,EAAQ,SAAQ,UAAW,CAAA;aAAG;EAA4B,CAAA,CAExF;;AAET;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,SAAgB,OAAjB;EACE,WAAW,GAAG,sCAAsC,SAAS;EAC7D,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,kBAAkB,EACzB,WACA,GAAG,SACwD;CAC3D,OACE,oBAACA,SAAgB,aAAjB;EACE,WAAW,GAAG,iCAAiC,SAAS;EACxD,aAAU;EACV,GAAI;CACL,CAAA;AAEL;;;ACxHA,SAAS,QAAQ,EAAE,WAAW,GAAG,SAAwD;CACvF,OACE,oBAACC,WAAD;EACE,WAAW,GACT,6FACA,SACF;EACA,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAkCA,SAAS,aAAa,EACpB,WACA,GAAG,SACmD;CACtD,OACE,qBAAC,OAAD;EAAK,WAAU;EAA4C,aAAU;YAArE,CACE,oBAAC,YAAD,EAAY,WAAU,6BAA8B,CAAA,GACpD,oBAACA,UAAiB,OAAlB;GACE,WAAW,GACT,4JACA,SACF;GACA,aAAU;GACV,GAAI;EACL,CAAA,CACE;;AAET;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,UAAiB,MAAlB;EACE,WAAW,GAAG,+DAA+D,SAAS;EACtF,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,EAAE,GAAG,SAA8D;CACvF,OACE,oBAACA,UAAiB,OAAlB;EACE,WAAU;EACV,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,aAAa,EACpB,WACA,GAAG,SACmD;CACtD,OACE,oBAACA,UAAiB,OAAlB;EACE,WAAW,GACT,0NACA,SACF;EACA,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,GAAG,SACuD;CAC1D,OACE,oBAACA,UAAiB,WAAlB;EACE,WAAW,GAAG,wBAAwB,SAAS;EAC/C,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,UAAiB,MAAlB;EACE,WAAW,GACT,uYACA,SACF;EACA,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,SAAuC;CAC9E,OACE,oBAAC,QAAD;EACE,WAAW,GAAG,yDAAyD,SAAS;EAChF,aAAU;EACV,GAAI;CACL,CAAA;AAEL;;;ACtHA,MAAM,eAAe,EACnB,eAAe,eACf,eAAe,SACf,OACA,cAAc,sBACQ;CACtB,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,QAAQ,MAAM,WAAW,MAAM,UAAU;IACzD,MAAM,eAAe;IACrB,SAAS,UAAU,CAAC,KAAK;GAC3B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa;GACX,SAAS,oBAAoB,WAAW,aAAa;EACvD;CACF,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,QAAD;EAAQ,cAAc;EAAe;YAArC,CACE,oBAAC,eAAD;GAAe,QAAQ,oBAAC,QAAD;IAAQ,cAAW;IAAc,MAAK;IAAO,SAAQ;GAAS,CAAA;aACnF,oBAAC,QAAD,CAAS,CAAA;EACI,CAAA,GACf,oBAAC,eAAD;GAAe,WAAU;aACvB,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,cAAD,EAA2B,YAAc,CAAA,GACzC,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,cAAD,EAAA,UAAe,aAA2B,CAAA,GAC1C,oBAAC,cAAD;IAAc,SAAS;cACpB,MAAM,KAAK,SACV,oBAAC,aAAD;KAEE,gBAAgB;MACd,OAAO,SAAS,OAAO,KAAK;KAC9B;KACA,OAAO,KAAK;eAEZ,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD,EAAA,UAAO,KAAK,MAAY,CAAA,GACvB,KAAK,cACJ,oBAAC,QAAD;OAAM,WAAU;iBAAiC,KAAK;MAAkB,CAAA,IACtE,IACD;;IACM,GAZN,KAAK,GAYC,CACd;GACW,CAAA,CACH,EAAA,CAAA,CACN,EAAA,CAAA;EACI,CAAA,CACT;;AAEZ;;;ACtEA,MAAM,uBAAqC;CACzC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,SAAS,OAAO,aAAa,QAAQ,OAAO;CAClD,IAAI,WAAW,WAAW,WAAW,QACnC,OAAO;CAET,OAAO;AACT;AAEA,MAAM,uBAAgC;CACpC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,OAAO,OAAO,WAAW,8BAA8B,CAAC,CAAC;AAC3D;AAEA,MAAM,wBAAwB,aAAyB;CACrD,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAEhB,MAAM,QAAQ,OAAO,WAAW,8BAA8B;CAC9D,MAAM,iBAAiB,UAAU,QAAQ;CACzC,aAAa;EACX,MAAM,oBAAoB,UAAU,QAAQ;CAC9C;AACF;AAMA,MAAM,eAAe,EAAE,YAAY,qBAA4B;CAE7D,MAAM,cAAc,qBAAqB,sBAAsB,sBAAsB,KAAK;CAC1F,MAAM,CAAC,UAAU,eAAe,eAA6B,eAAe,CAAC;CAC7E,MAAM,QAAe,aAAa,cAAc,SAAS;CAEzD,MAAM,0BAA0B;EAC9B,MAAM,OAAc,UAAU,SAAS,UAAU;EACjD,OAAO,aAAa,QAAQ,SAAS,IAAI;EACzC,SAAS,gBAAgB,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjE,YAAY,IAAI;CAClB;CAEA,OACE,oBAAC,QAAD;EAAQ,cAAY;EAAW,SAAS;EAAmB,MAAK;EAAO,SAAQ;YAC5E,UAAU,SAAS,oBAAC,KAAD,CAAM,CAAA,IAAI,oBAAC,MAAD,CAAO,CAAA;CAC/B,CAAA;AAEZ"}
|
package/dist/content/index.d.ts
CHANGED
|
@@ -2,16 +2,25 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
//#region src/content/index.d.ts
|
|
4
4
|
declare namespace index_d_exports {
|
|
5
|
-
export { Author, Post, Tag, authorSchema, postSchema, tagSchema };
|
|
5
|
+
export { Author, Post, Tag, authorSchema, notDraft, postSchema, tagSchema };
|
|
6
6
|
}
|
|
7
7
|
declare const postSchema: z.ZodObject<{
|
|
8
8
|
author: z.ZodOptional<z.ZodString>;
|
|
9
|
+
categories: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
10
|
cover: z.ZodOptional<z.ZodString>;
|
|
10
11
|
coverAlt: z.ZodOptional<z.ZodString>;
|
|
11
12
|
description: z.ZodString;
|
|
12
13
|
draft: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
heroImageHeight: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
heroImageUrl: z.ZodOptional<z.ZodURL>;
|
|
16
|
+
heroImageWidth: z.ZodOptional<z.ZodNumber>;
|
|
13
17
|
pubDate: z.ZodCoercedDate<unknown>;
|
|
18
|
+
seo: z.ZodOptional<z.ZodObject<{
|
|
19
|
+
canonical_url: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
14
21
|
series: z.ZodOptional<z.ZodString>;
|
|
22
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
23
|
+
status: z.ZodOptional<z.ZodString>;
|
|
15
24
|
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
16
25
|
title: z.ZodString;
|
|
17
26
|
updatedDate: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -29,6 +38,12 @@ declare const authorSchema: z.ZodObject<{
|
|
|
29
38
|
type Post = z.infer<typeof postSchema>;
|
|
30
39
|
type Tag = z.infer<typeof tagSchema>;
|
|
31
40
|
type Author = z.infer<typeof authorSchema>;
|
|
41
|
+
declare const notDraft: <T extends {
|
|
42
|
+
data: {
|
|
43
|
+
draft?: boolean;
|
|
44
|
+
status?: string;
|
|
45
|
+
};
|
|
46
|
+
}>(post: T) => boolean;
|
|
32
47
|
//#endregion
|
|
33
|
-
export { type Author, type Post, type Tag, authorSchema, postSchema, index_d_exports as t, tagSchema };
|
|
48
|
+
export { type Author, type Post, type Tag, authorSchema, notDraft, postSchema, index_d_exports as t, tagSchema };
|
|
34
49
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/content/index.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import { t as __exportAll } from "../chunk-
|
|
1
|
+
import { t as __exportAll } from "../chunk-D7D4PA-g.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/content/index.ts
|
|
4
4
|
var content_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
authorSchema: () => authorSchema,
|
|
6
|
+
notDraft: () => notDraft,
|
|
6
7
|
postSchema: () => postSchema,
|
|
7
8
|
tagSchema: () => tagSchema
|
|
8
9
|
});
|
|
9
10
|
const postSchema = z.object({
|
|
10
11
|
author: z.string().optional(),
|
|
12
|
+
categories: z.array(z.string()).default([]),
|
|
11
13
|
cover: z.string().optional(),
|
|
12
14
|
coverAlt: z.string().optional(),
|
|
13
15
|
description: z.string().min(1),
|
|
14
16
|
draft: z.boolean().default(false),
|
|
17
|
+
heroImageHeight: z.number().int().optional(),
|
|
18
|
+
heroImageUrl: z.url().optional(),
|
|
19
|
+
heroImageWidth: z.number().int().optional(),
|
|
15
20
|
pubDate: z.coerce.date(),
|
|
21
|
+
seo: z.object({ canonical_url: z.string().optional() }).optional(),
|
|
16
22
|
series: z.string().optional(),
|
|
23
|
+
slug: z.string().optional(),
|
|
24
|
+
status: z.string().optional(),
|
|
17
25
|
tags: z.array(z.string()).default([]),
|
|
18
26
|
title: z.string().min(1),
|
|
19
27
|
updatedDate: z.coerce.date().optional()
|
|
@@ -28,7 +36,8 @@ const authorSchema = z.object({
|
|
|
28
36
|
name: z.string().min(1),
|
|
29
37
|
url: z.url().optional()
|
|
30
38
|
});
|
|
39
|
+
const notDraft = (post) => post.data.status !== "DRAFT" && !post.data.draft;
|
|
31
40
|
//#endregion
|
|
32
|
-
export { authorSchema, postSchema, content_exports as t, tagSchema };
|
|
41
|
+
export { authorSchema, notDraft, postSchema, content_exports as t, tagSchema };
|
|
33
42
|
|
|
34
43
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().min(1),\n draft: z.boolean().default(false),\n pubDate: z.coerce.date(),\n series: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\nexport { authorSchema, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n categories: z.array(z.string()).default([]),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().min(1),\n draft: z.boolean().default(false),\n heroImageHeight: z.number().int().optional(),\n heroImageUrl: z.url().optional(),\n heroImageWidth: z.number().int().optional(),\n pubDate: z.coerce.date(),\n seo: z\n .object({\n canonical_url: z.string().optional(),\n })\n .optional(),\n series: z.string().optional(),\n slug: z.string().optional(),\n status: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\n// Predicate the 12 blog repos previously inlined ~72 times per repo\n// (`data.status !== \"DRAFT\" && !data.draft`). One source of truth so the\n// loader contract stays in sync with the consumer-side guard.\nconst notDraft = <T extends { data: { draft?: boolean; status?: string } }>(post: T): boolean =>\n post.data.status !== \"DRAFT\" && !post.data.draft;\n\nexport { authorSchema, notDraft, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":";;;;;;;;;AAEA,MAAM,aAAa,EAAE,OAAO;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC1C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC7B,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK;CAChC,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC3C,cAAc,EAAE,IAAI,CAAC,CAAC,SAAS;CAC/B,gBAAgB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC1C,SAAS,EAAE,OAAO,KAAK;CACvB,KAAK,EACF,OAAO,EACN,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS,EACrC,CAAC,CAAC,CACD,SAAS;CACZ,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACpC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC,SAAS;AACxC,CAAC;AAED,MAAM,YAAY,EAAE,OAAO;CACzB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS;CACzB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS;AACxB,CAAC;AASD,MAAM,YAAsE,SAC1E,KAAK,KAAK,WAAW,WAAW,CAAC,KAAK,KAAK"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ClassValue } from "clsx";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/cn.d.ts
|
|
4
|
+
declare const cn: (...inputs: Array<ClassValue>) => string;
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/lib/format-date.d.ts
|
|
7
|
+
declare const formatDate: (date: Date, locale?: string, options?: Intl.DateTimeFormatOptions) => string;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/lib/get-related-posts.d.ts
|
|
10
|
+
type HasTags = {
|
|
11
|
+
data: {
|
|
12
|
+
tags: Array<string>;
|
|
13
|
+
};
|
|
14
|
+
slug: string;
|
|
15
|
+
};
|
|
16
|
+
declare const getRelatedPosts: <T extends HasTags>(current: T, all: Array<T>, n: number) => T[];
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/lib/post-url.d.ts
|
|
19
|
+
type PostLike = {
|
|
20
|
+
data: {
|
|
21
|
+
categories?: Array<string>;
|
|
22
|
+
seo?: {
|
|
23
|
+
canonical_url?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
type PostParams = {
|
|
29
|
+
category: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
};
|
|
32
|
+
type PostUrlBuilder = {
|
|
33
|
+
postParams: (post: PostLike) => PostParams;
|
|
34
|
+
postUrl: (post: PostLike) => string;
|
|
35
|
+
};
|
|
36
|
+
declare const createPostUrl: (categorySlugMap: Record<string, string>, defaultCategory: string) => PostUrlBuilder;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/lib/reading-time.d.ts
|
|
39
|
+
declare const readingTime: (input: string) => {
|
|
40
|
+
minutes: number;
|
|
41
|
+
words: number;
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/lib/slugify.d.ts
|
|
45
|
+
declare const slugify: (input: string) => string;
|
|
46
|
+
declare namespace index_d_exports {
|
|
47
|
+
export { PostLike, PostParams, PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { PostParams as a, getRelatedPosts as c, PostLike as i, formatDate as l, slugify as n, PostUrlBuilder as o, readingTime as r, createPostUrl as s, index_d_exports as t, cn as u };
|
|
51
|
+
//# sourceMappingURL=index-pemcLff6.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Author, Post, Tag, t as index_d_exports } from "./content/index.js";
|
|
2
|
-
import { t as index_d_exports$1 } from "./index-
|
|
2
|
+
import { t as index_d_exports$1 } from "./index-pemcLff6.js";
|
|
3
3
|
export { type Author, type Post, type Tag, index_d_exports as content, index_d_exports$1 as lib };
|
package/dist/index.js
CHANGED
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { cn, formatDate, getRelatedPosts, readingTime, slugify };
|
|
1
|
+
import { a as PostParams, c as getRelatedPosts, i as PostLike, l as formatDate, n as slugify, o as PostUrlBuilder, r as readingTime, s as createPostUrl, u as cn } from "../index-pemcLff6.js";
|
|
2
|
+
export { type PostLike, type PostParams, type PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
package/dist/lib/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as cn } from "../cn-
|
|
2
|
-
import { a as
|
|
3
|
-
export { cn, formatDate, getRelatedPosts, readingTime, slugify };
|
|
1
|
+
import { t as cn } from "../cn-C_hacsED.js";
|
|
2
|
+
import { a as getRelatedPosts, i as slugify, n as readingTime, o as formatDate, r as createPostUrl } from "../lib-Dn4hRxdC.js";
|
|
3
|
+
export { cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-
|
|
2
|
-
import { t as cn } from "./cn-
|
|
1
|
+
import { t as __exportAll } from "./chunk-D7D4PA-g.js";
|
|
2
|
+
import { t as cn } from "./cn-C_hacsED.js";
|
|
3
3
|
//#region src/lib/format-date.ts
|
|
4
4
|
const defaultOptions = {
|
|
5
5
|
day: "numeric",
|
|
@@ -38,6 +38,38 @@ const getRelatedPosts = (current, all, n) => {
|
|
|
38
38
|
return scored.toSorted((x, y) => y.score - x.score).slice(0, n).map(({ post }) => post);
|
|
39
39
|
};
|
|
40
40
|
//#endregion
|
|
41
|
+
//#region src/lib/slugify.ts
|
|
42
|
+
const slugify = (input) => input.normalize("NFD").replaceAll(/* @__PURE__ */ new RegExp("[̀-ͯ]", "gv"), "").toLowerCase().replaceAll(/* @__PURE__ */ new RegExp("[^a-z0-9\\s\\-]", "gv"), "").trim().replaceAll(/* @__PURE__ */ new RegExp("[\\s\\-]+", "gv"), "-").replaceAll(/* @__PURE__ */ new RegExp("^-+|-+$", "gv"), "");
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/lib/post-url.ts
|
|
45
|
+
const createPostUrl = (categorySlugMap, defaultCategory) => {
|
|
46
|
+
const postParams = (post) => {
|
|
47
|
+
const canonical = post.data.seo?.canonical_url;
|
|
48
|
+
if (canonical) try {
|
|
49
|
+
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
50
|
+
const category = segments.at(-2);
|
|
51
|
+
const slug = segments.at(-1);
|
|
52
|
+
if (category && slug) return {
|
|
53
|
+
category,
|
|
54
|
+
slug
|
|
55
|
+
};
|
|
56
|
+
} catch {}
|
|
57
|
+
const first = post.data.categories?.at(0);
|
|
58
|
+
return {
|
|
59
|
+
category: first ? categorySlugMap[first] ?? slugify(first) : defaultCategory,
|
|
60
|
+
slug: post.id
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const postUrl = (post) => {
|
|
64
|
+
const { category, slug } = postParams(post);
|
|
65
|
+
return `/${category}/${slug}/`;
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
postParams,
|
|
69
|
+
postUrl
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
41
73
|
//#region src/lib/reading-time.ts
|
|
42
74
|
const WORDS_PER_MINUTE = 200;
|
|
43
75
|
const readingTime = (input) => {
|
|
@@ -49,18 +81,16 @@ const readingTime = (input) => {
|
|
|
49
81
|
};
|
|
50
82
|
};
|
|
51
83
|
//#endregion
|
|
52
|
-
//#region src/lib/slugify.ts
|
|
53
|
-
const slugify = (input) => input.normalize("NFD").replaceAll(/* @__PURE__ */ new RegExp("[̀-ͯ]", "gv"), "").toLowerCase().replaceAll(/* @__PURE__ */ new RegExp("[^a-z0-9\\s\\-]", "gv"), "").trim().replaceAll(/* @__PURE__ */ new RegExp("[\\s\\-]+", "gv"), "-").replaceAll(/* @__PURE__ */ new RegExp("^-+|-+$", "gv"), "");
|
|
54
|
-
//#endregion
|
|
55
84
|
//#region src/lib/index.ts
|
|
56
85
|
var lib_exports = /* @__PURE__ */ __exportAll({
|
|
57
86
|
cn: () => cn,
|
|
87
|
+
createPostUrl: () => createPostUrl,
|
|
58
88
|
formatDate: () => formatDate,
|
|
59
89
|
getRelatedPosts: () => getRelatedPosts,
|
|
60
90
|
readingTime: () => readingTime,
|
|
61
91
|
slugify: () => slugify
|
|
62
92
|
});
|
|
63
93
|
//#endregion
|
|
64
|
-
export {
|
|
94
|
+
export { getRelatedPosts as a, slugify as i, readingTime as n, formatDate as o, createPostUrl as r, lib_exports as t };
|
|
65
95
|
|
|
66
|
-
//# sourceMappingURL=lib-
|
|
96
|
+
//# sourceMappingURL=lib-Dn4hRxdC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-Dn4hRxdC.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/reading-time.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {\n const key = `${locale}:${JSON.stringify(options)}`;\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (\n date: Date,\n locale: string = \"en-US\",\n options: Intl.DateTimeFormatOptions = defaultOptions,\n) => getFormatter(locale, { ...defaultOptions, ...options }).format(date);\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; slug: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.slug === current.slug) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","import { slugify } from \"./slugify\";\n\ntype PostLike = {\n data: {\n categories?: Array<string>;\n seo?: { canonical_url?: string };\n };\n id: string;\n};\n\ntype PostParams = { category: string; slug: string };\n\ntype PostUrlBuilder = {\n postParams: (post: PostLike) => PostParams;\n postUrl: (post: PostLike) => string;\n};\n\n// Per-blog URL builder. Each blog supplies its own pt-BR -> slug map\n// (`category-slugs.json`) and the catch-all category used when the post has\n// no categories. The canonical_url short-circuit lets WordPress migrations\n// keep their original URL pair while everything else falls back to a slugified\n// derived category + Astro entry id.\nconst createPostUrl = (\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n): PostUrlBuilder => {\n const postParams = (post: PostLike): PostParams => {\n const canonical = post.data.seo?.canonical_url;\n if (canonical) {\n try {\n const segments = new URL(canonical).pathname.split(\"/\").filter(Boolean);\n const category = segments.at(-2);\n const slug = segments.at(-1);\n if (category && slug) {\n return { category, slug };\n }\n } catch {\n // Malformed canonical_url — fall through to category-derived logic.\n }\n }\n const first = post.data.categories?.at(0);\n const category = first ? (categorySlugMap[first] ?? slugify(first)) : defaultCategory;\n return { category, slug: post.id };\n };\n\n const postUrl = (post: PostLike): string => {\n const { category, slug } = postParams(post);\n return `/${category}/${slug}/`;\n };\n\n return { postParams, postUrl };\n};\n\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { createPostUrl };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport type { PostLike, PostParams, PostUrlBuilder } from \"./post-url\";\nexport { createPostUrl } from \"./post-url\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\n"],"mappings":";;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,QAAgB,YAAwC;CAC5E,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,UAAU,OAAO;CAC/C,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cACJ,MACA,SAAiB,SACjB,UAAsC,mBACnC,aAAa,QAAQ;CAAE,GAAG;CAAgB,GAAG;AAAQ,CAAC,CAAC,CAAC,OAAO,IAAI;;;ACrBxE,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,SAAS,QAAQ,MACxB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACrC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,EAAE,WAAW,IAAI;AAC3B;;;ACvBA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,CAAC,CAChB,2BAAW,IAAA,OAAA,SAAA,IAAQ,GAAG,EAAE,CAAC,CACzB,YAAY,CAAC,CACb,2BAAW,IAAA,OAAA,mBAAA,IAAgB,GAAG,EAAE,CAAC,CACjC,KAAK,CAAC,CACN,2BAAW,IAAA,OAAA,aAAA,IAAU,GAAG,GAAG,CAAC,CAC5B,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE;;;ACc/B,MAAM,iBACJ,iBACA,oBACmB;CACnB,MAAM,cAAc,SAA+B;EACjD,MAAM,YAAY,KAAK,KAAK,KAAK;EACjC,IAAI,WACF,IAAI;GACF,MAAM,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;GACtE,MAAM,WAAW,SAAS,GAAG,EAAE;GAC/B,MAAM,OAAO,SAAS,GAAG,EAAE;GAC3B,IAAI,YAAY,MACd,OAAO;IAAE;IAAU;GAAK;EAE5B,QAAQ,CAER;EAEF,MAAM,QAAQ,KAAK,KAAK,YAAY,GAAG,CAAC;EAExC,OAAO;GAAE,UADQ,QAAS,gBAAgB,UAAU,QAAQ,KAAK,IAAK;GACnD,MAAM,KAAK;EAAG;CACnC;CAEA,MAAM,WAAW,SAA2B;EAC1C,MAAM,EAAE,UAAU,SAAS,WAAW,IAAI;EAC1C,OAAO,IAAI,SAAS,GAAG,KAAK;CAC9B;CAEA,OAAO;EAAE;EAAY;CAAQ;AAC/B;;;ACnDA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE,CAAC,CAC3B,2BAAW,IAAA,OAAA,cAAA,IAAY,GAAG,GAAG,CAAC,CAC9B,2BAAW,IAAA,OAAA,QAAA,IAAM,GAAG,GAAG,CAAC,CACxB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-awesomeness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Astro 6 blog theme with shadcn primitives and a Tailwind v4 preset.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -10,11 +10,19 @@
|
|
|
10
10
|
"tailwindcss"
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/pedroapfilho/astro-theme-awesomeness.git",
|
|
16
|
+
"directory": "packages/astro-awesomeness"
|
|
17
|
+
},
|
|
13
18
|
"files": [
|
|
14
19
|
"dist",
|
|
15
20
|
"src"
|
|
16
21
|
],
|
|
17
22
|
"type": "module",
|
|
23
|
+
"sideEffects": [
|
|
24
|
+
"*.css"
|
|
25
|
+
],
|
|
18
26
|
"exports": {
|
|
19
27
|
".": {
|
|
20
28
|
"types": "./dist/index.d.ts",
|
|
@@ -40,26 +48,29 @@
|
|
|
40
48
|
},
|
|
41
49
|
"./styles.css": "./dist/styles/globals.css"
|
|
42
50
|
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
43
54
|
"dependencies": {
|
|
55
|
+
"@base-ui/react": "^1.5.0",
|
|
44
56
|
"class-variance-authority": "^0.7.1",
|
|
45
57
|
"clsx": "^2.1.1",
|
|
46
58
|
"cmdk": "^1.1.1",
|
|
47
|
-
"lucide-react": "^1.
|
|
48
|
-
"radix-ui": "^1.4.3",
|
|
59
|
+
"lucide-react": "^1.17.0",
|
|
49
60
|
"tailwind-merge": "^3.6.0",
|
|
50
61
|
"tw-animate-css": "^1.4.0",
|
|
51
62
|
"zod": "^4.4.3"
|
|
52
63
|
},
|
|
53
64
|
"devDependencies": {
|
|
54
65
|
"@tailwindcss/postcss": "^4.3.0",
|
|
55
|
-
"@types/node": "^24.
|
|
56
|
-
"@types/react": "^19.2.
|
|
66
|
+
"@types/node": "^24.13.1",
|
|
67
|
+
"@types/react": "^19.2.17",
|
|
57
68
|
"@types/react-dom": "^19.2.3",
|
|
58
|
-
"@vitest/coverage-v8": "^4.1.
|
|
59
|
-
"happy-dom": "^20.9.0",
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
60
70
|
"tailwindcss": "^4.3.0",
|
|
61
71
|
"typescript": "^6.0.3",
|
|
62
|
-
"vitest": "^4.1.
|
|
72
|
+
"vitest": "^4.1.8",
|
|
73
|
+
"@repo/config-vitest": "0.0.0",
|
|
63
74
|
"@repo/typescript-config": "0.0.0"
|
|
64
75
|
},
|
|
65
76
|
"peerDependencies": {
|
|
@@ -28,10 +28,7 @@ const href = `/blog/${post.id}`;
|
|
|
28
28
|
</article>
|
|
29
29
|
|
|
30
30
|
<style>
|
|
31
|
-
/*
|
|
32
|
-
three remain eagerly rendered so above-the-fold paint isn't blocked by
|
|
33
|
-
visibility containment evaluation. `contain-intrinsic-size: auto …`
|
|
34
|
-
lets the browser cache real measurements after first render. */
|
|
31
|
+
/* Keep first 3 eager so above-the-fold paint isn't gated on containment checks. */
|
|
35
32
|
.post-card:nth-child(n + 4) {
|
|
36
33
|
content-visibility: auto;
|
|
37
34
|
contain-intrinsic-size: auto 200px;
|
|
@@ -44,10 +44,8 @@ const CommandMenu = ({
|
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<Dialog onOpenChange={setOpen} open={open}>
|
|
47
|
-
<DialogTrigger
|
|
48
|
-
<
|
|
49
|
-
<Search />
|
|
50
|
-
</Button>
|
|
47
|
+
<DialogTrigger render={<Button aria-label="Open search" size="icon" variant="ghost" />}>
|
|
48
|
+
<Search />
|
|
51
49
|
</DialogTrigger>
|
|
52
50
|
<DialogContent className="p-0">
|
|
53
51
|
<Command>
|
|
@@ -39,8 +39,7 @@ type Props = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
const ThemeToggle = ({ ariaLabel = "Toggle theme" }: Props) => {
|
|
42
|
-
// useSyncExternalStore avoids hydration mismatch
|
|
43
|
-
// us read the actual system preference on the client.
|
|
42
|
+
// useSyncExternalStore avoids hydration mismatch on system preference reads.
|
|
44
43
|
const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);
|
|
45
44
|
const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());
|
|
46
45
|
const theme: Theme = override ?? (prefersDark ? "dark" : "light");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
1
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
3
|
import * as React from "react";
|
|
3
|
-
import { Slot } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn";
|
|
6
6
|
|
|
@@ -36,27 +36,25 @@ const buttonVariants = cva(
|
|
|
36
36
|
},
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
asChild = false,
|
|
41
|
-
className,
|
|
42
|
-
size = "default",
|
|
43
|
-
variant = "default",
|
|
44
|
-
...props
|
|
45
|
-
}: React.ComponentProps<"button"> &
|
|
39
|
+
type ButtonProps = Omit<React.ComponentPropsWithoutRef<typeof ButtonPrimitive>, "className"> &
|
|
46
40
|
VariantProps<typeof buttonVariants> & {
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
const Comp = asChild ? Slot.Root : "button";
|
|
41
|
+
className?: string;
|
|
42
|
+
};
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
44
|
+
const Button = React.forwardRef<React.ComponentRef<typeof ButtonPrimitive>, ButtonProps>(
|
|
45
|
+
({ className, size = "default", variant = "default", ...props }, ref) => {
|
|
46
|
+
return (
|
|
47
|
+
<ButtonPrimitive
|
|
48
|
+
className={cn(buttonVariants({ className, size, variant }))}
|
|
49
|
+
data-size={size}
|
|
50
|
+
data-slot="button"
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
ref={ref}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
Button.displayName = "Button";
|
|
61
59
|
|
|
62
60
|
export { Button, buttonVariants };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { XIcon } from "lucide-react";
|
|
3
|
-
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/cn";
|
|
6
6
|
import { Button } from "./button";
|
|
@@ -24,11 +24,11 @@ function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.C
|
|
|
24
24
|
function DialogOverlay({
|
|
25
25
|
className,
|
|
26
26
|
...props
|
|
27
|
-
}: React.ComponentProps<typeof DialogPrimitive.
|
|
27
|
+
}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) {
|
|
28
28
|
return (
|
|
29
|
-
<DialogPrimitive.
|
|
29
|
+
<DialogPrimitive.Backdrop
|
|
30
30
|
className={cn(
|
|
31
|
-
"fixed inset-0 z-50 bg-black/50 data-[
|
|
31
|
+
"fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0",
|
|
32
32
|
className,
|
|
33
33
|
)}
|
|
34
34
|
data-slot="dialog-overlay"
|
|
@@ -42,15 +42,15 @@ function DialogContent({
|
|
|
42
42
|
className,
|
|
43
43
|
showCloseButton = true,
|
|
44
44
|
...props
|
|
45
|
-
}: React.ComponentProps<typeof DialogPrimitive.
|
|
45
|
+
}: React.ComponentProps<typeof DialogPrimitive.Popup> & {
|
|
46
46
|
showCloseButton?: boolean;
|
|
47
47
|
}) {
|
|
48
48
|
return (
|
|
49
49
|
<DialogPortal data-slot="dialog-portal">
|
|
50
50
|
<DialogOverlay />
|
|
51
|
-
<DialogPrimitive.
|
|
51
|
+
<DialogPrimitive.Popup
|
|
52
52
|
className={cn(
|
|
53
|
-
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[
|
|
53
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg",
|
|
54
54
|
className,
|
|
55
55
|
)}
|
|
56
56
|
data-slot="dialog-content"
|
|
@@ -61,12 +61,13 @@ function DialogContent({
|
|
|
61
61
|
<DialogPrimitive.Close
|
|
62
62
|
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
63
63
|
data-slot="dialog-close"
|
|
64
|
+
type="button"
|
|
64
65
|
>
|
|
65
66
|
<XIcon />
|
|
66
67
|
<span className="sr-only">Close</span>
|
|
67
68
|
</DialogPrimitive.Close>
|
|
68
69
|
)}
|
|
69
|
-
</DialogPrimitive.
|
|
70
|
+
</DialogPrimitive.Popup>
|
|
70
71
|
</DialogPortal>
|
|
71
72
|
);
|
|
72
73
|
}
|
|
@@ -97,9 +98,7 @@ function DialogFooter({
|
|
|
97
98
|
>
|
|
98
99
|
{children}
|
|
99
100
|
{showCloseButton && (
|
|
100
|
-
<DialogPrimitive.Close
|
|
101
|
-
<Button variant="outline">Close</Button>
|
|
102
|
-
</DialogPrimitive.Close>
|
|
101
|
+
<DialogPrimitive.Close render={<Button variant="outline" />}>Close</DialogPrimitive.Close>
|
|
103
102
|
)}
|
|
104
103
|
</div>
|
|
105
104
|
);
|
package/src/content/index.ts
CHANGED
|
@@ -2,12 +2,23 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
const postSchema = z.object({
|
|
4
4
|
author: z.string().optional(),
|
|
5
|
+
categories: z.array(z.string()).default([]),
|
|
5
6
|
cover: z.string().optional(),
|
|
6
7
|
coverAlt: z.string().optional(),
|
|
7
8
|
description: z.string().min(1),
|
|
8
9
|
draft: z.boolean().default(false),
|
|
10
|
+
heroImageHeight: z.number().int().optional(),
|
|
11
|
+
heroImageUrl: z.url().optional(),
|
|
12
|
+
heroImageWidth: z.number().int().optional(),
|
|
9
13
|
pubDate: z.coerce.date(),
|
|
14
|
+
seo: z
|
|
15
|
+
.object({
|
|
16
|
+
canonical_url: z.string().optional(),
|
|
17
|
+
})
|
|
18
|
+
.optional(),
|
|
10
19
|
series: z.string().optional(),
|
|
20
|
+
slug: z.string().optional(),
|
|
21
|
+
status: z.string().optional(),
|
|
11
22
|
tags: z.array(z.string()).default([]),
|
|
12
23
|
title: z.string().min(1),
|
|
13
24
|
updatedDate: z.coerce.date().optional(),
|
|
@@ -29,5 +40,11 @@ type Post = z.infer<typeof postSchema>;
|
|
|
29
40
|
type Tag = z.infer<typeof tagSchema>;
|
|
30
41
|
type Author = z.infer<typeof authorSchema>;
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
// Predicate the 12 blog repos previously inlined ~72 times per repo
|
|
44
|
+
// (`data.status !== "DRAFT" && !data.draft`). One source of truth so the
|
|
45
|
+
// loader contract stays in sync with the consumer-side guard.
|
|
46
|
+
const notDraft = <T extends { data: { draft?: boolean; status?: string } }>(post: T): boolean =>
|
|
47
|
+
post.data.status !== "DRAFT" && !post.data.draft;
|
|
48
|
+
|
|
49
|
+
export { authorSchema, notDraft, postSchema, tagSchema };
|
|
33
50
|
export type { Author, Post, Tag };
|
package/src/lib/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { cn } from "./cn";
|
|
2
2
|
export { formatDate } from "./format-date";
|
|
3
3
|
export { getRelatedPosts } from "./get-related-posts";
|
|
4
|
+
export type { PostLike, PostParams, PostUrlBuilder } from "./post-url";
|
|
5
|
+
export { createPostUrl } from "./post-url";
|
|
4
6
|
export { readingTime } from "./reading-time";
|
|
5
7
|
export { slugify } from "./slugify";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createPostUrl } from "./post-url";
|
|
4
|
+
|
|
5
|
+
const map = { "Aluguel de Espaços": "aluguel-de-espacos", Arquitetura: "arquitetura" };
|
|
6
|
+
const DEFAULT = "sem-categoria";
|
|
7
|
+
|
|
8
|
+
describe("createPostUrl", () => {
|
|
9
|
+
const { postParams, postUrl } = createPostUrl(map, DEFAULT);
|
|
10
|
+
|
|
11
|
+
it("returns mapped category + post id when categories has a known entry", () => {
|
|
12
|
+
const post = { data: { categories: ["Arquitetura"] }, id: "casa-minimal" };
|
|
13
|
+
expect(postParams(post)).toEqual({ category: "arquitetura", slug: "casa-minimal" });
|
|
14
|
+
expect(postUrl(post)).toBe("/arquitetura/casa-minimal/");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("slugifies an unmapped first category", () => {
|
|
18
|
+
const post = { data: { categories: ["Arquitetura Moderna"] }, id: "x" };
|
|
19
|
+
expect(postParams(post)).toEqual({ category: "arquitetura-moderna", slug: "x" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses the default category when there are no categories", () => {
|
|
23
|
+
const post = { data: { categories: [] }, id: "orphan" };
|
|
24
|
+
expect(postUrl(post)).toBe("/sem-categoria/orphan/");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("honors seo.canonical_url over derived category when it has >=2 segments", () => {
|
|
28
|
+
const post = {
|
|
29
|
+
data: { categories: ["Arquitetura"], seo: { canonical_url: "https://x.com/foo/bar/" } },
|
|
30
|
+
id: "ignored",
|
|
31
|
+
};
|
|
32
|
+
expect(postParams(post)).toEqual({ category: "foo", slug: "bar" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("falls back to derived category when seo.canonical_url is malformed", () => {
|
|
36
|
+
const post = {
|
|
37
|
+
data: { categories: ["Arquitetura"], seo: { canonical_url: "not-a-url" } },
|
|
38
|
+
id: "x",
|
|
39
|
+
};
|
|
40
|
+
expect(postParams(post)).toEqual({ category: "arquitetura", slug: "x" });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("falls back when canonical_url has fewer than 2 segments", () => {
|
|
44
|
+
const post = {
|
|
45
|
+
data: { categories: ["Arquitetura"], seo: { canonical_url: "https://x.com/foo" } },
|
|
46
|
+
id: "x",
|
|
47
|
+
};
|
|
48
|
+
expect(postParams(post)).toEqual({ category: "arquitetura", slug: "x" });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { slugify } from "./slugify";
|
|
2
|
+
|
|
3
|
+
type PostLike = {
|
|
4
|
+
data: {
|
|
5
|
+
categories?: Array<string>;
|
|
6
|
+
seo?: { canonical_url?: string };
|
|
7
|
+
};
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type PostParams = { category: string; slug: string };
|
|
12
|
+
|
|
13
|
+
type PostUrlBuilder = {
|
|
14
|
+
postParams: (post: PostLike) => PostParams;
|
|
15
|
+
postUrl: (post: PostLike) => string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Per-blog URL builder. Each blog supplies its own pt-BR -> slug map
|
|
19
|
+
// (`category-slugs.json`) and the catch-all category used when the post has
|
|
20
|
+
// no categories. The canonical_url short-circuit lets WordPress migrations
|
|
21
|
+
// keep their original URL pair while everything else falls back to a slugified
|
|
22
|
+
// derived category + Astro entry id.
|
|
23
|
+
const createPostUrl = (
|
|
24
|
+
categorySlugMap: Record<string, string>,
|
|
25
|
+
defaultCategory: string,
|
|
26
|
+
): PostUrlBuilder => {
|
|
27
|
+
const postParams = (post: PostLike): PostParams => {
|
|
28
|
+
const canonical = post.data.seo?.canonical_url;
|
|
29
|
+
if (canonical) {
|
|
30
|
+
try {
|
|
31
|
+
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
32
|
+
const category = segments.at(-2);
|
|
33
|
+
const slug = segments.at(-1);
|
|
34
|
+
if (category && slug) {
|
|
35
|
+
return { category, slug };
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Malformed canonical_url — fall through to category-derived logic.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const first = post.data.categories?.at(0);
|
|
42
|
+
const category = first ? (categorySlugMap[first] ?? slugify(first)) : defaultCategory;
|
|
43
|
+
return { category, slug: post.id };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const postUrl = (post: PostLike): string => {
|
|
47
|
+
const { category, slug } = postParams(post);
|
|
48
|
+
return `/${category}/${slug}/`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return { postParams, postUrl };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type { PostLike, PostParams, PostUrlBuilder };
|
|
55
|
+
export { createPostUrl };
|
package/dist/index-BAPPsft-.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ClassValue } from "clsx";
|
|
2
|
-
|
|
3
|
-
//#region src/lib/cn.d.ts
|
|
4
|
-
declare const cn: (...inputs: Array<ClassValue>) => string;
|
|
5
|
-
//#endregion
|
|
6
|
-
//#region src/lib/format-date.d.ts
|
|
7
|
-
declare const formatDate: (date: Date, locale?: string, options?: Intl.DateTimeFormatOptions) => string;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/lib/get-related-posts.d.ts
|
|
10
|
-
type HasTags = {
|
|
11
|
-
data: {
|
|
12
|
-
tags: Array<string>;
|
|
13
|
-
};
|
|
14
|
-
slug: string;
|
|
15
|
-
};
|
|
16
|
-
declare const getRelatedPosts: <T extends HasTags>(current: T, all: Array<T>, n: number) => T[];
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/lib/reading-time.d.ts
|
|
19
|
-
declare const readingTime: (input: string) => {
|
|
20
|
-
minutes: number;
|
|
21
|
-
words: number;
|
|
22
|
-
};
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/lib/slugify.d.ts
|
|
25
|
-
declare const slugify: (input: string) => string;
|
|
26
|
-
declare namespace index_d_exports {
|
|
27
|
-
export { cn, formatDate, getRelatedPosts, readingTime, slugify };
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { formatDate as a, getRelatedPosts as i, slugify as n, cn as o, readingTime as r, index_d_exports as t };
|
|
31
|
-
//# sourceMappingURL=index-BAPPsft-.d.ts.map
|
package/dist/lib-BjqsRto0.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lib-BjqsRto0.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/reading-time.ts","../src/lib/slugify.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {\n const key = `${locale}:${JSON.stringify(options)}`;\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (\n date: Date,\n locale: string = \"en-US\",\n options: Intl.DateTimeFormatOptions = defaultOptions,\n) => getFormatter(locale, { ...defaultOptions, ...options }).format(date);\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; slug: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.slug === current.slug) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\n"],"mappings":";;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,QAAgB,YAAwC;CAC5E,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,UAAU,OAAO;CAC/C,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cACJ,MACA,SAAiB,SACjB,UAAsC,mBACnC,aAAa,QAAQ;CAAE,GAAG;CAAgB,GAAG;AAAQ,CAAC,EAAE,OAAO,IAAI;;;ACrBxE,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,SAAS,QAAQ,MACxB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EACpC,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,WAAW,IAAI;AAC3B;;;ACvBA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE,EAC1B,2BAAW,IAAA,OAAA,cAAA,IAAY,GAAG,GAAG,EAC7B,2BAAW,IAAA,OAAA,QAAA,IAAM,GAAG,GAAG,EACvB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B;;;ACXA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,EACf,2BAAW,IAAA,OAAA,SAAA,IAAQ,GAAG,EAAE,EACxB,YAAY,EACZ,2BAAW,IAAA,OAAA,mBAAA,IAAgB,GAAG,EAAE,EAChC,KAAK,EACL,2BAAW,IAAA,OAAA,aAAA,IAAU,GAAG,GAAG,EAC3B,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE"}
|
|
File without changes
|