@owlmeans/create-app 0.1.18-rc.3 → 0.1.18-rc.30

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.
Files changed (78) hide show
  1. package/README.md +39 -2
  2. package/build/args.d.ts +8 -0
  3. package/build/args.d.ts.map +1 -1
  4. package/build/args.js +60 -6
  5. package/build/args.js.map +1 -1
  6. package/build/index.d.ts +4 -1
  7. package/build/index.d.ts.map +1 -1
  8. package/build/index.js +2 -0
  9. package/build/index.js.map +1 -1
  10. package/build/naming.d.ts +11 -0
  11. package/build/naming.d.ts.map +1 -0
  12. package/build/naming.js +18 -0
  13. package/build/naming.js.map +1 -0
  14. package/build/run.d.ts.map +1 -1
  15. package/build/run.js +19 -13
  16. package/build/run.js.map +1 -1
  17. package/build/scaffold.d.ts +21 -0
  18. package/build/scaffold.d.ts.map +1 -0
  19. package/build/scaffold.js +18 -0
  20. package/build/scaffold.js.map +1 -0
  21. package/build/template.d.ts +19 -3
  22. package/build/template.d.ts.map +1 -1
  23. package/build/template.js +52 -12
  24. package/build/template.js.map +1 -1
  25. package/package.json +4 -3
  26. package/template/AGENTS.md +28 -1
  27. package/template/README.bare.md +57 -0
  28. package/template/README.md +5 -61
  29. package/template/_agents/scripts/link-skills.sh +310 -15
  30. package/template/_agents/skills/agent-memory/SKILL.md +12 -11
  31. package/template/_agents/skills/getting-started/SKILL.md +26 -86
  32. package/template/_agents/skills/memory-promotion/SKILL.md +10 -4
  33. package/template/_agents/skills/memory-recompact/SKILL.md +15 -14
  34. package/template/_agents/skills/reuse-code/SKILL.md +36 -8
  35. package/template/_agents/skills/self-education/SKILL.md +19 -5
  36. package/template/_agents/skills/skill-authoring/SKILL.md +53 -11
  37. package/template/_bare.json +23 -0
  38. package/template/_gitignore +3 -0
  39. package/template/bunfig.toml +2 -0
  40. package/template/package.json +3 -2
  41. package/template/sources/api/package.json +3 -3
  42. package/template/sources/api/src/app/session/add.ts +7 -6
  43. package/template/sources/api/src/app/session/list.ts +12 -9
  44. package/template/sources/api/src/app/session/remove.ts +7 -6
  45. package/template/sources/api/src/context.bare.ts +11 -0
  46. package/template/sources/api/src/entrypoints.bare.ts +4 -0
  47. package/template/sources/api/src/entrypoints.ts +13 -0
  48. package/template/sources/api/src/index.ts +2 -2
  49. package/template/sources/api/src/types.bare.ts +5 -0
  50. package/template/sources/common/package.json +6 -6
  51. package/template/sources/common/src/consts.bare.ts +8 -0
  52. package/template/sources/common/src/consts.ts +0 -13
  53. package/template/sources/common/src/entrypoints.bare.ts +14 -0
  54. package/template/sources/common/src/entrypoints.ts +60 -0
  55. package/template/sources/common/src/index.bare.ts +3 -0
  56. package/template/sources/common/src/index.ts +1 -1
  57. package/template/sources/common/src/schemas.ts +7 -7
  58. package/template/sources/web/index.html +3 -1
  59. package/template/sources/web/package.json +13 -11
  60. package/template/sources/web/src/components/ui/dropdown-menu.tsx +257 -0
  61. package/template/sources/web/src/components/ui/navigation-menu.tsx +169 -0
  62. package/template/sources/web/src/context.bare.ts +13 -0
  63. package/template/sources/web/src/context.ts +14 -2
  64. package/template/sources/web/src/entrypoints.bare.ts +13 -0
  65. package/template/sources/web/src/entrypoints.ts +16 -0
  66. package/template/sources/web/src/index.css +7 -0
  67. package/template/sources/web/src/index.tsx +2 -2
  68. package/template/sources/web/src/layout/main.tsx +16 -19
  69. package/template/sources/web/src/nav.bare.ts +21 -0
  70. package/template/sources/web/src/nav.ts +28 -0
  71. package/template/sources/web/src/screens/about.tsx +19 -0
  72. package/template/sources/web/src/screens/home.bare.tsx +17 -0
  73. package/template/sources/web/src/screens/session.tsx +28 -14
  74. package/template/sources/web/src/vite-env.d.ts +1 -0
  75. package/template/sources/api/src/modules.ts +0 -11
  76. package/template/sources/common/src/modules.ts +0 -26
  77. package/template/sources/web/src/components/nav/main.tsx +0 -15
  78. package/template/sources/web/src/modules.ts +0 -20
