@getcoherent/core 0.3.12 → 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/LICENSE +21 -0
- package/dist/index.d.ts +34 -99
- package/dist/index.js +569 -33
- package/package.json +8 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergei Kovtun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -1094,53 +1094,27 @@ declare const PageDefinitionSchema: z.ZodObject<{
|
|
|
1094
1094
|
}>;
|
|
1095
1095
|
type PageDefinition = z.infer<typeof PageDefinitionSchema>;
|
|
1096
1096
|
/**
|
|
1097
|
-
* Navigation item
|
|
1097
|
+
* Navigation item — supports flat links and grouped dropdown menus.
|
|
1098
|
+
* Items with the same `group` value are rendered as a DropdownMenu.
|
|
1099
|
+
* Items with `children` render as a dropdown trigger with sub-items.
|
|
1098
1100
|
*/
|
|
1099
|
-
declare const NavigationItemSchema: z.
|
|
1100
|
-
|
|
1101
|
-
route: z.ZodString;
|
|
1102
|
-
icon: z.ZodOptional<z.ZodString>;
|
|
1103
|
-
requiresAuth: z.ZodDefault<z.ZodBoolean>;
|
|
1104
|
-
order: z.ZodNumber;
|
|
1105
|
-
}, "strip", z.ZodTypeAny, {
|
|
1106
|
-
order: number;
|
|
1107
|
-
route: string;
|
|
1108
|
-
requiresAuth: boolean;
|
|
1101
|
+
declare const NavigationItemSchema: z.ZodType<NavigationItem>;
|
|
1102
|
+
interface NavigationItem {
|
|
1109
1103
|
label: string;
|
|
1110
|
-
icon?: string | undefined;
|
|
1111
|
-
}, {
|
|
1112
|
-
order: number;
|
|
1113
1104
|
route: string;
|
|
1114
|
-
|
|
1115
|
-
requiresAuth?: boolean
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1105
|
+
icon?: string;
|
|
1106
|
+
requiresAuth?: boolean;
|
|
1107
|
+
order: number;
|
|
1108
|
+
group?: string;
|
|
1109
|
+
children?: NavigationItem[];
|
|
1110
|
+
}
|
|
1119
1111
|
/**
|
|
1120
1112
|
* Navigation configuration
|
|
1121
1113
|
*/
|
|
1122
1114
|
declare const NavigationSchema: z.ZodObject<{
|
|
1123
1115
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
1124
1116
|
type: z.ZodDefault<z.ZodEnum<["header", "sidebar", "both"]>>;
|
|
1125
|
-
items: z.ZodArray<z.
|
|
1126
|
-
label: z.ZodString;
|
|
1127
|
-
route: z.ZodString;
|
|
1128
|
-
icon: z.ZodOptional<z.ZodString>;
|
|
1129
|
-
requiresAuth: z.ZodDefault<z.ZodBoolean>;
|
|
1130
|
-
order: z.ZodNumber;
|
|
1131
|
-
}, "strip", z.ZodTypeAny, {
|
|
1132
|
-
order: number;
|
|
1133
|
-
route: string;
|
|
1134
|
-
requiresAuth: boolean;
|
|
1135
|
-
label: string;
|
|
1136
|
-
icon?: string | undefined;
|
|
1137
|
-
}, {
|
|
1138
|
-
order: number;
|
|
1139
|
-
route: string;
|
|
1140
|
-
label: string;
|
|
1141
|
-
requiresAuth?: boolean | undefined;
|
|
1142
|
-
icon?: string | undefined;
|
|
1143
|
-
}>, "many">;
|
|
1117
|
+
items: z.ZodArray<z.ZodType<NavigationItem, z.ZodTypeDef, NavigationItem>, "many">;
|
|
1144
1118
|
logo: z.ZodOptional<z.ZodObject<{
|
|
1145
1119
|
text: z.ZodOptional<z.ZodString>;
|
|
1146
1120
|
image: z.ZodOptional<z.ZodString>;
|
|
@@ -1154,25 +1128,13 @@ declare const NavigationSchema: z.ZodObject<{
|
|
|
1154
1128
|
}, "strip", z.ZodTypeAny, {
|
|
1155
1129
|
type: "header" | "sidebar" | "both";
|
|
1156
1130
|
enabled: boolean;
|
|
1157
|
-
items:
|
|
1158
|
-
order: number;
|
|
1159
|
-
route: string;
|
|
1160
|
-
requiresAuth: boolean;
|
|
1161
|
-
label: string;
|
|
1162
|
-
icon?: string | undefined;
|
|
1163
|
-
}[];
|
|
1131
|
+
items: NavigationItem[];
|
|
1164
1132
|
logo?: {
|
|
1165
1133
|
text?: string | undefined;
|
|
1166
1134
|
image?: string | undefined;
|
|
1167
1135
|
} | undefined;
|
|
1168
1136
|
}, {
|
|
1169
|
-
items:
|
|
1170
|
-
order: number;
|
|
1171
|
-
route: string;
|
|
1172
|
-
label: string;
|
|
1173
|
-
requiresAuth?: boolean | undefined;
|
|
1174
|
-
icon?: string | undefined;
|
|
1175
|
-
}[];
|
|
1137
|
+
items: NavigationItem[];
|
|
1176
1138
|
type?: "header" | "sidebar" | "both" | undefined;
|
|
1177
1139
|
enabled?: boolean | undefined;
|
|
1178
1140
|
logo?: {
|
|
@@ -2058,25 +2020,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
|
|
|
2058
2020
|
navigation: z.ZodOptional<z.ZodObject<{
|
|
2059
2021
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
2060
2022
|
type: z.ZodDefault<z.ZodEnum<["header", "sidebar", "both"]>>;
|
|
2061
|
-
items: z.ZodArray<z.
|
|
2062
|
-
label: z.ZodString;
|
|
2063
|
-
route: z.ZodString;
|
|
2064
|
-
icon: z.ZodOptional<z.ZodString>;
|
|
2065
|
-
requiresAuth: z.ZodDefault<z.ZodBoolean>;
|
|
2066
|
-
order: z.ZodNumber;
|
|
2067
|
-
}, "strip", z.ZodTypeAny, {
|
|
2068
|
-
order: number;
|
|
2069
|
-
route: string;
|
|
2070
|
-
requiresAuth: boolean;
|
|
2071
|
-
label: string;
|
|
2072
|
-
icon?: string | undefined;
|
|
2073
|
-
}, {
|
|
2074
|
-
order: number;
|
|
2075
|
-
route: string;
|
|
2076
|
-
label: string;
|
|
2077
|
-
requiresAuth?: boolean | undefined;
|
|
2078
|
-
icon?: string | undefined;
|
|
2079
|
-
}>, "many">;
|
|
2023
|
+
items: z.ZodArray<z.ZodType<NavigationItem, z.ZodTypeDef, NavigationItem>, "many">;
|
|
2080
2024
|
logo: z.ZodOptional<z.ZodObject<{
|
|
2081
2025
|
text: z.ZodOptional<z.ZodString>;
|
|
2082
2026
|
image: z.ZodOptional<z.ZodString>;
|
|
@@ -2090,25 +2034,13 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
|
|
|
2090
2034
|
}, "strip", z.ZodTypeAny, {
|
|
2091
2035
|
type: "header" | "sidebar" | "both";
|
|
2092
2036
|
enabled: boolean;
|
|
2093
|
-
items:
|
|
2094
|
-
order: number;
|
|
2095
|
-
route: string;
|
|
2096
|
-
requiresAuth: boolean;
|
|
2097
|
-
label: string;
|
|
2098
|
-
icon?: string | undefined;
|
|
2099
|
-
}[];
|
|
2037
|
+
items: NavigationItem[];
|
|
2100
2038
|
logo?: {
|
|
2101
2039
|
text?: string | undefined;
|
|
2102
2040
|
image?: string | undefined;
|
|
2103
2041
|
} | undefined;
|
|
2104
2042
|
}, {
|
|
2105
|
-
items:
|
|
2106
|
-
order: number;
|
|
2107
|
-
route: string;
|
|
2108
|
-
label: string;
|
|
2109
|
-
requiresAuth?: boolean | undefined;
|
|
2110
|
-
icon?: string | undefined;
|
|
2111
|
-
}[];
|
|
2043
|
+
items: NavigationItem[];
|
|
2112
2044
|
type?: "header" | "sidebar" | "both" | undefined;
|
|
2113
2045
|
enabled?: boolean | undefined;
|
|
2114
2046
|
logo?: {
|
|
@@ -2436,13 +2368,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
|
|
|
2436
2368
|
navigation?: {
|
|
2437
2369
|
type: "header" | "sidebar" | "both";
|
|
2438
2370
|
enabled: boolean;
|
|
2439
|
-
items:
|
|
2440
|
-
order: number;
|
|
2441
|
-
route: string;
|
|
2442
|
-
requiresAuth: boolean;
|
|
2443
|
-
label: string;
|
|
2444
|
-
icon?: string | undefined;
|
|
2445
|
-
}[];
|
|
2371
|
+
items: NavigationItem[];
|
|
2446
2372
|
logo?: {
|
|
2447
2373
|
text?: string | undefined;
|
|
2448
2374
|
image?: string | undefined;
|
|
@@ -2628,13 +2554,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
|
|
|
2628
2554
|
autoScaffold?: boolean | undefined;
|
|
2629
2555
|
};
|
|
2630
2556
|
navigation?: {
|
|
2631
|
-
items:
|
|
2632
|
-
order: number;
|
|
2633
|
-
route: string;
|
|
2634
|
-
label: string;
|
|
2635
|
-
requiresAuth?: boolean | undefined;
|
|
2636
|
-
icon?: string | undefined;
|
|
2637
|
-
}[];
|
|
2557
|
+
items: NavigationItem[];
|
|
2638
2558
|
type?: "header" | "sidebar" | "both" | undefined;
|
|
2639
2559
|
enabled?: boolean | undefined;
|
|
2640
2560
|
logo?: {
|
|
@@ -3491,6 +3411,11 @@ declare class PageGenerator {
|
|
|
3491
3411
|
* Generate shared Footer component code for components/shared/footer.tsx.
|
|
3492
3412
|
*/
|
|
3493
3413
|
generateSharedFooterCode(): string;
|
|
3414
|
+
/**
|
|
3415
|
+
* Generate shared Sidebar component code for components/shared/sidebar.tsx.
|
|
3416
|
+
* Used when navigation.type is 'sidebar' or 'both'.
|
|
3417
|
+
*/
|
|
3418
|
+
generateSharedSidebarCode(): string;
|
|
3494
3419
|
/**
|
|
3495
3420
|
* Generate React SPA root layout
|
|
3496
3421
|
*/
|
|
@@ -3745,6 +3670,16 @@ declare class ProjectScaffolder {
|
|
|
3745
3670
|
* Update config reference
|
|
3746
3671
|
*/
|
|
3747
3672
|
updateConfig(newConfig: DesignSystemConfig): void;
|
|
3673
|
+
/**
|
|
3674
|
+
* Initial header for coherent init — Coherent Design Method branding.
|
|
3675
|
+
* Replaced by app-branded header on first `coherent chat` via regenerateLayout().
|
|
3676
|
+
*/
|
|
3677
|
+
private generateInitialHeaderCode;
|
|
3678
|
+
/**
|
|
3679
|
+
* Initial footer for coherent init — Coherent Design Method branding.
|
|
3680
|
+
* Replaced by app-branded footer on first `coherent chat` via regenerateLayout().
|
|
3681
|
+
*/
|
|
3682
|
+
private generateInitialFooterCode;
|
|
3748
3683
|
}
|
|
3749
3684
|
|
|
3750
3685
|
interface BasePageContent {
|
package/dist/index.js
CHANGED
|
@@ -206,9 +206,10 @@ var NavigationItemSchema = z.object({
|
|
|
206
206
|
label: z.string(),
|
|
207
207
|
route: z.string(),
|
|
208
208
|
icon: z.string().optional(),
|
|
209
|
-
// lucide-react icon name
|
|
210
209
|
requiresAuth: z.boolean().default(false),
|
|
211
|
-
order: z.number()
|
|
210
|
+
order: z.number(),
|
|
211
|
+
group: z.string().optional(),
|
|
212
|
+
children: z.lazy(() => z.array(NavigationItemSchema)).optional()
|
|
212
213
|
});
|
|
213
214
|
var NavigationSchema = z.object({
|
|
214
215
|
enabled: z.boolean().default(true),
|
|
@@ -3673,6 +3674,10 @@ const knownNames: Record<string, string> = {
|
|
|
3673
3674
|
tooltip: 'Tooltip',
|
|
3674
3675
|
'radio-group': 'RadioGroup',
|
|
3675
3676
|
slider: 'Slider',
|
|
3677
|
+
sheet: 'Sheet',
|
|
3678
|
+
'dropdown-menu': 'DropdownMenu',
|
|
3679
|
+
'context-menu': 'ContextMenu',
|
|
3680
|
+
command: 'Command',
|
|
3676
3681
|
}
|
|
3677
3682
|
|
|
3678
3683
|
const descriptions: Record<string, string> = {
|
|
@@ -3697,6 +3702,10 @@ const descriptions: Record<string, string> = {
|
|
|
3697
3702
|
tooltip: 'Contextual info on hover',
|
|
3698
3703
|
'radio-group': 'Select one option from a set',
|
|
3699
3704
|
slider: 'Select a value from a range',
|
|
3705
|
+
sheet: 'Slide-out panel from the edge of the screen',
|
|
3706
|
+
'dropdown-menu': 'Menu triggered by a button with a list of actions',
|
|
3707
|
+
'context-menu': 'Right-click menu with contextual actions',
|
|
3708
|
+
command: 'Command palette for searching and executing actions',
|
|
3700
3709
|
}
|
|
3701
3710
|
|
|
3702
3711
|
function displayName(component: any): string {
|
|
@@ -3848,6 +3857,147 @@ function SelectPreview({ size }: { size?: string }) {
|
|
|
3848
3857
|
)
|
|
3849
3858
|
}
|
|
3850
3859
|
|
|
3860
|
+
function SheetPreview({ side }: { side?: string }) {
|
|
3861
|
+
const [open, setOpen] = useState(false)
|
|
3862
|
+
const s = side || 'right'
|
|
3863
|
+
const positionClasses: Record<string, string> = {
|
|
3864
|
+
top: 'inset-x-0 top-0 border-b',
|
|
3865
|
+
bottom: 'inset-x-0 bottom-0 border-t',
|
|
3866
|
+
left: 'inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
|
3867
|
+
right: 'inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
|
3868
|
+
}
|
|
3869
|
+
return (
|
|
3870
|
+
<>
|
|
3871
|
+
<button onClick={() => setOpen(true)}
|
|
3872
|
+
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors">
|
|
3873
|
+
Open Sheet
|
|
3874
|
+
</button>
|
|
3875
|
+
{open && (
|
|
3876
|
+
<div className="fixed inset-0 z-50" onClick={() => setOpen(false)}>
|
|
3877
|
+
<div className="fixed inset-0 bg-black/80" />
|
|
3878
|
+
<div className={cn('fixed z-50 gap-4 bg-background p-6 shadow-lg', positionClasses[s] || positionClasses.right)}
|
|
3879
|
+
onClick={e => e.stopPropagation()}>
|
|
3880
|
+
<div className="flex flex-col space-y-2 mb-4">
|
|
3881
|
+
<h3 className="text-lg font-semibold">Sheet Panel</h3>
|
|
3882
|
+
<p className="text-sm text-muted-foreground">This is a sheet sliding from the {s}.</p>
|
|
3883
|
+
</div>
|
|
3884
|
+
<button onClick={() => setOpen(false)}
|
|
3885
|
+
className="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity">
|
|
3886
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
3887
|
+
</button>
|
|
3888
|
+
</div>
|
|
3889
|
+
</div>
|
|
3890
|
+
)}
|
|
3891
|
+
</>
|
|
3892
|
+
)
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
function DropdownMenuPreview({ size }: { size?: string }) {
|
|
3896
|
+
const [open, setOpen] = useState(false)
|
|
3897
|
+
const sizeClasses: Record<string, string> = {
|
|
3898
|
+
sm: 'min-w-[6rem]',
|
|
3899
|
+
md: 'min-w-[8rem]',
|
|
3900
|
+
lg: 'min-w-[12rem]',
|
|
3901
|
+
}
|
|
3902
|
+
const s = size || 'md'
|
|
3903
|
+
return (
|
|
3904
|
+
<div className="relative">
|
|
3905
|
+
<button onClick={() => setOpen(!open)}
|
|
3906
|
+
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors gap-1">
|
|
3907
|
+
Actions {chevronSvg}
|
|
3908
|
+
</button>
|
|
3909
|
+
{open && (
|
|
3910
|
+
<>
|
|
3911
|
+
<div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
|
|
3912
|
+
<div className={cn('absolute top-full left-0 z-50 mt-1 rounded-md border bg-popover text-popover-foreground p-1 shadow-md animate-in fade-in-0 zoom-in-95', sizeClasses[s] || sizeClasses.md)}>
|
|
3913
|
+
<button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">
|
|
3914
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
|
3915
|
+
Edit
|
|
3916
|
+
</button>
|
|
3917
|
+
<button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">
|
|
3918
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
3919
|
+
Duplicate
|
|
3920
|
+
</button>
|
|
3921
|
+
<div className="-mx-1 my-1 h-px bg-border" />
|
|
3922
|
+
<button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none text-destructive hover:bg-destructive/10 transition-colors">
|
|
3923
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
|
|
3924
|
+
Delete
|
|
3925
|
+
</button>
|
|
3926
|
+
</div>
|
|
3927
|
+
</>
|
|
3928
|
+
)}
|
|
3929
|
+
</div>
|
|
3930
|
+
)
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
function ContextMenuPreview() {
|
|
3934
|
+
const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
|
|
3935
|
+
return (
|
|
3936
|
+
<div className="relative">
|
|
3937
|
+
<div
|
|
3938
|
+
onContextMenu={e => { e.preventDefault(); setPos({ x: e.nativeEvent.offsetX, y: e.nativeEvent.offsetY }) }}
|
|
3939
|
+
className="flex items-center justify-center w-48 h-24 rounded-lg border-2 border-dashed border-muted-foreground/25 text-sm text-muted-foreground cursor-context-menu select-none hover:border-muted-foreground/40 transition-colors"
|
|
3940
|
+
>
|
|
3941
|
+
Right-click here
|
|
3942
|
+
</div>
|
|
3943
|
+
{pos && (
|
|
3944
|
+
<>
|
|
3945
|
+
<div className="fixed inset-0 z-40" onClick={() => setPos(null)} onContextMenu={e => { e.preventDefault(); setPos(null) }} />
|
|
3946
|
+
<div style={{ position: 'absolute', top: pos.y, left: pos.x }} className="z-50 min-w-[8rem] rounded-md border bg-popover text-popover-foreground p-1 shadow-md animate-in fade-in-0 zoom-in-95">
|
|
3947
|
+
<button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">View</button>
|
|
3948
|
+
<button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">Edit</button>
|
|
3949
|
+
<div className="-mx-1 my-1 h-px bg-border" />
|
|
3950
|
+
<button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none text-destructive hover:bg-destructive/10 transition-colors">Delete</button>
|
|
3951
|
+
</div>
|
|
3952
|
+
</>
|
|
3953
|
+
)}
|
|
3954
|
+
</div>
|
|
3955
|
+
)
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
function CommandPreview() {
|
|
3959
|
+
const [query, setQuery] = useState('')
|
|
3960
|
+
const items = ['Dashboard', 'Projects', 'Team', 'Settings', 'Profile']
|
|
3961
|
+
const filtered = items.filter(i => i.toLowerCase().includes(query.toLowerCase()))
|
|
3962
|
+
return (
|
|
3963
|
+
<div className="w-72 rounded-lg border bg-popover text-popover-foreground shadow-md">
|
|
3964
|
+
<div className="flex items-center border-b px-3 h-10">
|
|
3965
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2 shrink-0 opacity-50"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
3966
|
+
<input value={query} onChange={e => setQuery(e.target.value)} placeholder="Type a command\u2026" className="flex h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" />
|
|
3967
|
+
</div>
|
|
3968
|
+
<div className="p-1 max-h-48 overflow-y-auto">
|
|
3969
|
+
{filtered.length === 0 ? (
|
|
3970
|
+
<p className="py-4 text-center text-sm text-muted-foreground">No results found.</p>
|
|
3971
|
+
) : (
|
|
3972
|
+
filtered.map(item => (
|
|
3973
|
+
<button key={item} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">{item}</button>
|
|
3974
|
+
))
|
|
3975
|
+
)}
|
|
3976
|
+
</div>
|
|
3977
|
+
</div>
|
|
3978
|
+
)
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
function TooltipPreview() {
|
|
3982
|
+
const [show, setShow] = useState(false)
|
|
3983
|
+
return (
|
|
3984
|
+
<div className="relative inline-block">
|
|
3985
|
+
<button
|
|
3986
|
+
onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
|
|
3987
|
+
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors"
|
|
3988
|
+
>
|
|
3989
|
+
Hover me
|
|
3990
|
+
</button>
|
|
3991
|
+
{show && (
|
|
3992
|
+
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50 rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground shadow-md animate-in fade-in-0 zoom-in-95">
|
|
3993
|
+
Tooltip content
|
|
3994
|
+
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-primary" />
|
|
3995
|
+
</div>
|
|
3996
|
+
)}
|
|
3997
|
+
</div>
|
|
3998
|
+
)
|
|
3999
|
+
}
|
|
4000
|
+
|
|
3851
4001
|
function Preview({ component, variant, size }: { component: any; variant?: string; size?: string }) {
|
|
3852
4002
|
const id = component.id
|
|
3853
4003
|
|
|
@@ -3958,6 +4108,12 @@ function Preview({ component, variant, size }: { component: any; variant?: strin
|
|
|
3958
4108
|
)
|
|
3959
4109
|
}
|
|
3960
4110
|
|
|
4111
|
+
if (id === 'sheet') return <SheetPreview side={variant} />
|
|
4112
|
+
if (id === 'dropdown-menu') return <DropdownMenuPreview size={size} />
|
|
4113
|
+
if (id === 'context-menu') return <ContextMenuPreview />
|
|
4114
|
+
if (id === 'command') return <CommandPreview />
|
|
4115
|
+
if (id === 'tooltip') return <TooltipPreview />
|
|
4116
|
+
|
|
3961
4117
|
const cls = resolveClasses(component, variant, size)
|
|
3962
4118
|
if (cls) {
|
|
3963
4119
|
return <div className={cn(cls)}>{displayName(component)}</div>
|
|
@@ -3992,6 +4148,10 @@ export default function ComponentShowcase({ component }: ComponentShowcaseProps)
|
|
|
3992
4148
|
if (id === 'separator') return \`<\${n} />\`
|
|
3993
4149
|
if (id === 'progress') return \`<\${n} value={60} />\`
|
|
3994
4150
|
if (id === 'avatar') return \`<\${n}>\\n <AvatarImage src="/avatar.png" />\\n <AvatarFallback>AV</AvatarFallback>\\n</\${n}>\`
|
|
4151
|
+
if (id === 'sheet') return \`<Sheet>\\n <SheetTrigger asChild>\\n <Button variant="outline">Open</Button>\\n </SheetTrigger>\\n <SheetContent>\\n <SheetHeader>\\n <SheetTitle>Title</SheetTitle>\\n <SheetDescription>Description</SheetDescription>\\n </SheetHeader>\\n </SheetContent>\\n</Sheet>\`
|
|
4152
|
+
if (id === 'dropdown-menu') return \`<DropdownMenu>\\n <DropdownMenuTrigger asChild>\\n <Button variant="outline">Open</Button>\\n </DropdownMenuTrigger>\\n <DropdownMenuContent>\\n <DropdownMenuItem>Action</DropdownMenuItem>\\n </DropdownMenuContent>\\n</DropdownMenu>\`
|
|
4153
|
+
if (id === 'context-menu') return \`<ContextMenu>\\n <ContextMenuTrigger>Right click</ContextMenuTrigger>\\n <ContextMenuContent>\\n <ContextMenuItem>Action</ContextMenuItem>\\n </ContextMenuContent>\\n</ContextMenu>\`
|
|
4154
|
+
if (id === 'command') return \`<Command>\\n <CommandInput placeholder="Type a command..." />\\n <CommandList>\\n <CommandEmpty>No results.</CommandEmpty>\\n <CommandGroup heading="Suggestions">\\n <CommandItem>Calendar</CommandItem>\\n </CommandGroup>\\n </CommandList>\\n</Command>\`
|
|
3995
4155
|
return \`<\${n} />\`
|
|
3996
4156
|
})()
|
|
3997
4157
|
|
|
@@ -5237,9 +5397,9 @@ ${imports}
|
|
|
5237
5397
|
export default function ${pageName}Page() {
|
|
5238
5398
|
${this.generateFormState(def)}
|
|
5239
5399
|
return (
|
|
5240
|
-
<
|
|
5400
|
+
<div className="${containerClass}">
|
|
5241
5401
|
${sections}
|
|
5242
|
-
</
|
|
5402
|
+
</div>
|
|
5243
5403
|
)
|
|
5244
5404
|
}
|
|
5245
5405
|
`;
|
|
@@ -5261,9 +5421,9 @@ export const metadata: Metadata = {
|
|
|
5261
5421
|
|
|
5262
5422
|
export default function ${pageName}Page() {
|
|
5263
5423
|
return (
|
|
5264
|
-
<
|
|
5424
|
+
<div className="${containerClass}">
|
|
5265
5425
|
${sections}
|
|
5266
|
-
</
|
|
5426
|
+
</div>
|
|
5267
5427
|
)
|
|
5268
5428
|
}
|
|
5269
5429
|
`;
|
|
@@ -5573,13 +5733,13 @@ ${sections}
|
|
|
5573
5733
|
*/
|
|
5574
5734
|
getContainerClass(layout) {
|
|
5575
5735
|
const layoutClasses = {
|
|
5576
|
-
centered: "
|
|
5577
|
-
"sidebar-left": "flex
|
|
5578
|
-
"sidebar-right": "flex flex-row-reverse
|
|
5579
|
-
"full-width": "
|
|
5580
|
-
grid: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6
|
|
5736
|
+
centered: "space-y-6",
|
|
5737
|
+
"sidebar-left": "flex gap-6",
|
|
5738
|
+
"sidebar-right": "flex flex-row-reverse gap-6",
|
|
5739
|
+
"full-width": "space-y-6",
|
|
5740
|
+
grid: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
5581
5741
|
};
|
|
5582
|
-
return layoutClasses[layout] || "
|
|
5742
|
+
return layoutClasses[layout] || "space-y-6";
|
|
5583
5743
|
}
|
|
5584
5744
|
/**
|
|
5585
5745
|
* Generate layout code (for root layout)
|
|
@@ -5787,22 +5947,81 @@ export function AppNav() {
|
|
|
5787
5947
|
"/forgot-password",
|
|
5788
5948
|
"/reset-password"
|
|
5789
5949
|
]);
|
|
5950
|
+
const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
|
|
5951
|
+
const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
|
|
5790
5952
|
const visibleItems = navItems.filter(
|
|
5791
|
-
(item) => item.route
|
|
5953
|
+
(item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
|
|
5792
5954
|
);
|
|
5793
|
-
const
|
|
5794
|
-
const
|
|
5955
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5956
|
+
const ungrouped = [];
|
|
5957
|
+
for (const item of visibleItems) {
|
|
5958
|
+
if (item.group) {
|
|
5959
|
+
const list = grouped.get(item.group) || [];
|
|
5960
|
+
list.push(item);
|
|
5961
|
+
grouped.set(item.group, list);
|
|
5962
|
+
} else if (item.children && item.children.length > 0) {
|
|
5963
|
+
grouped.set(item.label, [item]);
|
|
5964
|
+
} else {
|
|
5965
|
+
ungrouped.push(item);
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
const hasDropdowns = grouped.size > 0;
|
|
5969
|
+
const hasAuthItems = navItems.some((item) => authRoutes.has(item.route));
|
|
5970
|
+
const authItems = navItems.filter((item) => authRoutes.has(item.route));
|
|
5971
|
+
const signInItem = authItems.find((item) => /sign.?in|login/i.test(item.label));
|
|
5972
|
+
const signUpItem = authItems.find((item) => /sign.?up|register/i.test(item.label));
|
|
5973
|
+
const linkItems = ungrouped.map(
|
|
5795
5974
|
(item) => `<Link href="${item.route}" className={\`text-sm font-medium px-3 py-2 rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}>${item.label}</Link>`
|
|
5796
5975
|
).join("\n ");
|
|
5797
|
-
const
|
|
5798
|
-
|
|
5976
|
+
const dropdownBlocks = [];
|
|
5977
|
+
for (const [groupName, items] of grouped) {
|
|
5978
|
+
const parentItem = items.length === 1 && items[0].children ? items[0] : null;
|
|
5979
|
+
const childItems = parentItem ? parentItem.children : items;
|
|
5980
|
+
const triggerLabel = parentItem ? parentItem.label : groupName;
|
|
5981
|
+
const menuItems = childItems.map(
|
|
5982
|
+
(child) => ` <DropdownMenuItem asChild>
|
|
5983
|
+
<Link href="${child.route}" className="w-full">${child.label}</Link>
|
|
5984
|
+
</DropdownMenuItem>`
|
|
5985
|
+
).join("\n");
|
|
5986
|
+
dropdownBlocks.push(`<DropdownMenu>
|
|
5987
|
+
<DropdownMenuTrigger className="flex items-center gap-1 text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
5988
|
+
${triggerLabel}
|
|
5989
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
|
5990
|
+
</DropdownMenuTrigger>
|
|
5991
|
+
<DropdownMenuContent align="start">
|
|
5992
|
+
${menuItems}
|
|
5993
|
+
</DropdownMenuContent>
|
|
5994
|
+
</DropdownMenu>`);
|
|
5995
|
+
}
|
|
5996
|
+
const allNavElements = [linkItems, ...dropdownBlocks].filter(Boolean).join("\n ");
|
|
5997
|
+
const navItemsBlock = allNavElements ? `
|
|
5998
|
+
${allNavElements}
|
|
5799
5999
|
` : "";
|
|
6000
|
+
const authButtonsBlock = hasAuthItems && (signInItem || signUpItem) ? `${signInItem ? `
|
|
6001
|
+
<Link href="${signInItem.route}" className="text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors">${signInItem.label}</Link>` : ""}${signUpItem ? `
|
|
6002
|
+
<Link href="${signUpItem.route}" className="inline-flex items-center justify-center text-sm font-medium h-9 px-4 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">${signUpItem.label}</Link>` : ""}` : "";
|
|
6003
|
+
const dropdownImport = hasDropdowns ? `
|
|
6004
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'` : "";
|
|
5800
6005
|
const appName = this.escapeString(this.config.name);
|
|
6006
|
+
const mobileNavItems = [...ungrouped];
|
|
6007
|
+
for (const [, items] of grouped) {
|
|
6008
|
+
const parentItem = items.length === 1 && items[0].children ? items[0] : null;
|
|
6009
|
+
const childItems = parentItem ? parentItem.children : items;
|
|
6010
|
+
mobileNavItems.push(...childItems);
|
|
6011
|
+
}
|
|
6012
|
+
const mobileLinks = mobileNavItems.map(
|
|
6013
|
+
(item) => `<Link href="${item.route}" onClick={() => setMobileOpen(false)} className={\`block text-sm font-medium px-3 py-2 rounded-md transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}>${item.label}</Link>`
|
|
6014
|
+
).join("\n ");
|
|
6015
|
+
const mobileAuthBlock = hasAuthItems && (signInItem || signUpItem) ? `
|
|
6016
|
+
<div className="border-t pt-3 mt-2 space-y-1">${signInItem ? `
|
|
6017
|
+
<Link href="${signInItem.route}" onClick={() => setMobileOpen(false)} className="block text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors">${signInItem.label}</Link>` : ""}${signUpItem ? `
|
|
6018
|
+
<Link href="${signUpItem.route}" onClick={() => setMobileOpen(false)} className="block text-sm font-medium px-3 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors text-center">${signUpItem.label}</Link>` : ""}
|
|
6019
|
+
</div>` : "";
|
|
5801
6020
|
return `'use client'
|
|
5802
6021
|
|
|
5803
6022
|
import Link from 'next/link'
|
|
5804
6023
|
import { usePathname } from 'next/navigation'
|
|
5805
|
-
import { useEffect, useState } from 'react'
|
|
6024
|
+
import { useEffect, useState } from 'react'${dropdownImport}
|
|
5806
6025
|
|
|
5807
6026
|
function ThemeToggle() {
|
|
5808
6027
|
const [dark, setDark] = useState(false)
|
|
@@ -5832,6 +6051,12 @@ function ThemeToggle() {
|
|
|
5832
6051
|
|
|
5833
6052
|
export function Header() {
|
|
5834
6053
|
const pathname = usePathname()
|
|
6054
|
+
const [mobileOpen, setMobileOpen] = useState(false)
|
|
6055
|
+
|
|
6056
|
+
useEffect(() => {
|
|
6057
|
+
setMobileOpen(false)
|
|
6058
|
+
}, [pathname])
|
|
6059
|
+
|
|
5835
6060
|
if (pathname?.startsWith('/design-system')) return null
|
|
5836
6061
|
return (
|
|
5837
6062
|
<>
|
|
@@ -5841,12 +6066,30 @@ export function Header() {
|
|
|
5841
6066
|
<Link href="/" className="flex items-center gap-2 text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors shrink-0">
|
|
5842
6067
|
${appName}
|
|
5843
6068
|
</Link>
|
|
5844
|
-
<div className="flex items-center gap-1">${navItemsBlock}</div>
|
|
6069
|
+
<div className="hidden md:flex items-center gap-1">${navItemsBlock}</div>
|
|
5845
6070
|
</div>
|
|
5846
|
-
<div className="flex items-center gap-1"
|
|
6071
|
+
<div className="flex items-center gap-1">${authButtonsBlock}
|
|
5847
6072
|
<ThemeToggle />
|
|
6073
|
+
<button
|
|
6074
|
+
onClick={() => setMobileOpen(!mobileOpen)}
|
|
6075
|
+
className="flex md:hidden items-center justify-center w-9 h-9 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
6076
|
+
aria-label="Toggle menu"
|
|
6077
|
+
>
|
|
6078
|
+
{mobileOpen ? (
|
|
6079
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
6080
|
+
) : (
|
|
6081
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
|
6082
|
+
)}
|
|
6083
|
+
</button>
|
|
5848
6084
|
</div>
|
|
5849
6085
|
</div>
|
|
6086
|
+
{mobileOpen && (
|
|
6087
|
+
<div className="md:hidden border-t bg-background">
|
|
6088
|
+
<div className="mx-auto max-w-7xl px-4 py-3 space-y-1">
|
|
6089
|
+
${mobileLinks}${mobileAuthBlock}
|
|
6090
|
+
</div>
|
|
6091
|
+
</div>
|
|
6092
|
+
)}
|
|
5850
6093
|
</nav>
|
|
5851
6094
|
<Link
|
|
5852
6095
|
href="/design-system"
|
|
@@ -5865,22 +6108,174 @@ export function Header() {
|
|
|
5865
6108
|
*/
|
|
5866
6109
|
generateSharedFooterCode() {
|
|
5867
6110
|
const appName = this.escapeString(this.config.name);
|
|
6111
|
+
const navItems = this.config.navigation?.items || [];
|
|
6112
|
+
const authRoutes = /* @__PURE__ */ new Set([
|
|
6113
|
+
"/login",
|
|
6114
|
+
"/signin",
|
|
6115
|
+
"/sign-in",
|
|
6116
|
+
"/signup",
|
|
6117
|
+
"/sign-up",
|
|
6118
|
+
"/register",
|
|
6119
|
+
"/forgot-password",
|
|
6120
|
+
"/reset-password"
|
|
6121
|
+
]);
|
|
6122
|
+
const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
|
|
6123
|
+
const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
|
|
6124
|
+
const appLinks = navItems.filter(
|
|
6125
|
+
(item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
|
|
6126
|
+
).slice(0, 4);
|
|
6127
|
+
const linkElements = appLinks.map(
|
|
6128
|
+
(item) => ` <Link href="${item.route}" className="text-sm text-muted-foreground hover:text-foreground transition-colors">${item.label}</Link>`
|
|
6129
|
+
).join("\n");
|
|
6130
|
+
const marketingLinks = navItems.filter((item) => marketingRoutes.has(item.route) && item.route !== "/").slice(0, 3);
|
|
6131
|
+
const marketingLinkElements = marketingLinks.map(
|
|
6132
|
+
(item) => ` <Link href="${item.route}" className="text-sm text-muted-foreground hover:text-foreground transition-colors">${item.label}</Link>`
|
|
6133
|
+
).join("\n");
|
|
6134
|
+
const hasMarketingLinks = marketingLinks.length > 0;
|
|
6135
|
+
const companyColumn = hasMarketingLinks ? ` <div className="flex flex-col space-y-3">
|
|
6136
|
+
<p className="text-sm font-medium text-foreground">Company</p>
|
|
6137
|
+
${marketingLinkElements}
|
|
6138
|
+
</div>` : "";
|
|
5868
6139
|
return `'use client'
|
|
5869
6140
|
|
|
6141
|
+
import Link from 'next/link'
|
|
5870
6142
|
import { usePathname } from 'next/navigation'
|
|
5871
6143
|
|
|
5872
6144
|
export function Footer() {
|
|
5873
6145
|
const pathname = usePathname()
|
|
5874
6146
|
if (pathname?.startsWith('/design-system')) return null
|
|
5875
6147
|
return (
|
|
5876
|
-
<footer className="border-t">
|
|
5877
|
-
<div className="mx-auto
|
|
5878
|
-
<
|
|
5879
|
-
|
|
6148
|
+
<footer className="border-t bg-muted/30">
|
|
6149
|
+
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
6150
|
+
<div className="flex flex-col gap-8 py-8 md:flex-row md:justify-between">
|
|
6151
|
+
<div className="space-y-3">
|
|
6152
|
+
<Link href="/" className="text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors">
|
|
6153
|
+
${appName}
|
|
6154
|
+
</Link>
|
|
6155
|
+
<p className="text-sm text-muted-foreground max-w-xs">
|
|
6156
|
+
Modern project management for teams of all sizes.
|
|
6157
|
+
</p>
|
|
6158
|
+
</div>
|
|
6159
|
+
<div className="flex gap-12">
|
|
6160
|
+
<div className="flex flex-col space-y-3">
|
|
6161
|
+
<p className="text-sm font-medium text-foreground">Product</p>
|
|
6162
|
+
${linkElements}
|
|
6163
|
+
</div>
|
|
6164
|
+
${companyColumn}
|
|
6165
|
+
</div>
|
|
6166
|
+
</div>
|
|
6167
|
+
<div className="flex items-center justify-between border-t py-4 text-xs text-muted-foreground">
|
|
6168
|
+
<p>{'\xA9'} {new Date().getFullYear()} ${appName}. All rights reserved.</p>
|
|
6169
|
+
<div className="flex gap-4">
|
|
6170
|
+
<span>Privacy Policy</span>
|
|
6171
|
+
<span>Terms of Service</span>
|
|
6172
|
+
</div>
|
|
6173
|
+
</div>
|
|
5880
6174
|
</div>
|
|
5881
6175
|
</footer>
|
|
5882
6176
|
)
|
|
5883
6177
|
}
|
|
6178
|
+
`;
|
|
6179
|
+
}
|
|
6180
|
+
/**
|
|
6181
|
+
* Generate shared Sidebar component code for components/shared/sidebar.tsx.
|
|
6182
|
+
* Used when navigation.type is 'sidebar' or 'both'.
|
|
6183
|
+
*/
|
|
6184
|
+
generateSharedSidebarCode() {
|
|
6185
|
+
const navItems = this.config.navigation?.items || [];
|
|
6186
|
+
const authRoutes = /* @__PURE__ */ new Set([
|
|
6187
|
+
"/login",
|
|
6188
|
+
"/signin",
|
|
6189
|
+
"/sign-in",
|
|
6190
|
+
"/signup",
|
|
6191
|
+
"/sign-up",
|
|
6192
|
+
"/register",
|
|
6193
|
+
"/forgot-password",
|
|
6194
|
+
"/reset-password"
|
|
6195
|
+
]);
|
|
6196
|
+
const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
|
|
6197
|
+
const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
|
|
6198
|
+
const visibleItems = navItems.filter(
|
|
6199
|
+
(item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
|
|
6200
|
+
);
|
|
6201
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6202
|
+
const ungrouped = [];
|
|
6203
|
+
for (const item of visibleItems) {
|
|
6204
|
+
if (item.group) {
|
|
6205
|
+
const list = grouped.get(item.group) || [];
|
|
6206
|
+
list.push(item);
|
|
6207
|
+
grouped.set(item.group, list);
|
|
6208
|
+
} else {
|
|
6209
|
+
ungrouped.push(item);
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
const linkItems = ungrouped.map(
|
|
6213
|
+
(item) => ` <Link
|
|
6214
|
+
href="${item.route}"
|
|
6215
|
+
className={\`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}
|
|
6216
|
+
>
|
|
6217
|
+
${item.label}
|
|
6218
|
+
</Link>`
|
|
6219
|
+
).join("\n");
|
|
6220
|
+
const groupBlocks = [];
|
|
6221
|
+
for (const [groupName, items] of grouped) {
|
|
6222
|
+
const groupLinks = items.map(
|
|
6223
|
+
(item) => ` <Link
|
|
6224
|
+
href="${item.route}"
|
|
6225
|
+
className={\`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground font-medium' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}
|
|
6226
|
+
>
|
|
6227
|
+
${item.label}
|
|
6228
|
+
</Link>`
|
|
6229
|
+
).join("\n");
|
|
6230
|
+
groupBlocks.push(` <div className="space-y-1">
|
|
6231
|
+
<p className="px-3 py-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70">${groupName}</p>
|
|
6232
|
+
${groupLinks}
|
|
6233
|
+
</div>`);
|
|
6234
|
+
}
|
|
6235
|
+
const allSections = [linkItems, ...groupBlocks].filter(Boolean).join("\n");
|
|
6236
|
+
const appName = this.escapeString(this.config.name);
|
|
6237
|
+
return `'use client'
|
|
6238
|
+
|
|
6239
|
+
import Link from 'next/link'
|
|
6240
|
+
import { usePathname } from 'next/navigation'
|
|
6241
|
+
import { useState } from 'react'
|
|
6242
|
+
|
|
6243
|
+
export function Sidebar() {
|
|
6244
|
+
const pathname = usePathname()
|
|
6245
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
6246
|
+
|
|
6247
|
+
if (pathname?.startsWith('/design-system')) return null
|
|
6248
|
+
|
|
6249
|
+
return (
|
|
6250
|
+
<aside className={\`shrink-0 border-r bg-muted/30 transition-all duration-200 \${collapsed ? 'w-16' : 'w-64'}\`}>
|
|
6251
|
+
<div className="flex h-14 items-center justify-between border-b px-4">
|
|
6252
|
+
{!collapsed && (
|
|
6253
|
+
<Link href="/" className="text-sm font-semibold text-foreground truncate">
|
|
6254
|
+
${appName}
|
|
6255
|
+
</Link>
|
|
6256
|
+
)}
|
|
6257
|
+
<button
|
|
6258
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
6259
|
+
className="flex items-center justify-center w-8 h-8 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
6260
|
+
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
6261
|
+
>
|
|
6262
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
6263
|
+
{collapsed ? (
|
|
6264
|
+
<><path d="m9 18 6-6-6-6"/></>
|
|
6265
|
+
) : (
|
|
6266
|
+
<><path d="m15 18-6-6 6-6"/></>
|
|
6267
|
+
)}
|
|
6268
|
+
</svg>
|
|
6269
|
+
</button>
|
|
6270
|
+
</div>
|
|
6271
|
+
{!collapsed && (
|
|
6272
|
+
<nav className="flex flex-col gap-1 p-3">
|
|
6273
|
+
${allSections}
|
|
6274
|
+
</nav>
|
|
6275
|
+
)}
|
|
6276
|
+
</aside>
|
|
6277
|
+
)
|
|
6278
|
+
}
|
|
5884
6279
|
`;
|
|
5885
6280
|
}
|
|
5886
6281
|
/**
|
|
@@ -6715,15 +7110,18 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6715
7110
|
const code = await this.pageGenerator.generateLayout(layout, appType, { skipNav: true });
|
|
6716
7111
|
await this.writeFile("app/layout.tsx", code);
|
|
6717
7112
|
if (this.config.navigation?.enabled && appType === "multi-page") {
|
|
6718
|
-
const
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
7113
|
+
const navType = this.config.navigation.type || "header";
|
|
7114
|
+
if (navType === "header" || navType === "both") {
|
|
7115
|
+
const headerCode = this.generateInitialHeaderCode();
|
|
7116
|
+
await generateSharedComponent(this.projectRoot, {
|
|
7117
|
+
name: "Header",
|
|
7118
|
+
type: "layout",
|
|
7119
|
+
code: headerCode,
|
|
7120
|
+
description: "Main site header with navigation and theme toggle",
|
|
7121
|
+
usedIn: ["app/layout.tsx"]
|
|
7122
|
+
});
|
|
7123
|
+
}
|
|
7124
|
+
const footerCode = this.generateInitialFooterCode();
|
|
6727
7125
|
await generateSharedComponent(this.projectRoot, {
|
|
6728
7126
|
name: "Footer",
|
|
6729
7127
|
type: "layout",
|
|
@@ -6731,6 +7129,16 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6731
7129
|
description: "Site footer",
|
|
6732
7130
|
usedIn: ["app/layout.tsx"]
|
|
6733
7131
|
});
|
|
7132
|
+
if (navType === "sidebar" || navType === "both") {
|
|
7133
|
+
const sidebarCode = this.pageGenerator.generateSharedSidebarCode();
|
|
7134
|
+
await generateSharedComponent(this.projectRoot, {
|
|
7135
|
+
name: "Sidebar",
|
|
7136
|
+
type: "layout",
|
|
7137
|
+
code: sidebarCode,
|
|
7138
|
+
description: "Vertical sidebar navigation with collapsible sections",
|
|
7139
|
+
usedIn: ["app/(app)/layout.tsx"]
|
|
7140
|
+
});
|
|
7141
|
+
}
|
|
6734
7142
|
await integrateSharedLayoutIntoRootLayout(this.projectRoot);
|
|
6735
7143
|
}
|
|
6736
7144
|
}
|
|
@@ -7205,6 +7613,134 @@ Recommendations are added here when you use \`coherent chat\` and the AI suggest
|
|
|
7205
7613
|
this.pageGenerator.updateConfig(newConfig);
|
|
7206
7614
|
this.tailwindGenerator.updateConfig(newConfig);
|
|
7207
7615
|
}
|
|
7616
|
+
/**
|
|
7617
|
+
* Initial header for coherent init — Coherent Design Method branding.
|
|
7618
|
+
* Replaced by app-branded header on first `coherent chat` via regenerateLayout().
|
|
7619
|
+
*/
|
|
7620
|
+
generateInitialHeaderCode() {
|
|
7621
|
+
return `'use client'
|
|
7622
|
+
|
|
7623
|
+
import Link from 'next/link'
|
|
7624
|
+
import { usePathname } from 'next/navigation'
|
|
7625
|
+
import { useEffect, useState } from 'react'
|
|
7626
|
+
|
|
7627
|
+
function ThemeToggle() {
|
|
7628
|
+
const [dark, setDark] = useState(false)
|
|
7629
|
+
useEffect(() => {
|
|
7630
|
+
setDark(document.documentElement.classList.contains('dark'))
|
|
7631
|
+
}, [])
|
|
7632
|
+
const toggle = () => {
|
|
7633
|
+
const next = !dark
|
|
7634
|
+
setDark(next)
|
|
7635
|
+
document.documentElement.classList.toggle('dark', next)
|
|
7636
|
+
}
|
|
7637
|
+
return (
|
|
7638
|
+
<button
|
|
7639
|
+
onClick={toggle}
|
|
7640
|
+
className="flex items-center justify-center w-9 h-9 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
7641
|
+
title={dark ? 'Switch to light theme' : 'Switch to dark theme'}
|
|
7642
|
+
aria-label="Toggle theme"
|
|
7643
|
+
>
|
|
7644
|
+
{dark ? (
|
|
7645
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
|
7646
|
+
) : (
|
|
7647
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
|
7648
|
+
)}
|
|
7649
|
+
</button>
|
|
7650
|
+
)
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7653
|
+
export function Header() {
|
|
7654
|
+
const pathname = usePathname()
|
|
7655
|
+
if (pathname?.startsWith('/design-system')) return null
|
|
7656
|
+
return (
|
|
7657
|
+
<>
|
|
7658
|
+
<nav className="sticky top-0 z-50 shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
7659
|
+
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
|
7660
|
+
<div className="flex items-center gap-3">
|
|
7661
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0 text-primary">
|
|
7662
|
+
<path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="currentColor"/>
|
|
7663
|
+
</svg>
|
|
7664
|
+
<Link href="/" className="text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors">
|
|
7665
|
+
Coherent Design Method
|
|
7666
|
+
</Link>
|
|
7667
|
+
</div>
|
|
7668
|
+
<div className="flex items-center gap-1">
|
|
7669
|
+
<ThemeToggle />
|
|
7670
|
+
</div>
|
|
7671
|
+
</div>
|
|
7672
|
+
</nav>
|
|
7673
|
+
<Link
|
|
7674
|
+
href="/design-system"
|
|
7675
|
+
className="fixed bottom-4 right-4 z-50 flex items-center gap-2 rounded-full border border-white/20 bg-black/60 backdrop-blur-md text-white px-4 py-2 text-xs shadow-sm hover:bg-black/80 transition-all"
|
|
7676
|
+
title="Design System"
|
|
7677
|
+
>
|
|
7678
|
+
Design System
|
|
7679
|
+
</Link>
|
|
7680
|
+
</>
|
|
7681
|
+
)
|
|
7682
|
+
}
|
|
7683
|
+
`;
|
|
7684
|
+
}
|
|
7685
|
+
/**
|
|
7686
|
+
* Initial footer for coherent init — Coherent Design Method branding.
|
|
7687
|
+
* Replaced by app-branded footer on first `coherent chat` via regenerateLayout().
|
|
7688
|
+
*/
|
|
7689
|
+
generateInitialFooterCode() {
|
|
7690
|
+
return `'use client'
|
|
7691
|
+
|
|
7692
|
+
import Link from 'next/link'
|
|
7693
|
+
import { usePathname } from 'next/navigation'
|
|
7694
|
+
|
|
7695
|
+
export function Footer() {
|
|
7696
|
+
const pathname = usePathname()
|
|
7697
|
+
if (pathname?.startsWith('/design-system')) return null
|
|
7698
|
+
return (
|
|
7699
|
+
<footer className="border-t bg-background">
|
|
7700
|
+
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
7701
|
+
<div className="grid grid-cols-2 gap-8 py-10 md:grid-cols-4">
|
|
7702
|
+
<div className="col-span-2 md:col-span-1">
|
|
7703
|
+
<div className="flex items-center gap-2 mb-2">
|
|
7704
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0 text-primary">
|
|
7705
|
+
<path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="currentColor"/>
|
|
7706
|
+
</svg>
|
|
7707
|
+
<span className="text-sm font-semibold">Coherent Design Method</span>
|
|
7708
|
+
</div>
|
|
7709
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
7710
|
+
Design once. Stay consistent everywhere.
|
|
7711
|
+
</p>
|
|
7712
|
+
</div>
|
|
7713
|
+
<div>
|
|
7714
|
+
<h4 className="mb-3 text-sm font-semibold">Product</h4>
|
|
7715
|
+
<ul className="space-y-2">
|
|
7716
|
+
<li><Link href="/design-system" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Design System</Link></li>
|
|
7717
|
+
<li><a href="https://getcoherent.design" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Website</a></li>
|
|
7718
|
+
<li><a href="https://github.com/skovtun/coherent-design-method" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">GitHub</a></li>
|
|
7719
|
+
</ul>
|
|
7720
|
+
</div>
|
|
7721
|
+
<div>
|
|
7722
|
+
<h4 className="mb-3 text-sm font-semibold">Legal</h4>
|
|
7723
|
+
<ul className="space-y-2">
|
|
7724
|
+
<li><span className="text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors">Terms of Use</span></li>
|
|
7725
|
+
<li><span className="text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors">Privacy Policy</span></li>
|
|
7726
|
+
</ul>
|
|
7727
|
+
</div>
|
|
7728
|
+
<div>
|
|
7729
|
+
<h4 className="mb-3 text-sm font-semibold">Author</h4>
|
|
7730
|
+
<ul className="space-y-2">
|
|
7731
|
+
<li><a href="https://www.linkedin.com/in/sergeikovtun/" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Sergei Kovtun</a></li>
|
|
7732
|
+
</ul>
|
|
7733
|
+
</div>
|
|
7734
|
+
</div>
|
|
7735
|
+
<div className="border-t py-6 text-center text-xs text-muted-foreground">
|
|
7736
|
+
{'\\u00A9'} {new Date().getFullYear()} Coherent Design Method. All rights reserved.
|
|
7737
|
+
</div>
|
|
7738
|
+
</div>
|
|
7739
|
+
</footer>
|
|
7740
|
+
)
|
|
7741
|
+
}
|
|
7742
|
+
`;
|
|
7743
|
+
}
|
|
7208
7744
|
};
|
|
7209
7745
|
|
|
7210
7746
|
// src/generators/templates/pages/_shared.ts
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"description": "Core design system engine for Coherent",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -32,12 +32,6 @@
|
|
|
32
32
|
],
|
|
33
33
|
"author": "Coherent Design Method",
|
|
34
34
|
"license": "MIT",
|
|
35
|
-
"scripts": {
|
|
36
|
-
"dev": "tsup --watch",
|
|
37
|
-
"build": "tsup",
|
|
38
|
-
"typecheck": "tsc --noEmit",
|
|
39
|
-
"test": "vitest"
|
|
40
|
-
},
|
|
41
35
|
"dependencies": {
|
|
42
36
|
"handlebars": "^4.7.8",
|
|
43
37
|
"zod": "^3.22.4"
|
|
@@ -47,5 +41,11 @@
|
|
|
47
41
|
"tsup": "^8.0.1",
|
|
48
42
|
"typescript": "^5.3.3",
|
|
49
43
|
"vitest": "^1.2.1"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|