devskill 2.0.3 → 2.0.5
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/.claude/settings.local.json +11 -0
- package/LICENSE +21 -0
- package/README.md +4 -1
- package/landing/AGENTS.md +5 -0
- package/landing/CLAUDE.md +1 -0
- package/landing/README.md +36 -0
- package/landing/app/[locale]/layout.tsx +58 -0
- package/landing/app/[locale]/page.tsx +291 -0
- package/landing/app/globals.css +129 -0
- package/landing/app/icon.tsx +41 -0
- package/landing/app/opengraph-image.tsx +69 -0
- package/landing/components/ui/accordion.tsx +74 -0
- package/landing/components/ui/badge.tsx +52 -0
- package/landing/components/ui/button.tsx +60 -0
- package/landing/components/ui/card.tsx +103 -0
- package/landing/components/ui/copy-button.tsx +60 -0
- package/landing/components/ui/navigation-menu.tsx +168 -0
- package/landing/components.json +25 -0
- package/landing/eslint.config.mjs +18 -0
- package/landing/i18n/request.ts +17 -0
- package/landing/i18n/routing.ts +17 -0
- package/landing/lib/utils.ts +6 -0
- package/landing/messages/en.json +32 -0
- package/landing/messages/vi.json +32 -0
- package/landing/middleware.ts +9 -0
- package/landing/next.config.ts +10 -0
- package/landing/package-lock.json +10540 -0
- package/landing/package.json +35 -0
- package/landing/postcss.config.mjs +7 -0
- package/landing/public/file.svg +1 -0
- package/landing/public/globe.svg +1 -0
- package/landing/public/next.svg +1 -0
- package/landing/public/vercel.svg +1 -0
- package/landing/public/window.svg +1 -0
- package/landing/tsconfig.json +34 -0
- package/meta.ts +5 -1
- package/package.json +7 -1
- package/skills/builderx_api-kafka/SKILL.md +175 -0
- package/skills/builderx_api-mongodb/SKILL.md +93 -0
- package/skills/builderx_api-rabbitmq/SKILL.md +169 -0
- package/skills/builderx_api-redis/SKILL.md +93 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ImageResponse } from 'next/og';
|
|
2
|
+
|
|
3
|
+
export const runtime = 'edge';
|
|
4
|
+
|
|
5
|
+
export const alt = 'devskill - Upgrade Your AI\'s Brain';
|
|
6
|
+
export const size = {
|
|
7
|
+
width: 1200,
|
|
8
|
+
height: 630,
|
|
9
|
+
};
|
|
10
|
+
export const contentType = 'image/png';
|
|
11
|
+
|
|
12
|
+
export default function Image() {
|
|
13
|
+
return new ImageResponse(
|
|
14
|
+
(
|
|
15
|
+
<div
|
|
16
|
+
style={{
|
|
17
|
+
background: '#09090b',
|
|
18
|
+
width: '100%',
|
|
19
|
+
height: '100%',
|
|
20
|
+
display: 'flex',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
fontFamily: 'sans-serif',
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div style={{
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
marginBottom: '40px'
|
|
31
|
+
}}>
|
|
32
|
+
<div style={{
|
|
33
|
+
display: 'flex',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
justifyContent: 'center',
|
|
36
|
+
background: 'linear-gradient(to right, #22d3ee, #f43f5e)',
|
|
37
|
+
borderRadius: '32px',
|
|
38
|
+
width: '120px',
|
|
39
|
+
height: '120px',
|
|
40
|
+
marginRight: '32px'
|
|
41
|
+
}}>
|
|
42
|
+
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
43
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
|
|
44
|
+
</svg>
|
|
45
|
+
</div>
|
|
46
|
+
<div style={{
|
|
47
|
+
fontSize: '110px',
|
|
48
|
+
fontWeight: 800,
|
|
49
|
+
color: 'white',
|
|
50
|
+
letterSpacing: '-0.02em'
|
|
51
|
+
}}>
|
|
52
|
+
devskill
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div style={{
|
|
56
|
+
fontSize: '48px',
|
|
57
|
+
color: '#a1a1aa', // zinc-400
|
|
58
|
+
fontWeight: 500,
|
|
59
|
+
textAlign: 'center'
|
|
60
|
+
}}>
|
|
61
|
+
Equip your AI Agents with Expert Superpowers
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
),
|
|
65
|
+
{
|
|
66
|
+
...size,
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
9
|
+
return (
|
|
10
|
+
<AccordionPrimitive.Root
|
|
11
|
+
data-slot="accordion"
|
|
12
|
+
className={cn("flex w-full flex-col", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
19
|
+
return (
|
|
20
|
+
<AccordionPrimitive.Item
|
|
21
|
+
data-slot="accordion-item"
|
|
22
|
+
className={cn("not-last:border-b", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AccordionTrigger({
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: AccordionPrimitive.Trigger.Props) {
|
|
33
|
+
return (
|
|
34
|
+
<AccordionPrimitive.Header className="flex">
|
|
35
|
+
<AccordionPrimitive.Trigger
|
|
36
|
+
data-slot="accordion-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
"group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<ChevronDownIcon data-slot="accordion-trigger-icon" className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
|
|
45
|
+
<ChevronUpIcon data-slot="accordion-trigger-icon" className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
|
46
|
+
</AccordionPrimitive.Trigger>
|
|
47
|
+
</AccordionPrimitive.Header>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function AccordionContent({
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
...props
|
|
55
|
+
}: AccordionPrimitive.Panel.Props) {
|
|
56
|
+
return (
|
|
57
|
+
<AccordionPrimitive.Panel
|
|
58
|
+
data-slot="accordion-content"
|
|
59
|
+
className="overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up"
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
<div
|
|
63
|
+
className={cn(
|
|
64
|
+
"h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
</AccordionPrimitive.Panel>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function Badge({
|
|
31
|
+
className,
|
|
32
|
+
variant = "default",
|
|
33
|
+
render,
|
|
34
|
+
...props
|
|
35
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
36
|
+
return useRender({
|
|
37
|
+
defaultTagName: "span",
|
|
38
|
+
props: mergeProps<"span">(
|
|
39
|
+
{
|
|
40
|
+
className: cn(badgeVariants({ variant }), className),
|
|
41
|
+
},
|
|
42
|
+
props
|
|
43
|
+
),
|
|
44
|
+
render,
|
|
45
|
+
state: {
|
|
46
|
+
slot: "badge",
|
|
47
|
+
variant,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
14
|
+
outline:
|
|
15
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
+
destructive:
|
|
21
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default:
|
|
26
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
27
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
28
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
29
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
30
|
+
icon: "size-8",
|
|
31
|
+
"icon-xs":
|
|
32
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
33
|
+
"icon-sm":
|
|
34
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
35
|
+
"icon-lg": "size-9",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
size: "default",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
function Button({
|
|
46
|
+
className,
|
|
47
|
+
variant = "default",
|
|
48
|
+
size = "default",
|
|
49
|
+
...props
|
|
50
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
51
|
+
return (
|
|
52
|
+
<ButtonPrimitive
|
|
53
|
+
data-slot="button"
|
|
54
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
data-slot="card"
|
|
13
|
+
data-size={size}
|
|
14
|
+
className={cn(
|
|
15
|
+
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
data-slot="card-header"
|
|
27
|
+
className={cn(
|
|
28
|
+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
data-slot="card-title"
|
|
40
|
+
className={cn(
|
|
41
|
+
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
data-slot="card-description"
|
|
53
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
data-slot="card-action"
|
|
63
|
+
className={cn(
|
|
64
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="card-content"
|
|
76
|
+
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="card-footer"
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
Card,
|
|
97
|
+
CardHeader,
|
|
98
|
+
CardFooter,
|
|
99
|
+
CardTitle,
|
|
100
|
+
CardAction,
|
|
101
|
+
CardDescription,
|
|
102
|
+
CardContent,
|
|
103
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Copy, Check } from "lucide-react";
|
|
5
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
|
|
8
|
+
interface CopyButtonProps {
|
|
9
|
+
value: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CopyButton({ value, className, variant = "ghost" }: CopyButtonProps) {
|
|
15
|
+
const [hasCopied, setHasCopied] = useState(false);
|
|
16
|
+
|
|
17
|
+
const copyToClipboard = async () => {
|
|
18
|
+
try {
|
|
19
|
+
await navigator.clipboard.writeText(value);
|
|
20
|
+
setHasCopied(true);
|
|
21
|
+
setTimeout(() => setHasCopied(false), 2000);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error("Failed to copy!", err);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Button
|
|
29
|
+
variant={variant}
|
|
30
|
+
size="icon"
|
|
31
|
+
className={className}
|
|
32
|
+
onClick={copyToClipboard}
|
|
33
|
+
aria-label="Copy to clipboard"
|
|
34
|
+
>
|
|
35
|
+
<AnimatePresence mode="wait" initial={false}>
|
|
36
|
+
{hasCopied ? (
|
|
37
|
+
<motion.div
|
|
38
|
+
key="check"
|
|
39
|
+
initial={{ opacity: 0, scale: 0.5, rotate: -90 }}
|
|
40
|
+
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
|
41
|
+
exit={{ opacity: 0, scale: 0.5, rotate: 90 }}
|
|
42
|
+
transition={{ duration: 0.15 }}
|
|
43
|
+
>
|
|
44
|
+
<Check className="w-4 h-4 text-green-500" />
|
|
45
|
+
</motion.div>
|
|
46
|
+
) : (
|
|
47
|
+
<motion.div
|
|
48
|
+
key="copy"
|
|
49
|
+
initial={{ opacity: 0, scale: 0.5, rotate: 90 }}
|
|
50
|
+
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
|
51
|
+
exit={{ opacity: 0, scale: 0.5, rotate: -90 }}
|
|
52
|
+
transition={{ duration: 0.15 }}
|
|
53
|
+
>
|
|
54
|
+
<Copy className="w-4 h-4" />
|
|
55
|
+
</motion.div>
|
|
56
|
+
)}
|
|
57
|
+
</AnimatePresence>
|
|
58
|
+
</Button>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
|
|
2
|
+
import { cva } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
function NavigationMenu({
|
|
8
|
+
align = "start",
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
...props
|
|
12
|
+
}: NavigationMenuPrimitive.Root.Props &
|
|
13
|
+
Pick<NavigationMenuPrimitive.Positioner.Props, "align">) {
|
|
14
|
+
return (
|
|
15
|
+
<NavigationMenuPrimitive.Root
|
|
16
|
+
data-slot="navigation-menu"
|
|
17
|
+
className={cn(
|
|
18
|
+
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
<NavigationMenuPositioner align={align} />
|
|
25
|
+
</NavigationMenuPrimitive.Root>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function NavigationMenuList({
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>) {
|
|
33
|
+
return (
|
|
34
|
+
<NavigationMenuPrimitive.List
|
|
35
|
+
data-slot="navigation-menu-list"
|
|
36
|
+
className={cn(
|
|
37
|
+
"group flex flex-1 list-none items-center justify-center gap-0",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function NavigationMenuItem({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>) {
|
|
49
|
+
return (
|
|
50
|
+
<NavigationMenuPrimitive.Item
|
|
51
|
+
data-slot="navigation-menu-item"
|
|
52
|
+
className={cn("relative", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const navigationMenuTriggerStyle = cva(
|
|
59
|
+
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-lg bg-background px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted data-open:bg-muted/50 data-open:hover:bg-muted data-open:focus:bg-muted"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
function NavigationMenuTrigger({
|
|
63
|
+
className,
|
|
64
|
+
children,
|
|
65
|
+
...props
|
|
66
|
+
}: NavigationMenuPrimitive.Trigger.Props) {
|
|
67
|
+
return (
|
|
68
|
+
<NavigationMenuPrimitive.Trigger
|
|
69
|
+
data-slot="navigation-menu-trigger"
|
|
70
|
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
{children}{" "}
|
|
74
|
+
<ChevronDownIcon className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180" aria-hidden="true" />
|
|
75
|
+
</NavigationMenuPrimitive.Trigger>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function NavigationMenuContent({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: NavigationMenuPrimitive.Content.Props) {
|
|
83
|
+
return (
|
|
84
|
+
<NavigationMenuPrimitive.Content
|
|
85
|
+
data-slot="navigation-menu-content"
|
|
86
|
+
className={cn(
|
|
87
|
+
"data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-1 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-lg group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function NavigationMenuPositioner({
|
|
96
|
+
className,
|
|
97
|
+
side = "bottom",
|
|
98
|
+
sideOffset = 8,
|
|
99
|
+
align = "start",
|
|
100
|
+
alignOffset = 0,
|
|
101
|
+
...props
|
|
102
|
+
}: NavigationMenuPrimitive.Positioner.Props) {
|
|
103
|
+
return (
|
|
104
|
+
<NavigationMenuPrimitive.Portal>
|
|
105
|
+
<NavigationMenuPrimitive.Positioner
|
|
106
|
+
side={side}
|
|
107
|
+
sideOffset={sideOffset}
|
|
108
|
+
align={align}
|
|
109
|
+
alignOffset={alignOffset}
|
|
110
|
+
className={cn(
|
|
111
|
+
"isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0",
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
>
|
|
116
|
+
<NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0">
|
|
117
|
+
<NavigationMenuPrimitive.Viewport className="relative size-full overflow-hidden" />
|
|
118
|
+
</NavigationMenuPrimitive.Popup>
|
|
119
|
+
</NavigationMenuPrimitive.Positioner>
|
|
120
|
+
</NavigationMenuPrimitive.Portal>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function NavigationMenuLink({
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}: NavigationMenuPrimitive.Link.Props) {
|
|
128
|
+
return (
|
|
129
|
+
<NavigationMenuPrimitive.Link
|
|
130
|
+
data-slot="navigation-menu-link"
|
|
131
|
+
className={cn(
|
|
132
|
+
"flex items-center gap-2 rounded-lg p-2 text-sm transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-md data-active:bg-muted/50 data-active:hover:bg-muted data-active:focus:bg-muted [&_svg:not([class*='size-'])]:size-4",
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function NavigationMenuIndicator({
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) {
|
|
144
|
+
return (
|
|
145
|
+
<NavigationMenuPrimitive.Icon
|
|
146
|
+
data-slot="navigation-menu-indicator"
|
|
147
|
+
className={cn(
|
|
148
|
+
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
|
154
|
+
</NavigationMenuPrimitive.Icon>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
NavigationMenu,
|
|
160
|
+
NavigationMenuContent,
|
|
161
|
+
NavigationMenuIndicator,
|
|
162
|
+
NavigationMenuItem,
|
|
163
|
+
NavigationMenuLink,
|
|
164
|
+
NavigationMenuList,
|
|
165
|
+
NavigationMenuTrigger,
|
|
166
|
+
navigationMenuTriggerStyle,
|
|
167
|
+
NavigationMenuPositioner,
|
|
168
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {getRequestConfig} from 'next-intl/server';
|
|
2
|
+
import {routing} from './routing';
|
|
3
|
+
|
|
4
|
+
export default getRequestConfig(async ({requestLocale}) => {
|
|
5
|
+
// This should typically correspond to the `[locale]` segment
|
|
6
|
+
let locale = await requestLocale;
|
|
7
|
+
|
|
8
|
+
// Ensure that a valid locale is used
|
|
9
|
+
if (!locale || !routing.locales.includes(locale as "en" | "vi")) {
|
|
10
|
+
locale = routing.defaultLocale;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
locale,
|
|
15
|
+
messages: (await import(`../messages/${locale}.json`)).default
|
|
16
|
+
};
|
|
17
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {defineRouting} from 'next-intl/routing';
|
|
2
|
+
import {createNavigation} from 'next-intl/navigation';
|
|
3
|
+
|
|
4
|
+
export const routing = defineRouting({
|
|
5
|
+
// A list of all locales that are supported
|
|
6
|
+
locales: ['en', 'vi'],
|
|
7
|
+
|
|
8
|
+
// Used when no locale matches
|
|
9
|
+
defaultLocale: 'vi',
|
|
10
|
+
// Hide default locale from URL
|
|
11
|
+
localePrefix: 'as-needed'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Lightweight wrappers around Next.js' navigation APIs
|
|
15
|
+
// that will consider the routing configuration
|
|
16
|
+
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
|
17
|
+
createNavigation(routing);
|