@@ -1,9 +1,9 @@
1
1
  import { main } from '@owlmeans/server-app'
2
2
  import config from './config.js'
3
3
  import { makeContext } from './context.js'
4
- import { appModules } from './modules.js'
4
+ import { appBindings } from './entrypoints.js'
5
5
  import type { Config, Context } from './types.js'
6
6
 
7
7
  const context = makeContext<Config, Context>(config)
8
8
 
9
- main<{}, Config, Context>(context, appModules)
9
+ main<{}, Config, Context>(context, appBindings)
@@ -0,0 +1,5 @@
1
+ import type { AppConfig, AppContext } from '@owlmeans/server-app'
2
+
3
+ export interface Config extends AppConfig {}
4
+
5
+ export interface Context<C extends Config = Config> extends AppContext<C> {}
@@ -19,15 +19,15 @@
19
19
  "typecheck": "tsc -b"
20
20
  },
21
21
  "dependencies": {
22
- "@owlmeans/config": "^0.1.16",
23
- "@owlmeans/entrypoint": "^0.1.16",
24
- "@owlmeans/error": "^0.1.16",
25
- "@owlmeans/resource": "^0.1.16",
26
- "@owlmeans/route": "^0.1.16",
22
+ "@owlmeans/config": "^0.1.18-rc.23",
23
+ "@owlmeans/entrypoint": "^0.1.18-rc.22",
24
+ "@owlmeans/error": "^0.1.18-rc.19",
25
+ "@owlmeans/resource": "^0.1.18-rc.20",
26
+ "@owlmeans/route": "^0.1.18-rc.20",
27
27
  "ajv": "^8.17.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@owlmeans/dep-config": "^0.1.16",
30
+ "@owlmeans/dep-config": "^0.1.18-rc.6",
31
31
  "@types/node": "^24.10.1",
32
32
  "typescript": "^7.0.2"
33
33
  }
@@ -0,0 +1,8 @@
1
+ /** Service aliases shared between web and api. */
2
+ export const APP = '__APP_SLUG__'
3
+ export const APP_WEB = '__APP_SLUG__-web'
4
+ export const APP_API = '__APP_SLUG__-api'
5
+
6
+ /** Local development ports. */
7
+ export const WEB_PORT = 3001
8
+ export const API_PORT = 3000
@@ -6,16 +6,3 @@ export const APP_API = '__APP_SLUG__-api'
6
6
  /** Local development ports. */
7
7
  export const WEB_PORT = 3001
8
8
  export const API_PORT = 3000
