create-strayl-web-app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/template/README.md +290 -0
- package/template/components.json +24 -0
- package/template/package.json +56 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/google-logo.svg +6 -0
- package/template/public/logo-dark.ico +0 -0
- package/template/public/logo-dark.webp +0 -0
- package/template/public/logo-light.ico +0 -0
- package/template/public/logo-light.webp +0 -0
- package/template/public/manifest.json +16 -0
- package/template/public/robots.txt +3 -0
- package/template/src/components/Header.tsx +76 -0
- package/template/src/components/language-switcher.tsx +38 -0
- package/template/src/components/theme-provider.tsx +14 -0
- package/template/src/components/themed-logo.tsx +44 -0
- package/template/src/components/ui/accordion.tsx +69 -0
- package/template/src/components/ui/alert-dialog.tsx +169 -0
- package/template/src/components/ui/alert.tsx +80 -0
- package/template/src/components/ui/autocomplete.tsx +301 -0
- package/template/src/components/ui/avatar.tsx +46 -0
- package/template/src/components/ui/badge.tsx +60 -0
- package/template/src/components/ui/breadcrumb.tsx +112 -0
- package/template/src/components/ui/button.tsx +73 -0
- package/template/src/components/ui/card.tsx +244 -0
- package/template/src/components/ui/checkbox-group.tsx +16 -0
- package/template/src/components/ui/checkbox.tsx +60 -0
- package/template/src/components/ui/collapsible.tsx +45 -0
- package/template/src/components/ui/combobox.tsx +415 -0
- package/template/src/components/ui/command.tsx +264 -0
- package/template/src/components/ui/dialog.tsx +196 -0
- package/template/src/components/ui/empty.tsx +127 -0
- package/template/src/components/ui/field.tsx +74 -0
- package/template/src/components/ui/fieldset.tsx +29 -0
- package/template/src/components/ui/form.tsx +17 -0
- package/template/src/components/ui/frame.tsx +82 -0
- package/template/src/components/ui/group.tsx +97 -0
- package/template/src/components/ui/input-group.tsx +101 -0
- package/template/src/components/ui/input.tsx +66 -0
- package/template/src/components/ui/kbd.tsx +28 -0
- package/template/src/components/ui/label.tsx +28 -0
- package/template/src/components/ui/menu.tsx +310 -0
- package/template/src/components/ui/meter.tsx +67 -0
- package/template/src/components/ui/number-field.tsx +160 -0
- package/template/src/components/ui/pagination.tsx +136 -0
- package/template/src/components/ui/popover.tsx +104 -0
- package/template/src/components/ui/preview-card.tsx +55 -0
- package/template/src/components/ui/progress.tsx +81 -0
- package/template/src/components/ui/radio-group.tsx +36 -0
- package/template/src/components/ui/scroll-area.tsx +64 -0
- package/template/src/components/ui/select.tsx +180 -0
- package/template/src/components/ui/separator.tsx +23 -0
- package/template/src/components/ui/sheet.tsx +203 -0
- package/template/src/components/ui/sidebar.tsx +743 -0
- package/template/src/components/ui/skeleton.tsx +16 -0
- package/template/src/components/ui/slider.tsx +74 -0
- package/template/src/components/ui/spinner.tsx +18 -0
- package/template/src/components/ui/switch.tsx +27 -0
- package/template/src/components/ui/table.tsx +126 -0
- package/template/src/components/ui/tabs.tsx +87 -0
- package/template/src/components/ui/textarea.tsx +51 -0
- package/template/src/components/ui/toast.tsx +269 -0
- package/template/src/components/ui/toggle-group.tsx +102 -0
- package/template/src/components/ui/toggle.tsx +45 -0
- package/template/src/components/ui/toolbar.tsx +83 -0
- package/template/src/components/ui/tooltip.tsx +65 -0
- package/template/src/hooks/use-mobile.ts +21 -0
- package/template/src/lib/i18n.ts +70 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/routeTree.gen.ts +68 -0
- package/template/src/router.tsx +17 -0
- package/template/src/routes/__root.tsx +62 -0
- package/template/src/routes/index.tsx +71 -0
- package/template/src/styles.css +121 -0
- package/template/tsconfig.json +28 -0
- package/template/vite.config.ts +30 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
4
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
|
|
5
|
+
import type { VariantProps } from "class-variance-authority";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
import { Separator } from "@/components/ui/separator";
|
|
10
|
+
import {
|
|
11
|
+
Toggle as ToggleComponent,
|
|
12
|
+
type toggleVariants,
|
|
13
|
+
} from "@/components/ui/toggle";
|
|
14
|
+
|
|
15
|
+
const ToggleGroupContext = React.createContext<
|
|
16
|
+
VariantProps<typeof toggleVariants>
|
|
17
|
+
>({
|
|
18
|
+
size: "default",
|
|
19
|
+
variant: "default",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function ToggleGroup({
|
|
23
|
+
className,
|
|
24
|
+
variant = "default",
|
|
25
|
+
size = "default",
|
|
26
|
+
orientation = "horizontal",
|
|
27
|
+
children,
|
|
28
|
+
...props
|
|
29
|
+
}: ToggleGroupPrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
30
|
+
return (
|
|
31
|
+
<ToggleGroupPrimitive
|
|
32
|
+
className={cn(
|
|
33
|
+
"flex w-fit *:focus-visible:z-10 dark:*:[[data-slot=separator]:has(+[data-slot=toggle]:hover)]:before:bg-input/64 dark:*:[[data-slot=separator]:has(+[data-slot=toggle][data-pressed])]:before:bg-input dark:*:[[data-slot=toggle]:hover+[data-slot=separator]]:before:bg-input/64 dark:*:[[data-slot=toggle][data-pressed]+[data-slot=separator]]:before:bg-input",
|
|
34
|
+
orientation === "horizontal"
|
|
35
|
+
? "*:pointer-coarse:after:min-w-auto"
|
|
36
|
+
: "*:pointer-coarse:after:min-h-auto",
|
|
37
|
+
variant === "default"
|
|
38
|
+
? "gap-0.5"
|
|
39
|
+
: orientation === "horizontal"
|
|
40
|
+
? "*:not-first:not-data-[slot=separator]:before:-start-[0.5px] *:not-last:not-data-[slot=separator]:before:-end-[0.5px] *:not-first:rounded-s-none *:not-last:rounded-e-none *:not-first:border-s-0 *:not-last:border-e-0 *:not-first:before:rounded-s-none *:not-last:before:rounded-e-none"
|
|
41
|
+
: "*:not-first:not-data-[slot=separator]:before:-top-[0.5px] *:not-last:not-data-[slot=separator]:before:-bottom-[0.5px] flex-col *:not-first:rounded-t-none *:not-last:rounded-b-none *:not-first:border-t-0 *:not-last:border-b-0 *:not-first:before:rounded-t-none *:not-last:before:rounded-b-none *:data-[slot=toggle]:not-last:before:hidden dark:*:last:before:hidden dark:*:first:before:block",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
data-size={size}
|
|
45
|
+
data-slot="toggle-group"
|
|
46
|
+
data-variant={variant}
|
|
47
|
+
orientation={orientation}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<ToggleGroupContext.Provider value={{ size, variant }}>
|
|
51
|
+
{children}
|
|
52
|
+
</ToggleGroupContext.Provider>
|
|
53
|
+
</ToggleGroupPrimitive>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Toggle({
|
|
58
|
+
className,
|
|
59
|
+
children,
|
|
60
|
+
variant,
|
|
61
|
+
size,
|
|
62
|
+
...props
|
|
63
|
+
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
64
|
+
const context = React.useContext(ToggleGroupContext);
|
|
65
|
+
|
|
66
|
+
const resolvedVariant = context.variant || variant;
|
|
67
|
+
const resolvedSize = context.size || size;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ToggleComponent
|
|
71
|
+
className={className}
|
|
72
|
+
data-size={resolvedSize}
|
|
73
|
+
data-variant={resolvedVariant}
|
|
74
|
+
size={resolvedSize}
|
|
75
|
+
variant={resolvedVariant}
|
|
76
|
+
{...props}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</ToggleComponent>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ToggleGroupSeparator({
|
|
84
|
+
className,
|
|
85
|
+
orientation = "vertical",
|
|
86
|
+
...props
|
|
87
|
+
}: {
|
|
88
|
+
className?: string;
|
|
89
|
+
} & React.ComponentProps<typeof Separator>) {
|
|
90
|
+
return (
|
|
91
|
+
<Separator
|
|
92
|
+
className={cn(
|
|
93
|
+
"pointer-events-none relative bg-input before:absolute before:inset-0 dark:before:bg-input/32",
|
|
94
|
+
className,
|
|
95
|
+
)}
|
|
96
|
+
orientation={orientation}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ToggleGroup, Toggle, Toggle as ToggleGroupItem, ToggleGroupSeparator };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const toggleVariants = cva(
|
|
9
|
+
"[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
10
|
+
{
|
|
11
|
+
defaultVariants: {
|
|
12
|
+
size: "default",
|
|
13
|
+
variant: "default",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
default: "h-9 min-w-9 px-[calc(--spacing(2)-1px)] sm:h-8 sm:min-w-8",
|
|
18
|
+
lg: "h-10 min-w-10 px-[calc(--spacing(2.5)-1px)] sm:h-9 sm:min-w-9",
|
|
19
|
+
sm: "h-8 min-w-8 px-[calc(--spacing(1.5)-1px)] sm:h-7 sm:min-w-7",
|
|
20
|
+
},
|
|
21
|
+
variant: {
|
|
22
|
+
default: "border-transparent",
|
|
23
|
+
outline:
|
|
24
|
+
"border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/6%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function Toggle({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
...props
|
|
35
|
+
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
36
|
+
return (
|
|
37
|
+
<TogglePrimitive
|
|
38
|
+
className={cn(toggleVariants({ className, size, variant }))}
|
|
39
|
+
data-slot="toggle"
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { Toggle, toggleVariants };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Toolbar({ className, ...props }: ToolbarPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<ToolbarPrimitive.Root
|
|
10
|
+
className={cn(
|
|
11
|
+
"relative flex gap-2 rounded-xl border bg-card not-dark:bg-clip-padding p-1 text-card-foreground",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
data-slot="toolbar"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ToolbarButton({ className, ...props }: ToolbarPrimitive.Button.Props) {
|
|
21
|
+
return (
|
|
22
|
+
<ToolbarPrimitive.Button
|
|
23
|
+
className={cn(className)}
|
|
24
|
+
data-slot="toolbar-button"
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ToolbarLink({ className, ...props }: ToolbarPrimitive.Link.Props) {
|
|
31
|
+
return (
|
|
32
|
+
<ToolbarPrimitive.Link
|
|
33
|
+
className={cn(className)}
|
|
34
|
+
data-slot="toolbar-link"
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ToolbarInput({ className, ...props }: ToolbarPrimitive.Input.Props) {
|
|
41
|
+
return (
|
|
42
|
+
<ToolbarPrimitive.Input
|
|
43
|
+
className={cn(className)}
|
|
44
|
+
data-slot="toolbar-input"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ToolbarGroup({ className, ...props }: ToolbarPrimitive.Group.Props) {
|
|
51
|
+
return (
|
|
52
|
+
<ToolbarPrimitive.Group
|
|
53
|
+
className={cn("flex items-center gap-1", className)}
|
|
54
|
+
data-slot="toolbar-group"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ToolbarSeparator({
|
|
61
|
+
className,
|
|
62
|
+
...props
|
|
63
|
+
}: ToolbarPrimitive.Separator.Props) {
|
|
64
|
+
return (
|
|
65
|
+
<ToolbarPrimitive.Separator
|
|
66
|
+
className={cn(
|
|
67
|
+
"shrink-0 bg-border data-[orientation=horizontal]:my-0.5 data-[orientation=vertical]:my-1.5 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
data-slot="toolbar-separator"
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
Toolbar,
|
|
78
|
+
ToolbarGroup,
|
|
79
|
+
ToolbarSeparator,
|
|
80
|
+
ToolbarButton,
|
|
81
|
+
ToolbarLink,
|
|
82
|
+
ToolbarInput,
|
|
83
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const TooltipCreateHandle = TooltipPrimitive.createHandle;
|
|
8
|
+
|
|
9
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
10
|
+
|
|
11
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
12
|
+
|
|
13
|
+
function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) {
|
|
14
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function TooltipPopup({
|
|
18
|
+
className,
|
|
19
|
+
align = "center",
|
|
20
|
+
sideOffset = 4,
|
|
21
|
+
side = "top",
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}: TooltipPrimitive.Popup.Props & {
|
|
25
|
+
align?: TooltipPrimitive.Positioner.Props["align"];
|
|
26
|
+
side?: TooltipPrimitive.Positioner.Props["side"];
|
|
27
|
+
sideOffset?: TooltipPrimitive.Positioner.Props["sideOffset"];
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<TooltipPrimitive.Portal>
|
|
31
|
+
<TooltipPrimitive.Positioner
|
|
32
|
+
align={align}
|
|
33
|
+
className="z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none"
|
|
34
|
+
data-slot="tooltip-positioner"
|
|
35
|
+
side={side}
|
|
36
|
+
sideOffset={sideOffset}
|
|
37
|
+
>
|
|
38
|
+
<TooltipPrimitive.Popup
|
|
39
|
+
className={cn(
|
|
40
|
+
"relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) text-balance rounded-md border bg-popover not-dark:bg-clip-padding text-popover-foreground text-xs shadow-md/5 transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 data-instant:duration-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
data-slot="tooltip-popup"
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<TooltipPrimitive.Viewport
|
|
47
|
+
className="relative size-full overflow-clip px-(--viewport-inline-padding) py-1 [--viewport-inline-padding:--spacing(2)] data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:truncate **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity"
|
|
48
|
+
data-slot="tooltip-viewport"
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</TooltipPrimitive.Viewport>
|
|
52
|
+
</TooltipPrimitive.Popup>
|
|
53
|
+
</TooltipPrimitive.Positioner>
|
|
54
|
+
</TooltipPrimitive.Portal>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
TooltipCreateHandle,
|
|
60
|
+
TooltipProvider,
|
|
61
|
+
Tooltip,
|
|
62
|
+
TooltipTrigger,
|
|
63
|
+
TooltipPopup,
|
|
64
|
+
TooltipPopup as TooltipContent,
|
|
65
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768;
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
|
7
|
+
undefined,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
12
|
+
const onChange = () => {
|
|
13
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
14
|
+
};
|
|
15
|
+
mql.addEventListener("change", onChange);
|
|
16
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
17
|
+
return () => mql.removeEventListener("change", onChange);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return !!isMobile;
|
|
21
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import i18n from 'i18next'
|
|
2
|
+
import { initReactI18next } from 'react-i18next'
|
|
3
|
+
import LanguageDetector from 'i18next-browser-languagedetector'
|
|
4
|
+
|
|
5
|
+
const resources = {
|
|
6
|
+
en: {
|
|
7
|
+
translation: {
|
|
8
|
+
tagline: 'Build end to end',
|
|
9
|
+
description: 'Create your app with AI. Describe your idea — Strayl will build, deploy, and host it for you. No coding required.',
|
|
10
|
+
deployButton: 'Deploy to Strayl',
|
|
11
|
+
beginJourney: 'Begin your journey by asking the agent to edit your app',
|
|
12
|
+
toggleTheme: 'Toggle theme',
|
|
13
|
+
language: 'Language',
|
|
14
|
+
languages: {
|
|
15
|
+
en: 'English',
|
|
16
|
+
ru: 'Русский',
|
|
17
|
+
kk: 'Қазақша',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
ru: {
|
|
22
|
+
translation: {
|
|
23
|
+
tagline: 'Создавай от начала до конца',
|
|
24
|
+
description: 'Создайте приложение с помощью ИИ. Опишите идею — Strayl соберёт, задеплоит и захостит его за вас. Код писать не нужно.',
|
|
25
|
+
deployButton: 'Деплой на Strayl',
|
|
26
|
+
beginJourney: 'Начните путь, попросив агента отредактировать ваше приложение',
|
|
27
|
+
toggleTheme: 'Сменить тему',
|
|
28
|
+
language: 'Язык',
|
|
29
|
+
languages: {
|
|
30
|
+
en: 'English',
|
|
31
|
+
ru: 'Русский',
|
|
32
|
+
kk: 'Қазақша',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
kk: {
|
|
37
|
+
translation: {
|
|
38
|
+
tagline: 'Басынан аяғына дейін құр',
|
|
39
|
+
description: 'AI көмегімен қосымша жасаңыз. Идеяңызды сипаттаңыз — Strayl оны құрастырып, орналастырып, хостинг жасайды. Код жазу қажет емес.',
|
|
40
|
+
deployButton: 'Strayl-ға орналастыру',
|
|
41
|
+
beginJourney: 'Агенттен қосымшаңызды өңдеуді сұрап, саяхатыңызды бастаңыз',
|
|
42
|
+
toggleTheme: 'Тақырыпты ауыстыру',
|
|
43
|
+
language: 'Тіл',
|
|
44
|
+
languages: {
|
|
45
|
+
en: 'English',
|
|
46
|
+
ru: 'Русский',
|
|
47
|
+
kk: 'Қазақша',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
i18n
|
|
54
|
+
.use(LanguageDetector)
|
|
55
|
+
.use(initReactI18next)
|
|
56
|
+
.init({
|
|
57
|
+
resources,
|
|
58
|
+
fallbackLng: 'en',
|
|
59
|
+
supportedLngs: ['en', 'ru', 'kk'],
|
|
60
|
+
load: 'languageOnly',
|
|
61
|
+
interpolation: {
|
|
62
|
+
escapeValue: false,
|
|
63
|
+
},
|
|
64
|
+
detection: {
|
|
65
|
+
order: ['localStorage', 'navigator'],
|
|
66
|
+
caches: ['localStorage'],
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export default i18n
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
+
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
+
|
|
14
|
+
const IndexRoute = IndexRouteImport.update({
|
|
15
|
+
id: '/',
|
|
16
|
+
path: '/',
|
|
17
|
+
getParentRoute: () => rootRouteImport,
|
|
18
|
+
} as any)
|
|
19
|
+
|
|
20
|
+
export interface FileRoutesByFullPath {
|
|
21
|
+
'/': typeof IndexRoute
|
|
22
|
+
}
|
|
23
|
+
export interface FileRoutesByTo {
|
|
24
|
+
'/': typeof IndexRoute
|
|
25
|
+
}
|
|
26
|
+
export interface FileRoutesById {
|
|
27
|
+
__root__: typeof rootRouteImport
|
|
28
|
+
'/': typeof IndexRoute
|
|
29
|
+
}
|
|
30
|
+
export interface FileRouteTypes {
|
|
31
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
32
|
+
fullPaths: '/'
|
|
33
|
+
fileRoutesByTo: FileRoutesByTo
|
|
34
|
+
to: '/'
|
|
35
|
+
id: '__root__' | '/'
|
|
36
|
+
fileRoutesById: FileRoutesById
|
|
37
|
+
}
|
|
38
|
+
export interface RootRouteChildren {
|
|
39
|
+
IndexRoute: typeof IndexRoute
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module '@tanstack/react-router' {
|
|
43
|
+
interface FileRoutesByPath {
|
|
44
|
+
'/': {
|
|
45
|
+
id: '/'
|
|
46
|
+
path: '/'
|
|
47
|
+
fullPath: '/'
|
|
48
|
+
preLoaderRoute: typeof IndexRouteImport
|
|
49
|
+
parentRoute: typeof rootRouteImport
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
55
|
+
IndexRoute: IndexRoute,
|
|
56
|
+
}
|
|
57
|
+
export const routeTree = rootRouteImport
|
|
58
|
+
._addFileChildren(rootRouteChildren)
|
|
59
|
+
._addFileTypes<FileRouteTypes>()
|
|
60
|
+
|
|
61
|
+
import type { getRouter } from './router.tsx'
|
|
62
|
+
import type { createStart } from '@tanstack/react-start'
|
|
63
|
+
declare module '@tanstack/react-start' {
|
|
64
|
+
interface Register {
|
|
65
|
+
ssr: true
|
|
66
|
+
router: Awaited<ReturnType<typeof getRouter>>
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
// Import the generated route tree
|
|
4
|
+
import { routeTree } from './routeTree.gen'
|
|
5
|
+
|
|
6
|
+
// Create a new router instance
|
|
7
|
+
export const getRouter = () => {
|
|
8
|
+
const router = createRouter({
|
|
9
|
+
routeTree,
|
|
10
|
+
context: {},
|
|
11
|
+
|
|
12
|
+
scrollRestoration: true,
|
|
13
|
+
defaultPreloadStaleTime: 0,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
return router
|
|
17
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
// Fontsource imports - must be in JS, not CSS
|
|
4
|
+
import '@fontsource-variable/geist'
|
|
5
|
+
import '@fontsource-variable/geist-mono'
|
|
6
|
+
import '@fontsource-variable/bitcount-prop-single'
|
|
7
|
+
|
|
8
|
+
// i18n
|
|
9
|
+
import '../lib/i18n'
|
|
10
|
+
|
|
11
|
+
import { ThemeProvider } from '../components/theme-provider'
|
|
12
|
+
|
|
13
|
+
import appCss from '../styles.css?url'
|
|
14
|
+
|
|
15
|
+
export const Route = createRootRoute({
|
|
16
|
+
head: () => ({
|
|
17
|
+
meta: [
|
|
18
|
+
{
|
|
19
|
+
charSet: 'utf-8',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'viewport',
|
|
23
|
+
content: 'width=device-width, initial-scale=1',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: 'Strayl',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'description',
|
|
30
|
+
content: 'Build end to end',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
links: [
|
|
34
|
+
{
|
|
35
|
+
rel: 'stylesheet',
|
|
36
|
+
href: appCss,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rel: 'icon',
|
|
40
|
+
href: '/favicon.ico',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
shellComponent: RootDocument,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
49
|
+
return (
|
|
50
|
+
<html lang="en" suppressHydrationWarning>
|
|
51
|
+
<head>
|
|
52
|
+
<HeadContent />
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<ThemeProvider>
|
|
56
|
+
{children}
|
|
57
|
+
</ThemeProvider>
|
|
58
|
+
<Scripts />
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { useTheme } from 'next-themes'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
import { Moon, Sun } from 'lucide-react'
|
|
6
|
+
import { ThemedLogo } from '../components/themed-logo'
|
|
7
|
+
import { LanguageSwitcher } from '../components/language-switcher'
|
|
8
|
+
import { Button } from '@/components/ui/button'
|
|
9
|
+
|
|
10
|
+
export const Route = createFileRoute('/')({ component: App })
|
|
11
|
+
|
|
12
|
+
function App() {
|
|
13
|
+
const { resolvedTheme, setTheme } = useTheme()
|
|
14
|
+
const { t } = useTranslation()
|
|
15
|
+
const [mounted, setMounted] = useState(false)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setMounted(true)
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="min-h-screen bg-background">
|
|
23
|
+
{/* Language switcher - top left */}
|
|
24
|
+
<div className="fixed top-4 left-4 z-50">
|
|
25
|
+
<LanguageSwitcher />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
{/* Theme toggle - top right */}
|
|
29
|
+
<div className="fixed top-4 right-4 z-50">
|
|
30
|
+
<Button
|
|
31
|
+
variant="outline"
|
|
32
|
+
size="icon"
|
|
33
|
+
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
|
|
34
|
+
aria-label={t('toggleTheme')}
|
|
35
|
+
>
|
|
36
|
+
{mounted ? (
|
|
37
|
+
resolvedTheme === 'dark' ? <Sun className="size-4" /> : <Moon className="size-4" />
|
|
38
|
+
) : (
|
|
39
|
+
<div className="size-4" />
|
|
40
|
+
)}
|
|
41
|
+
</Button>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{/* Hero */}
|
|
45
|
+
<section className="relative py-20 px-6 text-center overflow-hidden">
|
|
46
|
+
<div className="relative max-w-5xl mx-auto">
|
|
47
|
+
<div className="flex items-center justify-center gap-4 mb-6">
|
|
48
|
+
<h1 className="text-6xl md:text-8xl font-normal text-foreground font-[family-name:var(--font-bitcount)]">
|
|
49
|
+
STRAYL
|
|
50
|
+
</h1>
|
|
51
|
+
</div>
|
|
52
|
+
<p className="text-2xl md:text-3xl text-muted-foreground mb-4 font-light">
|
|
53
|
+
{t('tagline')}
|
|
54
|
+
</p>
|
|
55
|
+
<p className="text-lg text-muted-foreground max-w-3xl mx-auto mb-8">
|
|
56
|
+
{t('description')}
|
|
57
|
+
</p>
|
|
58
|
+
<div className="flex flex-col items-center gap-4">
|
|
59
|
+
<Button size="lg" render={<a href="https://strayl.dev" target="_blank" rel="noopener noreferrer" />}>
|
|
60
|
+
<ThemedLogo width={20} height={20} inverted />
|
|
61
|
+
{t('deployButton')}
|
|
62
|
+
</Button>
|
|
63
|
+
<p className="text-muted-foreground text-sm mt-2">
|
|
64
|
+
{t('beginJourney')}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|