9
-
10
- /** Backend (API) entrypoint identifiers. */
11
- export const session = {
12
- base: '__APP_SLUG__:api:session',
13
- list: '__APP_SLUG__:api:session:list',
14
- add: '__APP_SLUG__:api:session:add',
15
- remove: '__APP_SLUG__:api:session:remove',
16
- }
17
-
18
- /** Frontend (web) entrypoint identifiers. */
19
- export const web = {
20
- session: '__APP_SLUG__:web:session',
21
- }
@@ -0,0 +1,14 @@
1
+ import { openProtocol } from '@owlmeans/entrypoint'
2
+ import { BASE, HOME } from '@owlmeans/context'
3
+ import { frontend, route } from '@owlmeans/route'
4
+
5
+ const webBase = openProtocol(route(BASE, '/', frontend()))
6
+
7
+ /** The shared immutable protocol tree. Add API declarations below `api` as the shell grows. */
8
+ export const appProtocols = {
9
+ api: {},
10
+ web: {
11
+ base: webBase,
12
+ home: openProtocol(route(HOME, '/', frontend({ default: true, parent: webBase }))),
13
+ },
14
+ }
@@ -0,0 +1,60 @@
1
+ import { contract, openProtocol, protocol, typed } from '@owlmeans/entrypoint'
2
+ import { route, RouteMethod, frontend } from '@owlmeans/route'
3
+ import { AddItemSchema, ItemParamsSchema, SessionParamsSchema } from './schemas.js'
4
+ import type { SessionItem } from './types.js'
5
+
6
+ const aliases = {
7
+ session: {
8
+ base: '__APP_SLUG__:api:session',
9
+ list: '__APP_SLUG__:api:session:list',
10
+ add: '__APP_SLUG__:api:session:add',
11
+ remove: '__APP_SLUG__:api:session:remove',
12
+ },
13
+ web: {
14
+ base: '__APP_SLUG__:web:base',
15
+ home: '__APP_SLUG__:web:home',
16
+ session: '__APP_SLUG__:web:session',
17
+ about: '__APP_SLUG__:web:about',
18
+ },
19
+ }
20
+
21
+ const sessionBase = protocol(route(aliases.session.base, '/session'), contract())
22
+ const webBase = openProtocol(route(aliases.web.base, '/', frontend()))
23
+
24
+ /**
25
+ * Shared entrypoint declarations. The api materializes these with handlers; the web
26
+ * materializes them with screen components and calls them. Routes resolve under the
27
+ * api service `base` (`/api`), so e.g. `session.list` → `GET /api/session/:sid/items`.
28
+ */
29
+ export const session = {
30
+ base: sessionBase,
31
+ list: protocol(
32
+ route(aliases.session.list, '/:sid/items', { parent: sessionBase, method: RouteMethod.GET }),
33
+ contract.request({ params: SessionParamsSchema }, typed<SessionItem[]>()),
34
+ ),
35
+ add: protocol(
36
+ route(aliases.session.add, '/:sid/items', { parent: sessionBase, method: RouteMethod.POST }),
37
+ contract.request({
38
+ params: SessionParamsSchema,
39
+ body: AddItemSchema,
40
+ }, typed<SessionItem>()),
41
+ ),
42
+ remove: protocol(
43
+ route(aliases.session.remove, '/:sid/items/:id', { parent: sessionBase, method: RouteMethod.DELETE }),
44
+ contract.request({ params: ItemParamsSchema }, typed<{ removed: boolean }>()),
45
+ ),
46
+ }
47
+
48
+ /** Frontend routes are protocol declarations too; their renderer is bound by the web project. */
49
+ export const web = {
50
+ base: webBase,
51
+ home: openProtocol(route(aliases.web.home, '/', frontend({ default: true, parent: webBase }))),
52
+ session: openProtocol(route(aliases.web.session, '/session', frontend({ parent: webBase }))),
53
+ about: openProtocol(route(aliases.web.about, '/about', frontend({ parent: webBase }))),
54
+ }
55
+
56
+ /** The shared immutable protocol tree. Runtime packages bind their own local handlers and screens. */
57
+ export const appProtocols = Object.freeze({
58
+ api: Object.freeze({ session }),
59
+ web: Object.freeze(web),
60
+ })
@@ -0,0 +1,3 @@
1
+ export * from './consts.js'
2
+ export * from './config.js'
3
+ export * from './entrypoints.js'
@@ -2,4 +2,4 @@ export * from './consts.js'
2
2
  export * from './types.js'
3
3
  export * from './schemas.js'
4
4
  export * from './config.js'
5
- export * from './modules.js'
5
+ export * from './entrypoints.js'
@@ -1,25 +1,25 @@
1
- import type { JSONSchemaType } from 'ajv'
1
+ import { schema } from '@owlmeans/entrypoint'
2
2
  import type { AddItemPayload, ItemParams, SessionParams } from './types.js'
3
3
 
4
- export const AddItemSchema: JSONSchemaType<AddItemPayload> = {
4
+ export const AddItemSchema = schema<AddItemPayload>({
5
5
  type: 'object',
6
6
  properties: {
7
7
  text: { type: 'string', minLength: 1, maxLength: 280 },
8
8
  },
9
9
  required: ['text'],
10
10
  additionalProperties: false,
11
- }
11
+ })
12
12
 
13
- export const SessionParamsSchema: JSONSchemaType<SessionParams> = {
13
+ export const SessionParamsSchema = schema<SessionParams>({
14
14
  type: 'object',
15
15
  properties: {
16
16
  sid: { type: 'string', minLength: 1 },
17
17
  },
18
18
  required: ['sid'],
19
19
  additionalProperties: false,
20
- }
20
+ })
21
21
 
22
- export const ItemParamsSchema: JSONSchemaType<ItemParams> = {
22
+ export const ItemParamsSchema = schema<ItemParams>({
23
23
  type: 'object',
24
24
  properties: {
25
25
  sid: { type: 'string', minLength: 1 },
@@ -27,4 +27,4 @@ export const ItemParamsSchema: JSONSchemaType<ItemParams> = {
27
27
  },
28
28
  required: ['sid', 'id'],
29
29
  additionalProperties: false,
30
- }
30
+ })
@@ -1,9 +1,11 @@
1
1
  <!doctype html>
2
- <html lang="en">
2
+ <html lang="__APP_LANG__">
3
3
 
4
4
  <head>
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="application-name" content="__APP_NAME__" />
8
+ <meta name="description" content="__APP_DESCRIPTION__" />
7
9
  <title>__APP_NAME__</title>
8
10
  </head>
9
11
 
@@ -11,19 +11,21 @@
11
11
  "preview": "vite preview"
12
12
  },
13
13
  "dependencies": {
14
- "@owlmeans/client": "^0.1.16",
15
- "@owlmeans/client-config": "^0.1.16",
16
- "@owlmeans/client-context": "^0.1.16",
17
- "@owlmeans/client-entrypoint": "^0.1.16",
18
- "@owlmeans/client-i18n": "^0.1.16",
19
- "@owlmeans/entrypoint": "^0.1.16",
20
- "@owlmeans/route": "^0.1.16",
21
- "@owlmeans/web-client": "^0.1.16",
22
- "@owlmeans/web-panel": "^0.1.16",
23
- "__APP_SLUG__-common": "workspace:^",
14
+ "@owlmeans/client": "^0.1.18-rc.27",
15
+ "@owlmeans/client-config": "^0.1.18-rc.23",
16
+ "@owlmeans/client-context": "^0.1.18-rc.24",
17
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.24",
18
+ "@owlmeans/client-i18n": "^0.1.18-rc.26",
19
+ "@owlmeans/entrypoint": "^0.1.18-rc.22",
20
+ "@owlmeans/route": "^0.1.18-rc.20",
21
+ "@owlmeans/state": "^0.1.18-rc.21",
22
+ "@owlmeans/web-client": "^0.1.18-rc.36",
23
+ "@owlmeans/web-panel": "^0.1.18-rc.44",
24
24
  "@radix-ui/react-label": "^2.1.0",
25
+ "@radix-ui/react-navigation-menu": "^1.2.14",
25
26
  "@radix-ui/react-progress": "^1.1.0",
26
27
  "@radix-ui/react-slot": "^1.1.0",
28
+ "__APP_SLUG__-common": "workspace:^",
27
29
  "ajv": "^8.17.1",
28
30
  "class-variance-authority": "^0.7.0",
29
31
  "clsx": "^2.1.0",
@@ -37,7 +39,7 @@
37
39
  "tailwindcss": "^4.1.13"
38
40
  },
39
41
  "devDependencies": {
40
- "@owlmeans/dep-config": "^0.1.16",
42
+ "@owlmeans/dep-config": "^0.1.18-rc.6",
41
43
  "@tailwindcss/vite": "^4.1.13",
42
44
  "@types/react": "^19.2.17",
43
45
  "@types/react-dom": "^19.2.3",
@@ -0,0 +1,257 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ )
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ )
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = "default",
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean
69
+ variant?: "default" | "destructive"
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ )
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ )
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ )
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ )
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
174
+ {...props}
175
+ />
176
+ )
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<"span">) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ "text-muted-foreground ml-auto text-xs tracking-widest",
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ )
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ )
239
+ }
240
+
241
+ export {
242
+ DropdownMenu,
243
+ DropdownMenuPortal,
244
+ DropdownMenuTrigger,
245
+ DropdownMenuContent,
246
+ DropdownMenuGroup,
247
+ DropdownMenuLabel,
248
+ DropdownMenuItem,
249
+ DropdownMenuCheckboxItem,
250
+ DropdownMenuRadioGroup,
251
+ DropdownMenuRadioItem,
252
+ DropdownMenuSeparator,
253
+ DropdownMenuShortcut,
254
+ DropdownMenuSub,
255
+ DropdownMenuSubTrigger,
256
+ DropdownMenuSubContent,
257
+ }
@@ -0,0 +1,169 @@
1
+ // shadcn navigation-menu — sourced from shadcn/ui (new-york) 2026-08-24
2
+ import * as React from "react"
3
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
4
+ import { cva } from "class-variance-authority"
5
+ import { ChevronDownIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function NavigationMenu({
10
+ className,
11
+ children,
12
+ viewport = true,
13
+ ...props
14
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
15
+ viewport?: boolean
16
+ }) {
17
+ return (
18
+ <NavigationMenuPrimitive.Root
19
+ data-slot="navigation-menu"
20
+ data-viewport={viewport}
21
+ className={cn(
22
+ "group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ {viewport && <NavigationMenuViewport />}
29
+ </NavigationMenuPrimitive.Root>
30
+ )
31
+ }
32
+
33
+ function NavigationMenuList({
34
+ className,
35
+ ...props
36
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
37
+ return (
38
+ <NavigationMenuPrimitive.List
39
+ data-slot="navigation-menu-list"
40
+ className={cn(
41
+ "group flex flex-1 list-none items-center justify-center gap-1",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function NavigationMenuItem({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
53
+ return (
54
+ <NavigationMenuPrimitive.Item
55
+ data-slot="navigation-menu-item"
56
+ className={cn("relative", className)}
57
+ {...props}
58
+ />
59
+ )
60
+ }
61
+
62
+ const navigationMenuTriggerStyle = cva(
63
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
64
+ )
65
+
66
+ function NavigationMenuTrigger({
67
+ className,
68
+ children,
69
+ ...props
70
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
71
+ return (
72
+ <NavigationMenuPrimitive.Trigger
73
+ data-slot="navigation-menu-trigger"
74
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
75
+ {...props}
76
+ >
77
+ {children}{" "}
78
+ <ChevronDownIcon
79
+ className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
80
+ aria-hidden="true"
81
+ />
82
+ </NavigationMenuPrimitive.Trigger>
83
+ )
84
+ }
85
+
86
+ function NavigationMenuContent({
87
+ className,
88
+ ...props
89
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
90
+ return (
91
+ <NavigationMenuPrimitive.Content
92
+ data-slot="navigation-menu-content"
93
+ className={cn(
94
+ "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out 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 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
95
+ "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
96
+ className
97
+ )}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ function NavigationMenuViewport({
104
+ className,
105
+ ...props
106
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
107
+ return (
108
+ <div
109
+ className={cn(
110
+ "absolute top-full left-0 isolate z-50 flex justify-center"
111
+ )}
112
+ >
113
+ <NavigationMenuPrimitive.Viewport
114
+ data-slot="navigation-menu-viewport"
115
+ className={cn(
116
+ "origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
117
+ className
118
+ )}
119
+ {...props}
120
+ />
121
+ </div>
122
+ )
123
+ }
124
+
125
+ function NavigationMenuLink({
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
129
+ return (
130
+ <NavigationMenuPrimitive.Link
131
+ data-slot="navigation-menu-link"
132
+ className={cn(
133
+ "data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
134
+ className
135
+ )}
136
+ {...props}
137
+ />
138
+ )
139
+ }
140
+
141
+ function NavigationMenuIndicator({
142
+ className,
143
+ ...props
144
+ }: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
145
+ return (
146
+ <NavigationMenuPrimitive.Indicator
147
+ data-slot="navigation-menu-indicator"
148
+ className={cn(
149
+ "data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
150
+ className
151
+ )}
152
+ {...props}
153
+ >
154
+ <div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
155
+ </NavigationMenuPrimitive.Indicator>
156
+ )
157
+ }
158
+
159
+ export {
160
+ NavigationMenu,
161
+ NavigationMenuList,
162
+ NavigationMenuItem,
163
+ NavigationMenuContent,
164
+ NavigationMenuTrigger,
165
+ NavigationMenuLink,
166
+ NavigationMenuIndicator,
167
+ NavigationMenuViewport,
168
+ navigationMenuTriggerStyle,
169
+ }